PNM clean-up.

(cherry picked from commit 4d190892df5402938db4a7fba3b358ecadee486a)
This commit is contained in:
Harald Kuhr 2022-02-09 20:12:49 +01:00
parent 76a9ff1122
commit b51e8ccf6e
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.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);
}
}

View File

@ -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");
}
}

View File

@ -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,

View File

@ -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:

View File

@ -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<PNMImageReader>
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

View File

@ -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<PNMImageReader>
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));
}
}
}