From 75ff0f265f5bf3dc076bbc6067a64e4ce3d0fa64 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Fri, 25 Sep 2020 19:35:55 +0200 Subject: [PATCH] Better PFM support. (cherry picked from commit 623d13a517b7b727076769976a2a9f4afe2a552f) --- .../twelvemonkeys/imageio/util/IIOUtil.java | 17 +++++++ .../imageio/plugins/pnm/PNMHeader.java | 15 +++---- .../imageio/plugins/pnm/PNMImageReader.java | 45 ++++++++----------- .../imageio/plugins/pnm/PNMMetadata.java | 5 ++- 4 files changed, 45 insertions(+), 37 deletions(-) diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOUtil.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOUtil.java index 0fe52052..072a65c1 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOUtil.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOUtil.java @@ -291,4 +291,21 @@ public final class IIOUtil { System.arraycopy(srcRow, srcPos + x, destRow, destPos + x / samplePeriod, pixelStride); } } + + public static void subsampleRow(float[] srcRow, int srcPos, int srcWidth, + float[] destRow, int destPos, + int samplesPerPixel, int bitsPerSample, int samplePeriod) { + Validate.isTrue(samplePeriod > 1, "samplePeriod must be > 1"); // Period == 1 could be a no-op... + Validate.isTrue(bitsPerSample > 0 && bitsPerSample <= 32 && (bitsPerSample == 1 || bitsPerSample % 2 == 0), + "bitsPerSample must be > 0 and <= 32 and a power of 2"); + Validate.isTrue(samplesPerPixel > 0, "samplesPerPixel must be > 0"); + Validate.isTrue(samplesPerPixel * bitsPerSample <= 32 || samplesPerPixel * bitsPerSample % 32 == 0, + "samplesPerPixel * bitsPerSample must be < 32 or a multiple of 32 "); + + int pixelStride = bitsPerSample * samplesPerPixel / 32; + for (int x = 0; x < srcWidth * pixelStride; x += samplePeriod * pixelStride) { + // System.arraycopy should be intrinsic, but consider using direct array access for pixelStride == 1 + System.arraycopy(srcRow, srcPos + x, destRow, destPos + x / samplePeriod, pixelStride); + } + } } \ No newline at end of file diff --git a/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMHeader.java b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMHeader.java index 902e6ff1..62629d27 100755 --- a/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMHeader.java +++ b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMHeader.java @@ -45,6 +45,7 @@ final class PNMHeader { private final TupleType tupleType; private final int width; private final int height; + private final float maxSampleFloat; private final int maxSample; private final List comments; @@ -57,6 +58,7 @@ final class PNMHeader { this.height = isTrue(height > 0, height, "height must be greater than: %d"); isTrue(depth == tupleType.getSamplesPerPixel(), depth, String.format("incorrect depth for %s, expected %d: %d", tupleType, tupleType.getSamplesPerPixel(), depth)); this.maxSample = isTrue(tupleType.isValidMaxSample(maxSample), maxSample, "maxSample out of range: %d"); + this.maxSampleFloat = this.maxSample; this.comments = Collections.unmodifiableList(new ArrayList(comments)); @@ -70,7 +72,8 @@ final class PNMHeader { this.height = isTrue(height > 0, height, "height must be greater than: %d"); isTrue(depth == tupleType.getSamplesPerPixel(), depth, String.format("incorrect depth for %s, expected %d: %d", tupleType, tupleType.getSamplesPerPixel(), depth)); - this.maxSample = -1; + this.maxSample = 1; + this.maxSampleFloat = maxSample; this.byteOrder = byteOrder; this.comments = Collections.unmodifiableList(new ArrayList(comments)); @@ -118,11 +121,8 @@ final class PNMHeader { if (maxSample <= PNM.MAX_VAL_16BIT) { return 16; } - if ((maxSample & 0xffffffffL) <= PNM.MAX_VAL_32BIT) { - return 32; - } - throw new AssertionError("maxSample exceeds 32 bit"); + return 32; } public int getTransferType() { @@ -135,11 +135,8 @@ final class PNMHeader { if (maxSample <= PNM.MAX_VAL_16BIT) { return DataBuffer.TYPE_USHORT; } - if ((maxSample & 0xffffffffL) <= PNM.MAX_VAL_32BIT) { - return DataBuffer.TYPE_INT; - } - throw new AssertionError("maxSample exceeds 32 bit"); + return DataBuffer.TYPE_INT; } public List getComments() { diff --git a/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReader.java b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReader.java index 3a9ce0ae..fac4a7c6 100755 --- a/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReader.java +++ b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReader.java @@ -49,11 +49,13 @@ import java.io.DataInput; import java.io.DataInputStream; import java.io.File; import java.io.IOException; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import static com.twelvemonkeys.imageio.util.IIOUtil.subsampleRow; + public final class PNMImageReader extends ImageReaderBase { // TODO: Allow reading unknown tuple types as Raster! // TODO: readAsRenderedImage? @@ -83,7 +85,7 @@ public final class PNMImageReader extends ImageReaderBase { static String asASCII(final short type) { byte[] asciiBytes = {(byte) ((type >> 8) & 0xff), (byte) (type & 0xff)}; - return new String(asciiBytes, Charset.forName("ASCII")); + return new String(asciiBytes, StandardCharsets.US_ASCII); } @Override @@ -150,7 +152,6 @@ public final class PNMImageReader extends ImageReaderBase { default: // TODO: Allow reading unknown tuple types as Raster! - throw new AssertionError("Unknown PNM tuple type: " + header.getTupleType()); } } @@ -261,7 +262,7 @@ public final class PNMImageReader extends ImageReaderBase { for (int y = 0; y < height; y++) { switch (transferType) { case DataBuffer.TYPE_BYTE: - readRowByte(destRaster, clippedRow, colorConvert, rowDataByte, samplesPerPixel, input, y, srcRegion, xSub, ySub); + readRowByte(destRaster, clippedRow, colorConvert, rowDataByte, header.getBitsPerSample(), samplesPerPixel, input, y, srcRegion, xSub, ySub); break; case DataBuffer.TYPE_USHORT: readRowUShort(destRaster, clippedRow, rowDataUShort, samplesPerPixel, input, y, srcRegion, xSub, ySub); @@ -339,7 +340,7 @@ public final class PNMImageReader extends ImageReaderBase { Raster rowRaster, final ColorConvertOp colorConvert, final byte[] rowDataByte, - final int samplesPerPixel, + final int bitsPerSample, final int samplesPerPixel, final DataInput input, final int y, final Rectangle srcRegion, final int xSub, final int ySub) throws IOException { @@ -352,7 +353,9 @@ public final class PNMImageReader extends ImageReaderBase { input.readFully(rowDataByte); // Subsample (horizontal) - subsampleHorizontal(rowDataByte, rowDataByte.length, samplesPerPixel, xSub); + if (xSub > 1) { + subsampleRow(rowDataByte, srcRegion.x, srcRegion.width, rowDataByte, 0, samplesPerPixel, bitsPerSample, xSub); + } normalize(rowDataByte, 0, rowDataByte.length / xSub); @@ -379,7 +382,9 @@ public final class PNMImageReader extends ImageReaderBase { readFully(input, rowDataUShort); // Subsample (horizontal) - subsampleHorizontal(rowDataUShort, rowDataUShort.length, samplesPerPixel, xSub); + if (xSub > 1) { + subsampleRow(rowDataUShort, srcRegion.x, srcRegion.width, rowDataUShort, 0, samplesPerPixel, 16, xSub); + } normalize(rowDataUShort); @@ -402,11 +407,14 @@ public final class PNMImageReader extends ImageReaderBase { readFully(input, rowDataFloat); // Subsample (horizontal) - subsampleHorizontal(rowDataFloat, rowDataFloat.length, samplesPerPixel, xSub); + if (xSub > 1) { + subsampleRow(rowDataFloat, srcRegion.x, srcRegion.width, rowDataFloat, 0, samplesPerPixel, 32, xSub); + } normalize(rowDataFloat); - int destY = (y - srcRegion.y) / ySub; + // Note: PFM is stored bottom to top! + int destY = destRaster.getHeight() - 1 - (y - srcRegion.y) / ySub; // TODO: ColorConvertOp if needed destRaster.setDataElements(0, destY, rowRaster); } @@ -437,19 +445,6 @@ public final class PNMImageReader extends ImageReaderBase { } } - @SuppressWarnings("SuspiciousSystemArraycopy") - private void subsampleHorizontal(final Object data, final int length, final int samplesPerPixel, final int xSub) { - if (xSub == 1) { - return; - } - - // TODO: Super-special 1 bit subsampling handling for PBM - - for (int x = 0; x < length / xSub; x += samplesPerPixel) { - System.arraycopy(data, x * xSub, data, x, samplesPerPixel); - } - } - private void normalize(final byte[] rowData, final int start, final int length) { switch (header.getTupleType()) { case BLACKANDWHITE: @@ -484,13 +479,9 @@ public final class PNMImageReader extends ImageReaderBase { } private void normalize(final float[] rowData) { - // TODO: Do the real thing, find min/max and normalize to range 0...255? But only if not reading raster..? Only support reading as raster? // Normalize for (int i = 0; i < rowData.length; i++) { -// if (rowData[i] > 275f /*header.getMaxSampleFloat()*/) { -// System.out.println("rowData[" + i + "]: " + rowData[i]); -// } -// rowData[i] = rowData[i] / 275f /*header.getMaxSampleFloat()*/; + rowData[i] = Math.min(1, rowData[i]); // Clamp } } diff --git a/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMMetadata.java b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMMetadata.java index d6f75e6f..cc11bc03 100755 --- a/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMMetadata.java +++ b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMMetadata.java @@ -151,7 +151,10 @@ final class PNMMetadata extends AbstractMetadata { IIOMetadataNode dimension = new IIOMetadataNode("Dimension"); IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation"); - imageOrientation.setAttribute("value", "Normal"); + imageOrientation.setAttribute("value", + header.getFileType() == PNM.PFM_GRAY || header.getFileType() == PNM.PFM_RGB + ? "FlipH" + : "Normal"); dimension.appendChild(imageOrientation); return dimension;