Easier subsampling with xSub == 1 as no-op

This commit is contained in:
Harald Kuhr 2021-03-27 14:37:33 +01:00
parent 3b34d6e7ce
commit bb650e5280
4 changed files with 59 additions and 40 deletions

View File

@ -223,7 +223,12 @@ public final class IIOUtil {
public static void subsampleRow(byte[] srcRow, int srcPos, int srcWidth, public static void subsampleRow(byte[] srcRow, int srcPos, int srcWidth,
byte[] destRow, int destPos, byte[] destRow, int destPos,
int samplesPerPixel, int bitsPerSample, int samplePeriod) { int samplesPerPixel, int bitsPerSample, int samplePeriod) {
Validate.isTrue(samplePeriod > 1, "samplePeriod must be > 1"); // Period == 1 could be a no-op... // Period == 1 is a no-op...
if (samplePeriod == 1) {
return;
}
Validate.isTrue(samplePeriod > 1, "samplePeriod must be > 1");
Validate.isTrue(bitsPerSample > 0 && bitsPerSample <= 8 && (bitsPerSample == 1 || bitsPerSample % 2 == 0), Validate.isTrue(bitsPerSample > 0 && bitsPerSample <= 8 && (bitsPerSample == 1 || bitsPerSample % 2 == 0),
"bitsPerSample must be > 0 and <= 8 and a power of 2"); "bitsPerSample must be > 0 and <= 8 and a power of 2");
Validate.isTrue(samplesPerPixel > 0, "samplesPerPixel must be > 0"); Validate.isTrue(samplesPerPixel > 0, "samplesPerPixel must be > 0");
@ -261,7 +266,12 @@ public final class IIOUtil {
public static void subsampleRow(short[] srcRow, int srcPos, int srcWidth, public static void subsampleRow(short[] srcRow, int srcPos, int srcWidth,
short[] destRow, int destPos, short[] destRow, int destPos,
int samplesPerPixel, int bitsPerSample, int samplePeriod) { int samplesPerPixel, int bitsPerSample, int samplePeriod) {
Validate.isTrue(samplePeriod > 1, "samplePeriod must be > 1"); // Period == 1 could be a no-op... // Period == 1 is a no-op...
if (samplePeriod == 1) {
return;
}
Validate.isTrue(samplePeriod > 1, "samplePeriod must be > 1");
Validate.isTrue(bitsPerSample > 0 && bitsPerSample <= 16 && (bitsPerSample == 1 || bitsPerSample % 2 == 0), Validate.isTrue(bitsPerSample > 0 && bitsPerSample <= 16 && (bitsPerSample == 1 || bitsPerSample % 2 == 0),
"bitsPerSample must be > 0 and <= 16 and a power of 2"); "bitsPerSample must be > 0 and <= 16 and a power of 2");
Validate.isTrue(samplesPerPixel > 0, "samplesPerPixel must be > 0"); Validate.isTrue(samplesPerPixel > 0, "samplesPerPixel must be > 0");
@ -278,7 +288,12 @@ public final class IIOUtil {
public static void subsampleRow(int[] srcRow, int srcPos, int srcWidth, public static void subsampleRow(int[] srcRow, int srcPos, int srcWidth,
int[] destRow, int destPos, int[] destRow, int destPos,
int samplesPerPixel, int bitsPerSample, int samplePeriod) { int samplesPerPixel, int bitsPerSample, int samplePeriod) {
Validate.isTrue(samplePeriod > 1, "samplePeriod must be > 1"); // Period == 1 could be a no-op... // Period == 1 is a no-op...
if (samplePeriod == 1) {
return;
}
Validate.isTrue(samplePeriod > 1, "samplePeriod must be > 1");
Validate.isTrue(bitsPerSample > 0 && bitsPerSample <= 32 && (bitsPerSample == 1 || bitsPerSample % 2 == 0), Validate.isTrue(bitsPerSample > 0 && bitsPerSample <= 32 && (bitsPerSample == 1 || bitsPerSample % 2 == 0),
"bitsPerSample must be > 0 and <= 32 and a power of 2"); "bitsPerSample must be > 0 and <= 32 and a power of 2");
Validate.isTrue(samplesPerPixel > 0, "samplesPerPixel must be > 0"); Validate.isTrue(samplesPerPixel > 0, "samplesPerPixel must be > 0");

View File

@ -353,10 +353,7 @@ public final class PNMImageReader extends ImageReaderBase {
input.readFully(rowDataByte); input.readFully(rowDataByte);
// Subsample (horizontal) // Subsample (horizontal)
if (xSub > 1) {
subsampleRow(rowDataByte, srcRegion.x, srcRegion.width, rowDataByte, 0, samplesPerPixel, bitsPerSample, xSub); subsampleRow(rowDataByte, srcRegion.x, srcRegion.width, rowDataByte, 0, samplesPerPixel, bitsPerSample, xSub);
}
normalize(rowDataByte, 0, rowDataByte.length / xSub); normalize(rowDataByte, 0, rowDataByte.length / xSub);
int destY = (y - srcRegion.y) / ySub; int destY = (y - srcRegion.y) / ySub;
@ -382,10 +379,7 @@ public final class PNMImageReader extends ImageReaderBase {
readFully(input, rowDataUShort); readFully(input, rowDataUShort);
// Subsample (horizontal) // Subsample (horizontal)
if (xSub > 1) {
subsampleRow(rowDataUShort, srcRegion.x, srcRegion.width, rowDataUShort, 0, samplesPerPixel, 16, xSub); subsampleRow(rowDataUShort, srcRegion.x, srcRegion.width, rowDataUShort, 0, samplesPerPixel, 16, xSub);
}
normalize(rowDataUShort); normalize(rowDataUShort);
int destY = (y - srcRegion.y) / ySub; int destY = (y - srcRegion.y) / ySub;

View File

@ -48,10 +48,10 @@ import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
import com.twelvemonkeys.imageio.metadata.xmp.XMPReader; import com.twelvemonkeys.imageio.metadata.xmp.XMPReader;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream; import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import com.twelvemonkeys.imageio.stream.SubImageInputStream; import com.twelvemonkeys.imageio.stream.SubImageInputStream;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers; import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import com.twelvemonkeys.imageio.util.ProgressListenerBase; import com.twelvemonkeys.imageio.util.ProgressListenerBase;
import com.twelvemonkeys.io.FastByteArrayOutputStream; import com.twelvemonkeys.io.FastByteArrayOutputStream;
import com.twelvemonkeys.io.FileUtil;
import com.twelvemonkeys.io.LittleEndianDataInputStream; import com.twelvemonkeys.io.LittleEndianDataInputStream;
import com.twelvemonkeys.io.enc.DecoderStream; import com.twelvemonkeys.io.enc.DecoderStream;
import com.twelvemonkeys.io.enc.PackBitsDecoder; import com.twelvemonkeys.io.enc.PackBitsDecoder;
@ -696,7 +696,7 @@ public final class TIFFImageReader extends ImageReaderBase {
} }
private int getPhotometricInterpretationWithFallback() throws IIOException { private int getPhotometricInterpretationWithFallback() throws IIOException {
// PhotometricInterpretation is a required TAG, but as it can be guessed this does a fallback that is equal to JAI ImageIO. // PhotometricInterpretation is a required tag, but as it can be guessed this does a fallback that is similar to JAI ImageIO.
int interpretation = getValueAsIntWithDefault(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, "PhotometricInterpretation", -1); int interpretation = getValueAsIntWithDefault(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, "PhotometricInterpretation", -1);
if (interpretation == -1) { if (interpretation == -1) {
int compression = getValueAsIntWithDefault(TIFF.TAG_COMPRESSION, TIFFBaseline.COMPRESSION_NONE); int compression = getValueAsIntWithDefault(TIFF.TAG_COMPRESSION, TIFFBaseline.COMPRESSION_NONE);
@ -713,8 +713,14 @@ public final class TIFFImageReader extends ImageReaderBase {
interpretation = TIFFBaseline.PHOTOMETRIC_PALETTE; interpretation = TIFFBaseline.PHOTOMETRIC_PALETTE;
} }
else if ((samplesPerPixel - extraSamples) == 3) { else if ((samplesPerPixel - extraSamples) == 3) {
if (compression == TIFFExtension.COMPRESSION_JPEG
|| compression == TIFFExtension.COMPRESSION_OLD_JPEG) {
interpretation = TIFFExtension.PHOTOMETRIC_YCBCR;
}
else {
interpretation = TIFFBaseline.PHOTOMETRIC_RGB; interpretation = TIFFBaseline.PHOTOMETRIC_RGB;
} }
}
else if ((samplesPerPixel - extraSamples) == 4) { else if ((samplesPerPixel - extraSamples) == 4) {
interpretation = TIFFExtension.PHOTOMETRIC_SEPARATED; interpretation = TIFFExtension.PHOTOMETRIC_SEPARATED;
} }
@ -959,10 +965,9 @@ public final class TIFFImageReader extends ImageReaderBase {
int tilesAcross = (width + stripTileWidth - 1) / stripTileWidth; int tilesAcross = (width + stripTileWidth - 1) / stripTileWidth;
int tilesDown = (height + stripTileHeight - 1) / stripTileHeight; int tilesDown = (height + stripTileHeight - 1) / stripTileHeight;
// TODO: Get number of extra samples not part of the rawType spec... // Raw type may contain extra samples
// TODO: If extrasamples, we might need to create a raster with more samples...
WritableRaster rowRaster = rawType.createBufferedImage(stripTileWidth, 1).getRaster(); WritableRaster rowRaster = rawType.createBufferedImage(stripTileWidth, 1).getRaster();
// WritableRaster rowRaster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, stripTileWidth, 1, 2, null).createWritableChild(0, 0, stripTileWidth, 1, 0, 0, new int[]{0});
Rectangle clip = new Rectangle(srcRegion); Rectangle clip = new Rectangle(srcRegion);
int srcRow = 0; int srcRow = 0;
Boolean needsCSConversion = null; Boolean needsCSConversion = null;
@ -1121,6 +1126,13 @@ public final class TIFFImageReader extends ImageReaderBase {
// TODO: Cache the JPEG reader for later use? Remember to reset to avoid resource leaks // TODO: Cache the JPEG reader for later use? Remember to reset to avoid resource leaks
ImageReader jpegReader = createJPEGDelegate(); ImageReader jpegReader = createJPEGDelegate();
// TODO: Use proper inner class + add case for old JPEG
jpegReader.addIIOReadWarningListener(new IIOReadWarningListener() {
@Override
public void warningOccurred(final ImageReader source, final String warning) {
processWarningOccurred(warning);
}
});
JPEGImageReadParam jpegParam = (JPEGImageReadParam) jpegReader.getDefaultReadParam(); JPEGImageReadParam jpegParam = (JPEGImageReadParam) jpegReader.getDefaultReadParam();
// JPEG_TABLES should be a full JPEG 'abbreviated table specification', containing: // JPEG_TABLES should be a full JPEG 'abbreviated table specification', containing:
@ -1135,7 +1147,8 @@ public final class TIFFImageReader extends ImageReaderBase {
// This initializes the tables and other internal settings for the reader, // This initializes the tables and other internal settings for the reader,
// and is actually a feature of JPEG, see abbreviated streams: // and is actually a feature of JPEG, see abbreviated streams:
// http://docs.oracle.com/javase/6/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html#abbrev // http://docs.oracle.com/javase/6/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html#abbrev
jpegReader.getStreamMetadata(); IIOMetadata streamMetadata = jpegReader.getStreamMetadata();
new XMLSerializer(System.out, "UTF8").serialize(streamMetadata.getAsTree(streamMetadata.getNativeMetadataFormatName()), false);
} }
else if (tilesDown * tilesAcross > 1) { else if (tilesDown * tilesAcross > 1) {
processWarningOccurred("Missing JPEGTables for tiled/striped TIFF with compression: 7 (JPEG)"); processWarningOccurred("Missing JPEGTables for tiled/striped TIFF with compression: 7 (JPEG)");
@ -1810,8 +1823,6 @@ public final class TIFFImageReader extends ImageReaderBase {
out.writeByte(0); // Spectral selection end out.writeByte(0); // Spectral selection end
out.writeByte(0); // Approx high & low out.writeByte(0); // Approx high & low
// System.err.println(TIFFReader.HexDump.dump(stream.toByteArray()));
//
return stream.createInputStream(); return stream.createInputStream();
} }
@ -1872,10 +1883,8 @@ public final class TIFFImageReader extends ImageReaderBase {
} }
// Subsample horizontal // Subsample horizontal
if (xSub != 1) { subsampleRow(rowDataByte, srcRegion.x * numBands, colsInTile,
IIOUtil.subsampleRow(rowDataByte, srcRegion.x * numBands, colsInTile,
rowDataByte, srcRegion.x * numBands / xSub, numBands, bitsPerSample, xSub); rowDataByte, srcRegion.x * numBands / xSub, numBands, bitsPerSample, xSub);
}
destChannel.setDataElements(startCol / xSub, (row - srcRegion.y) / ySub, srcChannel); destChannel.setDataElements(startCol / xSub, (row - srcRegion.y) / ySub, srcChannel);
} }
@ -1914,10 +1923,8 @@ public final class TIFFImageReader extends ImageReaderBase {
normalizeColor(interpretation, rowDataShort); normalizeColor(interpretation, rowDataShort);
// Subsample horizontal // Subsample horizontal
if (xSub != 1) {
subsampleRow(rowDataShort, srcRegion.x * numBands, colsInTile, subsampleRow(rowDataShort, srcRegion.x * numBands, colsInTile,
rowDataShort, srcRegion.x * numBands / xSub, numBands, bitsPerSample, xSub); rowDataShort, srcRegion.x * numBands / xSub, numBands, bitsPerSample, xSub);
}
destChannel.setDataElements(startCol / xSub, (row - srcRegion.y) / ySub, srcChannel); destChannel.setDataElements(startCol / xSub, (row - srcRegion.y) / ySub, srcChannel);
// TODO: Possible speedup ~30%!: // TODO: Possible speedup ~30%!:
@ -1950,10 +1957,8 @@ public final class TIFFImageReader extends ImageReaderBase {
normalizeColor(interpretation, rowDataInt); normalizeColor(interpretation, rowDataInt);
// Subsample horizontal // Subsample horizontal
if (xSub != 1) {
subsampleRow(rowDataInt, srcRegion.x * numBands, colsInTile, subsampleRow(rowDataInt, srcRegion.x * numBands, colsInTile,
rowDataInt, srcRegion.x * numBands / xSub, numBands, bitsPerSample, xSub); rowDataInt, srcRegion.x * numBands / xSub, numBands, bitsPerSample, xSub);
}
destChannel.setDataElements(startCol / xSub, (row - srcRegion.y) / ySub, srcChannel); destChannel.setDataElements(startCol / xSub, (row - srcRegion.y) / ySub, srcChannel);
} }
@ -2595,13 +2600,20 @@ public final class TIFFImageReader extends ImageReaderBase {
Iterator<ImageReader> readers = ImageIO.getImageReaders(input); Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
if (!readers.hasNext()) {
String suffix = FileUtil.getExtension(file.getName());
readers = ImageIO.getImageReadersBySuffix(suffix);
if (!readers.hasNext()) { if (!readers.hasNext()) {
System.err.println("No reader for: " + file); System.err.println("No reader for: " + file);
continue; continue;
} }
System.err.println("Could not determine file format, falling back to file extension: ." + suffix);
}
ImageReader reader = readers.next(); ImageReader reader = readers.next();
System.err.printf("Reading %s format (%s)%n", reader.getFormatName(), reader); System.out.printf("Reading %s format (%s)%n", reader.getFormatName(), reader);
reader.addIIOReadWarningListener(new IIOReadWarningListener() { reader.addIIOReadWarningListener(new IIOReadWarningListener() {
public void warningOccurred(ImageReader source, String warning) { public void warningOccurred(ImageReader source, String warning) {

View File

@ -171,11 +171,9 @@ final class XWDImageReader extends ImageReaderBase {
} }
} }
if (xSub != 1) {
// Horizontal subsampling // Horizontal subsampling
int samplesPerPixel = header.numComponents(); int samplesPerPixel = header.numComponents();
subsampleRow(row, srcRegion.x * samplesPerPixel, srcRegion.width, row, srcRegion.x * samplesPerPixel, samplesPerPixel, header.bitsPerRGB, xSub); subsampleRow(row, srcRegion.x * samplesPerPixel, srcRegion.width, row, srcRegion.x * samplesPerPixel, samplesPerPixel, header.bitsPerRGB, xSub);
}
raster.setDataElements(0, (y - srcRegion.y) / ySub, rowRaster); raster.setDataElements(0, (y - srcRegion.y) / ySub, rowRaster);