PNM clean-up.

This commit is contained in:
Harald Kuhr 2022-02-09 20:12:49 +01:00
parent 60eab81709
commit 4d190892df
6 changed files with 96 additions and 48 deletions

View File

@ -35,6 +35,7 @@ import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
import javax.imageio.ImageReader; import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageInputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteOrder;
import java.util.Locale; import java.util.Locale;
public final class PAMImageReaderSpi extends ImageReaderSpiBase { public final class PAMImageReaderSpi extends ImageReaderSpiBase {
@ -53,13 +54,16 @@ public final class PAMImageReaderSpi extends ImageReaderSpiBase {
} }
ImageInputStream stream = (ImageInputStream) source; ImageInputStream stream = (ImageInputStream) source;
ByteOrder order = stream.getByteOrder();
stream.mark(); stream.mark();
try { try {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
return stream.readShort() == PNM.PAM && stream.readInt() != PNM.XV_THUMBNAIL_MAGIC; return stream.readShort() == PNM.PAM && stream.readInt() != PNM.XV_THUMBNAIL_MAGIC;
} }
finally { finally {
stream.reset(); stream.reset();
stream.setByteOrder(order);
} }
} }

View File

@ -32,7 +32,6 @@ package com.twelvemonkeys.imageio.plugins.pnm;
import com.twelvemonkeys.io.FastByteArrayOutputStream; import com.twelvemonkeys.io.FastByteArrayOutputStream;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
@ -78,12 +77,12 @@ final class PNMHeaderParser extends HeaderParser {
while (width == 0 || height == 0 || maxSample == 0) { while (width == 0 || height == 0 || maxSample == 0) {
tokenBuffer.delete(0, tokenBuffer.length()); 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(); byte read = input.readByte();
if (read == '#') { if (read == '#') {
// Read rest of the line as comment // Read rest of the line as comment
String comment = readComment(input); String comment = readLineUTF8(input);
if (!comment.trim().isEmpty()) { if (!comment.trim().isEmpty()) {
comments.add(comment); comments.add(comment);
@ -111,11 +110,8 @@ final class PNMHeaderParser extends HeaderParser {
else if (height == 0) { else if (height == 0) {
height = Integer.parseInt(token); height = Integer.parseInt(token);
} }
else if (maxSample == 0) {
maxSample = Integer.parseInt(token);
}
else { 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); return new PNMHeader(fileType, tupleType, width, height, tupleType.getSamplesPerPixel(), maxSample, comments);
} }
private static String readComment(final ImageInputStream input) throws IOException { // Similar to DataInput.readLine, except it uses UTF8 encoding
ByteArrayOutputStream commentBuffer = new FastByteArrayOutputStream(128); private static String readLineUTF8(final ImageInputStream input) throws IOException {
ByteArrayOutputStream buffer = new FastByteArrayOutputStream(128);
int read; int value;
do { do {
switch (read = input.read()) { switch (value = input.read()) {
case -1:
case '\n':
case '\r': 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; break;
default: default:
commentBuffer.write(read); buffer.write(value);
} }
} while (read != -1); } while (value != -1);
return commentBuffer.toString("UTF8"); return buffer.toString("UTF8");
} }
} }

View File

@ -49,12 +49,14 @@ import java.io.DataInput;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import static com.twelvemonkeys.imageio.util.IIOUtil.subsampleRow; import static com.twelvemonkeys.imageio.util.IIOUtil.subsampleRow;
import static com.twelvemonkeys.imageio.util.RasterUtils.asByteRaster;
public final class PNMImageReader extends ImageReaderBase { public final class PNMImageReader extends ImageReaderBase {
// TODO: Allow reading unknown tuple types as Raster! // TODO: Allow reading unknown tuple types as Raster!
@ -73,6 +75,7 @@ public final class PNMImageReader extends ImageReaderBase {
private void readHeader() throws IOException { private void readHeader() throws IOException {
if (header == null) { if (header == null) {
imageInput.setByteOrder(ByteOrder.BIG_ENDIAN);
header = HeaderParser.parse(imageInput); header = HeaderParser.parse(imageInput);
imageInput.flushBefore(imageInput.getStreamPosition()); imageInput.flushBefore(imageInput.getStreamPosition());
@ -122,27 +125,19 @@ public final class PNMImageReader extends ImageReaderBase {
case GRAYSCALE_ALPHA: case GRAYSCALE_ALPHA:
case BLACKANDWHITE: case BLACKANDWHITE:
case GRAYSCALE: 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) { if (header.getMaxSample() <= PNM.MAX_VAL_16BIT) {
return hasAlpha ? ImageTypeSpecifiers.createGrayscale(bitsPerSample, transferType, false) return hasAlpha ? ImageTypeSpecifiers.createGrayscale(bitsPerSample, transferType, false)
: ImageTypeSpecifiers.createGrayscale(bitsPerSample, transferType); : 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); return ImageTypeSpecifiers.createInterleaved(gray, createBandOffsets(samplesPerPixel), transferType, hasAlpha, false);
case RGB: case RGB:
case RGB_ALPHA: 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) // 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); 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); return ImageTypeSpecifiers.createInterleaved(sRGB, createBandOffsets(samplesPerPixel), transferType, hasAlpha, false);
case CMYK: case CMYK:
@ -185,10 +180,9 @@ public final class PNMImageReader extends ImageReaderBase {
case RGB_ALPHA: case RGB_ALPHA:
if (header.getTransferType() == DataBuffer.TYPE_BYTE) { if (header.getTransferType() == DataBuffer.TYPE_BYTE) {
specifiers.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR)); 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_4BYTE_ABGR_PRE));
// specifiers.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB_PRE));
} }
break; break;
@ -215,11 +209,6 @@ public final class PNMImageReader extends ImageReaderBase {
Rectangle destRegion = new Rectangle(); Rectangle destRegion = new Rectangle();
computeRegions(param, width, height, destination, srcRegion, destRegion); 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(); WritableRaster rowRaster = rawType.createBufferedImage(width, 1).getRaster();
// Clip to source region // Clip to source region
Raster clippedRow = clipRowToRect(rowRaster, srcRegion, Raster clippedRow = clipRowToRect(rowRaster, srcRegion,
@ -247,10 +236,10 @@ public final class PNMImageReader extends ImageReaderBase {
throw new AssertionError("Unsupported transfer type: " + transferType); throw new AssertionError("Unsupported transfer type: " + transferType);
} }
ColorConvertOp colorConvert = null; WritableRaster destRaster = transferType == DataBuffer.TYPE_BYTE ? asByteRaster(destination.getRaster())
if (!destination.getColorModel().isCompatibleRaster(rowRaster)) { : destination.getRaster();
colorConvert = new ColorConvertOp(rawType.getColorModel().getColorSpace(), destination.getColorModel().getColorSpace(), null); destRaster = clipToRect(destRaster, destRegion, param != null ? param.getDestinationBands() : null);
} checkReadParamBandSettings(param, rawType.getNumBands(), destRaster.getNumBands());
int xSub = param == null ? 1 : param.getSourceXSubsampling(); int xSub = param == null ? 1 : param.getSourceXSubsampling();
int ySub = param == null ? 1 : param.getSourceYSubsampling(); int ySub = param == null ? 1 : param.getSourceYSubsampling();
@ -262,7 +251,7 @@ public final class PNMImageReader extends ImageReaderBase {
for (int y = 0; y < height; y++) { for (int y = 0; y < height; y++) {
switch (transferType) { switch (transferType) {
case DataBuffer.TYPE_BYTE: 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; break;
case DataBuffer.TYPE_USHORT: case DataBuffer.TYPE_USHORT:
readRowUShort(destRaster, clippedRow, rowDataUShort, samplesPerPixel, input, y, srcRegion, xSub, ySub); 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(); processImageComplete();
return destination; return destination;
@ -338,7 +331,6 @@ public final class PNMImageReader extends ImageReaderBase {
private void readRowByte(final WritableRaster destRaster, private void readRowByte(final WritableRaster destRaster,
Raster rowRaster, Raster rowRaster,
final ColorConvertOp colorConvert,
final byte[] rowDataByte, final byte[] rowDataByte,
final int bitsPerSample, final int samplesPerPixel, final int bitsPerSample, final int samplesPerPixel,
final DataInput input, final int y, final DataInput input, final int y,
@ -357,13 +349,8 @@ public final class PNMImageReader extends ImageReaderBase {
normalize(rowDataByte, 0, rowDataByte.length / xSub); normalize(rowDataByte, 0, rowDataByte.length / xSub);
int destY = (y - srcRegion.y) / ySub; 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, private void readRowUShort(final WritableRaster destRaster,
Raster rowRaster, Raster rowRaster,

View File

@ -57,7 +57,7 @@ public final class PNMImageReaderSpi extends ImageReaderSpiBase {
stream.mark(); stream.mark();
try { try {
short magic = stream.readShort(); short magic = (short) ((stream.readByte() & 0xFF) << 8 | (stream.readByte() & 0xFF));
switch (magic) { switch (magic) {
case PNM.PBM_PLAIN: case PNM.PBM_PLAIN:

View File

@ -33,7 +33,9 @@ import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import org.junit.Test; import org.junit.Test;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader; import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageReaderSpi; import javax.imageio.spi.ImageReaderSpi;
import java.awt.*; import java.awt.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
@ -102,6 +104,33 @@ public class PAMImageReaderTest extends ImageReaderAbstractTest<PNMImageReader>
assertImageDataEquals("Images differ from reference", expected, reader.read(0)); 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 @Test
public void testXVThumbNotIncorrectlyRecognizedAsPAM() throws IOException { public void testXVThumbNotIncorrectlyRecognizedAsPAM() throws IOException {
assertTrue("Should recognize PAM format", provider.canDecodeInput(new TestData(getClassLoaderResource("/pam/rgba.pam"), new Dimension()).getInputStream())); // Sanity assertTrue("Should recognize PAM format", provider.canDecodeInput(new TestData(getClassLoaderResource("/pam/rgba.pam"), new Dimension()).getInputStream())); // Sanity

View File

@ -33,7 +33,9 @@ import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import org.junit.Test; import org.junit.Test;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader; import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageReaderSpi; import javax.imageio.spi.ImageReaderSpi;
import java.awt.*; import java.awt.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
@ -120,4 +122,29 @@ public class PNMImageReaderTest extends ImageReaderAbstractTest<PNMImageReader>
assertImageDataEquals("Images differ from reference", expected, reader.read(0)); 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));
}
}
} }