From b51e8ccf6ea4b8ecfe8bd5828f29acf5327d0c13 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Wed, 9 Feb 2022 20:12:49 +0100 Subject: [PATCH] PNM clean-up. (cherry picked from commit 4d190892df5402938db4a7fba3b358ecadee486a) --- .../plugins/pnm/PAMImageReaderSpi.java | 4 ++ .../imageio/plugins/pnm/PNMHeaderParser.java | 35 +++++++------- .../imageio/plugins/pnm/PNMImageReader.java | 47 +++++++------------ .../plugins/pnm/PNMImageReaderSpi.java | 2 +- .../plugins/pnm/PAMImageReaderTest.java | 29 ++++++++++++ .../plugins/pnm/PNMImageReaderTest.java | 27 +++++++++++ 6 files changed, 96 insertions(+), 48 deletions(-) diff --git a/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PAMImageReaderSpi.java b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PAMImageReaderSpi.java index 6cd149e6..73841a7d 100755 --- a/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PAMImageReaderSpi.java +++ b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PAMImageReaderSpi.java @@ -35,6 +35,7 @@ import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase; import javax.imageio.ImageReader; import javax.imageio.stream.ImageInputStream; import java.io.IOException; +import java.nio.ByteOrder; import java.util.Locale; public final class PAMImageReaderSpi extends ImageReaderSpiBase { @@ -53,13 +54,16 @@ public final class PAMImageReaderSpi extends ImageReaderSpiBase { } ImageInputStream stream = (ImageInputStream) source; + ByteOrder order = stream.getByteOrder(); stream.mark(); try { + stream.setByteOrder(ByteOrder.BIG_ENDIAN); return stream.readShort() == PNM.PAM && stream.readInt() != PNM.XV_THUMBNAIL_MAGIC; } finally { stream.reset(); + stream.setByteOrder(order); } } diff --git a/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMHeaderParser.java b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMHeaderParser.java index e4db9b0c..02cab3c9 100755 --- a/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMHeaderParser.java +++ b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMHeaderParser.java @@ -32,7 +32,6 @@ package com.twelvemonkeys.imageio.plugins.pnm; import com.twelvemonkeys.io.FastByteArrayOutputStream; -import javax.imageio.IIOException; import javax.imageio.stream.ImageInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -78,12 +77,12 @@ final class PNMHeaderParser extends HeaderParser { while (width == 0 || height == 0 || maxSample == 0) { tokenBuffer.delete(0, tokenBuffer.length()); - while (tokenBuffer.length() < 16) { + while (tokenBuffer.length() < 16) { // Limit reads if we should read across into the binary part... byte read = input.readByte(); if (read == '#') { // Read rest of the line as comment - String comment = readComment(input); + String comment = readLineUTF8(input); if (!comment.trim().isEmpty()) { comments.add(comment); @@ -111,11 +110,8 @@ final class PNMHeaderParser extends HeaderParser { else if (height == 0) { height = Integer.parseInt(token); } - else if (maxSample == 0) { - maxSample = Integer.parseInt(token); - } else { - throw new IIOException("Unknown PNM token: " + token); + maxSample = Integer.parseInt(token); } } } @@ -123,22 +119,27 @@ final class PNMHeaderParser extends HeaderParser { return new PNMHeader(fileType, tupleType, width, height, tupleType.getSamplesPerPixel(), maxSample, comments); } - private static String readComment(final ImageInputStream input) throws IOException { - ByteArrayOutputStream commentBuffer = new FastByteArrayOutputStream(128); + // Similar to DataInput.readLine, except it uses UTF8 encoding + private static String readLineUTF8(final ImageInputStream input) throws IOException { + ByteArrayOutputStream buffer = new FastByteArrayOutputStream(128); - int read; + int value; do { - switch (read = input.read()) { - case -1: - case '\n': + switch (value = input.read()) { case '\r': - read = -1; + // Check for CR + LF pattern and skip, otherwise fall through + if (input.read() != '\n') { + input.seek(input.getStreamPosition() - 1); + } + case '\n': + case -1: + value = -1; break; default: - commentBuffer.write(read); + buffer.write(value); } - } while (read != -1); + } while (value != -1); - return commentBuffer.toString("UTF8"); + return buffer.toString("UTF8"); } } 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 7e036116..f432f7c9 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,12 +49,14 @@ import java.io.DataInput; import java.io.DataInputStream; import java.io.File; import java.io.IOException; +import java.nio.ByteOrder; 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; +import static com.twelvemonkeys.imageio.util.RasterUtils.asByteRaster; public final class PNMImageReader extends ImageReaderBase { // TODO: Allow reading unknown tuple types as Raster! @@ -73,6 +75,7 @@ public final class PNMImageReader extends ImageReaderBase { private void readHeader() throws IOException { if (header == null) { + imageInput.setByteOrder(ByteOrder.BIG_ENDIAN); header = HeaderParser.parse(imageInput); imageInput.flushBefore(imageInput.getStreamPosition()); @@ -122,27 +125,19 @@ public final class PNMImageReader extends ImageReaderBase { case GRAYSCALE_ALPHA: case BLACKANDWHITE: case GRAYSCALE: - // PGM: Linear or non-linear gray? - ColorSpace gray = ColorSpace.getInstance(ColorSpace.CS_GRAY); - - if (header.getTransferType() == DataBuffer.TYPE_FLOAT) { - return ImageTypeSpecifiers.createInterleaved(gray, createBandOffsets(samplesPerPixel), transferType, hasAlpha, false); - } if (header.getMaxSample() <= PNM.MAX_VAL_16BIT) { return hasAlpha ? ImageTypeSpecifiers.createGrayscale(bitsPerSample, transferType, false) : ImageTypeSpecifiers.createGrayscale(bitsPerSample, transferType); } + // PGM: Linear or non-linear gray? + ColorSpace gray = ColorSpace.getInstance(ColorSpace.CS_GRAY); return ImageTypeSpecifiers.createInterleaved(gray, createBandOffsets(samplesPerPixel), transferType, hasAlpha, false); case RGB: case RGB_ALPHA: // Using sRGB seems sufficient for PPM, as it is very close to ITU-R Recommendation BT.709 (same gamut and white point CIE D65) ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB); - if (header.getTransferType() == DataBuffer.TYPE_FLOAT) { - return ImageTypeSpecifiers.createInterleaved(sRGB, createBandOffsets(samplesPerPixel), transferType, hasAlpha, false); - } - return ImageTypeSpecifiers.createInterleaved(sRGB, createBandOffsets(samplesPerPixel), transferType, hasAlpha, false); case CMYK: @@ -185,10 +180,9 @@ public final class PNMImageReader extends ImageReaderBase { case RGB_ALPHA: if (header.getTransferType() == DataBuffer.TYPE_BYTE) { specifiers.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR)); - // TODO: Why does ColorConvertOp choke on these (Ok, because it misinterprets the alpha channel for a color component, but how do we make it work)? -// specifiers.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB)); + specifiers.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB)); + specifiers.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB_PRE)); specifiers.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR_PRE)); -// specifiers.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB_PRE)); } break; @@ -215,11 +209,6 @@ public final class PNMImageReader extends ImageReaderBase { Rectangle destRegion = new Rectangle(); computeRegions(param, width, height, destination, srcRegion, destRegion); - WritableRaster destRaster = clipToRect(destination.getRaster(), destRegion, param != null - ? param.getDestinationBands() - : null); - checkReadParamBandSettings(param, rawType.getNumBands(), destRaster.getNumBands()); - WritableRaster rowRaster = rawType.createBufferedImage(width, 1).getRaster(); // Clip to source region Raster clippedRow = clipRowToRect(rowRaster, srcRegion, @@ -247,10 +236,10 @@ public final class PNMImageReader extends ImageReaderBase { throw new AssertionError("Unsupported transfer type: " + transferType); } - ColorConvertOp colorConvert = null; - if (!destination.getColorModel().isCompatibleRaster(rowRaster)) { - colorConvert = new ColorConvertOp(rawType.getColorModel().getColorSpace(), destination.getColorModel().getColorSpace(), null); - } + WritableRaster destRaster = transferType == DataBuffer.TYPE_BYTE ? asByteRaster(destination.getRaster()) + : destination.getRaster(); + destRaster = clipToRect(destRaster, destRegion, param != null ? param.getDestinationBands() : null); + checkReadParamBandSettings(param, rawType.getNumBands(), destRaster.getNumBands()); int xSub = param == null ? 1 : param.getSourceXSubsampling(); int ySub = param == null ? 1 : param.getSourceYSubsampling(); @@ -262,7 +251,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, header.getBitsPerSample(), samplesPerPixel, input, y, srcRegion, xSub, ySub); + readRowByte(destRaster, clippedRow, rowDataByte, header.getBitsPerSample(), samplesPerPixel, input, y, srcRegion, xSub, ySub); break; case DataBuffer.TYPE_USHORT: readRowUShort(destRaster, clippedRow, rowDataUShort, samplesPerPixel, input, y, srcRegion, xSub, ySub); @@ -287,6 +276,10 @@ public final class PNMImageReader extends ImageReaderBase { } } + if (destination.isAlphaPremultiplied()) { + rawType.getColorModel().coerceData(destRaster, true); + } + processImageComplete(); return destination; @@ -338,7 +331,6 @@ public final class PNMImageReader extends ImageReaderBase { private void readRowByte(final WritableRaster destRaster, Raster rowRaster, - final ColorConvertOp colorConvert, final byte[] rowDataByte, final int bitsPerSample, final int samplesPerPixel, final DataInput input, final int y, @@ -357,12 +349,7 @@ public final class PNMImageReader extends ImageReaderBase { normalize(rowDataByte, 0, rowDataByte.length / xSub); int destY = (y - srcRegion.y) / ySub; - if (colorConvert != null) { - colorConvert.filter(rowRaster, destRaster.createWritableChild(0, destY, rowRaster.getWidth(), 1, 0, 0, null)); - } - else { - destRaster.setDataElements(0, destY, rowRaster); - } + destRaster.setDataElements(0, destY, rowRaster); } private void readRowUShort(final WritableRaster destRaster, diff --git a/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReaderSpi.java b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReaderSpi.java index 62863329..ab1cef7b 100755 --- a/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReaderSpi.java +++ b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReaderSpi.java @@ -57,7 +57,7 @@ public final class PNMImageReaderSpi extends ImageReaderSpiBase { stream.mark(); try { - short magic = stream.readShort(); + short magic = (short) ((stream.readByte() & 0xFF) << 8 | (stream.readByte() & 0xFF)); switch (magic) { case PNM.PBM_PLAIN: diff --git a/imageio/imageio-pnm/src/test/java/com/twelvemonkeys/imageio/plugins/pnm/PAMImageReaderTest.java b/imageio/imageio-pnm/src/test/java/com/twelvemonkeys/imageio/plugins/pnm/PAMImageReaderTest.java index 564684d6..a8e61c52 100755 --- a/imageio/imageio-pnm/src/test/java/com/twelvemonkeys/imageio/plugins/pnm/PAMImageReaderTest.java +++ b/imageio/imageio-pnm/src/test/java/com/twelvemonkeys/imageio/plugins/pnm/PAMImageReaderTest.java @@ -33,7 +33,9 @@ import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest; import org.junit.Test; +import javax.imageio.ImageReadParam; import javax.imageio.ImageReader; +import javax.imageio.ImageTypeSpecifier; import javax.imageio.spi.ImageReaderSpi; import java.awt.*; import java.awt.image.BufferedImage; @@ -102,6 +104,33 @@ public class PAMImageReaderTest extends ImageReaderAbstractTest assertImageDataEquals("Images differ from reference", expected, reader.read(0)); } + @Test + public void testTypes() throws IOException { + ImageReader reader = createReader(); + TestData data = new TestData(getClassLoaderResource("/pam/rgba.pam"), new Dimension(4, 2)); + reader.setInput(data.getInputStream()); + + int[] types = new int[] {BufferedImage.TYPE_INT_ARGB, BufferedImage.TYPE_4BYTE_ABGR, BufferedImage.TYPE_INT_ARGB_PRE}; + + for (int type : types) { + BufferedImage expected = new BufferedImage(4, 2, type); + + expected.setRGB(0, 0, new Color(0, 0, 255).getRGB()); + expected.setRGB(1, 0, new Color(0, 255, 0).getRGB()); + expected.setRGB(2, 0, new Color(255, 0, 0).getRGB()); + expected.setRGB(3, 0, new Color(255, 255, 255).getRGB()); + + expected.setRGB(0, 1, new Color(0, 0, 255, 127).getRGB()); + expected.setRGB(1, 1, new Color(0, 255, 0, 127).getRGB()); + expected.setRGB(2, 1, new Color(255, 0, 0, 127).getRGB()); + expected.setRGB(3, 1, new Color(255, 255, 255, 127).getRGB()); + + ImageReadParam param = reader.getDefaultReadParam(); + param.setDestinationType(ImageTypeSpecifier.createFromBufferedImageType(type)); + assertImageDataEquals("Images differ from reference for type: " + type, expected, reader.read(0, param)); + } + } + @Test public void testXVThumbNotIncorrectlyRecognizedAsPAM() throws IOException { assertTrue("Should recognize PAM format", provider.canDecodeInput(new TestData(getClassLoaderResource("/pam/rgba.pam"), new Dimension()).getInputStream())); // Sanity diff --git a/imageio/imageio-pnm/src/test/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReaderTest.java b/imageio/imageio-pnm/src/test/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReaderTest.java index a4656d1d..2b363906 100755 --- a/imageio/imageio-pnm/src/test/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReaderTest.java +++ b/imageio/imageio-pnm/src/test/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReaderTest.java @@ -33,7 +33,9 @@ import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest; import org.junit.Test; +import javax.imageio.ImageReadParam; import javax.imageio.ImageReader; +import javax.imageio.ImageTypeSpecifier; import javax.imageio.spi.ImageReaderSpi; import java.awt.*; import java.awt.image.BufferedImage; @@ -120,4 +122,29 @@ public class PNMImageReaderTest extends ImageReaderAbstractTest assertImageDataEquals("Images differ from reference", expected, reader.read(0)); } + + @Test + public void testTypes() throws IOException { + ImageReader reader = createReader(); + TestData data = new TestData(getClassLoaderResource("/ppm/colors.ppm"), new Dimension(3, 2)); + reader.setInput(data.getInputStream()); + + int[] types = new int[] {BufferedImage.TYPE_INT_RGB, BufferedImage.TYPE_INT_BGR, BufferedImage.TYPE_3BYTE_BGR}; + + for (int type : types) { + BufferedImage expected = new BufferedImage(3, 2, type); + + expected.setRGB(0, 0, new Color(255, 0, 0).getRGB()); + expected.setRGB(1, 0, new Color(0, 255, 0).getRGB()); + expected.setRGB(2, 0, new Color(0, 0, 255).getRGB()); + + expected.setRGB(0, 1, new Color(255, 255, 0).getRGB()); + expected.setRGB(1, 1, new Color(255, 255, 255).getRGB()); + expected.setRGB(2, 1, new Color(0, 0, 0).getRGB()); + + ImageReadParam param = reader.getDefaultReadParam(); + param.setDestinationType(ImageTypeSpecifier.createFromBufferedImageType(type)); + assertImageDataEquals("Images differ from reference for type: " + type, expected, reader.read(0, param)); + } + } }