mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-03 11:35:29 -04:00
TMI-TIFF: Now supports YCbCr subsampled images with image/tile/strip width/height not a multiple of the x/y subsampling. More lenience for weird subsampling.
+ Some minor house-keeping with no functional change.
This commit is contained in:
parent
c9809d0fa1
commit
2764460db5
@ -110,7 +110,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
// TODO: Implement readAsRenderedImage to allow tiled renderImage?
|
// TODO: Implement readAsRenderedImage to allow tiled renderImage?
|
||||||
// For some layouts, we could do reads super-fast with a memory mapped buffer.
|
// For some layouts, we could do reads super-fast with a memory mapped buffer.
|
||||||
// TODO: Implement readAsRaster directly
|
// TODO: Implement readAsRaster directly
|
||||||
// TODO: IIOMetadata
|
// TODO: IIOMetadata (stay close to Sun's TIFF metadata)
|
||||||
|
|
||||||
// TODOs Full BaseLine support:
|
// TODOs Full BaseLine support:
|
||||||
// TODO: Support ExtraSamples (an array, if multiple extra samples!)
|
// TODO: Support ExtraSamples (an array, if multiple extra samples!)
|
||||||
@ -121,17 +121,14 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
// TODO: Support PlanarConfiguration 2
|
// TODO: Support PlanarConfiguration 2
|
||||||
// TODO: Support ICCProfile (fully)
|
// TODO: Support ICCProfile (fully)
|
||||||
// TODO: Support Compression 3 & 4 (CCITT T.4 & T.6)
|
// TODO: Support Compression 3 & 4 (CCITT T.4 & T.6)
|
||||||
// TODO: Support Compression 6 ('Old-style' JPEG)
|
|
||||||
// TODO: Support Compression 34712 (JPEG2000)? Depends on JPEG2000 ImageReader
|
// TODO: Support Compression 34712 (JPEG2000)? Depends on JPEG2000 ImageReader
|
||||||
// TODO: Support Compression 34661 (JBIG)? Depends on JBIG ImageReader
|
// TODO: Support Compression 34661 (JBIG)? Depends on JBIG ImageReader
|
||||||
|
|
||||||
// DONE:
|
// DONE:
|
||||||
// Handle SampleFormat (and give up if not == 1)
|
// Handle SampleFormat (and give up if not == 1)
|
||||||
|
// Support Compression 6 ('Old-style' JPEG)
|
||||||
|
|
||||||
private final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.tiff.debug"));
|
final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.tiff.debug"));
|
||||||
|
|
||||||
// NOTE: DO NOT MODIFY OR EXPOSE!
|
|
||||||
static final double[] CCIR_601_1_COEFFICIENTS = new double[] {299.0 / 1000.0, 587.0 / 1000.0, 114.0 / 1000.0};
|
|
||||||
|
|
||||||
private CompoundDirectory IFDs;
|
private CompoundDirectory IFDs;
|
||||||
private Directory currentIFD;
|
private Directory currentIFD;
|
||||||
@ -155,12 +152,12 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
IFDs = (CompoundDirectory) new EXIFReader().read(imageInput); // NOTE: Sets byte order as a side effect
|
IFDs = (CompoundDirectory) new EXIFReader().read(imageInput); // NOTE: Sets byte order as a side effect
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
for (int i = 0; i < IFDs.directoryCount(); i++) {
|
|
||||||
System.err.printf("ifd[%d]: %s\n", i, IFDs.getDirectory(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
System.err.println("Byte order: " + imageInput.getByteOrder());
|
System.err.println("Byte order: " + imageInput.getByteOrder());
|
||||||
System.err.println("numImages: " + IFDs.directoryCount());
|
System.err.println("Number of images: " + IFDs.directoryCount());
|
||||||
|
|
||||||
|
for (int i = 0; i < IFDs.directoryCount(); i++) {
|
||||||
|
System.err.printf("IFD %d: %s\n", i, IFDs.getDirectory(i));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -257,9 +254,8 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case TIFFExtension.PHOTOMETRIC_YCBCR:
|
case TIFFExtension.PHOTOMETRIC_YCBCR:
|
||||||
// JPEG reader will handle YCbCr to RGB for us, we'll have to do it ourselves if not JPEG...
|
// JPEG reader will handle YCbCr to RGB for us, otherwise we'll convert while reading
|
||||||
// TODO: Sanity check that we have SamplesPerPixel == 3, BitsPerSample == [8,8,8] and Compression == 1 (none), 5 (LZW), or 6 (JPEG)
|
// TODO: Sanity check that we have SamplesPerPixel == 3, BitsPerSample == [8,8,8] and Compression == 1 (none), 5 (LZW), or 6 (JPEG)
|
||||||
// TODO: Handle YCbCrSubsampling (up-scaler stream, or read data as-is + up-sample (sub-)raster after read? Apply smoothing?)
|
|
||||||
case TIFFBaseline.PHOTOMETRIC_RGB:
|
case TIFFBaseline.PHOTOMETRIC_RGB:
|
||||||
// RGB
|
// RGB
|
||||||
cs = profile == null ? ColorSpace.getInstance(ColorSpace.CS_sRGB) : ColorSpaces.createColorSpace(profile);
|
cs = profile == null ? ColorSpace.getInstance(ColorSpace.CS_sRGB) : ColorSpaces.createColorSpace(profile);
|
||||||
@ -518,10 +514,10 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR) {
|
if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR) {
|
||||||
// getRawImageType does the lookup/conversion for these
|
// getRawImageType does the lookup/conversion for these
|
||||||
if (raster.getNumBands() != 3) {
|
if (raster.getNumBands() != 3) {
|
||||||
throw new IIOException("TIFF PhotometricInterpreatation YCbCr requires SamplesPerPixel == 3: " + raster.getNumBands());
|
throw new IIOException("TIFF PhotometricInterpretation YCbCr requires SamplesPerPixel == 3: " + raster.getNumBands());
|
||||||
}
|
}
|
||||||
if (raster.getTransferType() != DataBuffer.TYPE_BYTE) {
|
if (raster.getTransferType() != DataBuffer.TYPE_BYTE) {
|
||||||
throw new IIOException("TIFF PhotometricInterpreatation YCbCr requires BitsPerSample == [8,8,8]");
|
throw new IIOException("TIFF PhotometricInterpretation YCbCr requires BitsPerSample == [8,8,8]");
|
||||||
}
|
}
|
||||||
|
|
||||||
yCbCrPos = getValueAsIntWithDefault(TIFF.TAG_YCBCR_POSITIONING, TIFFExtension.YCBCR_POSITIONING_CENTERED);
|
yCbCrPos = getValueAsIntWithDefault(TIFF.TAG_YCBCR_POSITIONING, TIFFExtension.YCBCR_POSITIONING_CENTERED);
|
||||||
@ -541,10 +537,13 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
if (yCbCrSubsampling.length != 2 ||
|
if (yCbCrSubsampling.length != 2 ||
|
||||||
yCbCrSubsampling[0] != 1 && yCbCrSubsampling[0] != 2 && yCbCrSubsampling[0] != 4 ||
|
yCbCrSubsampling[0] != 1 && yCbCrSubsampling[0] != 2 && yCbCrSubsampling[0] != 4 ||
|
||||||
yCbCrSubsampling[1] != 1 && yCbCrSubsampling[1] != 2 && yCbCrSubsampling[1] != 4 ||
|
yCbCrSubsampling[1] != 1 && yCbCrSubsampling[1] != 2 && yCbCrSubsampling[1] != 4) {
|
||||||
yCbCrSubsampling[0] < yCbCrSubsampling[1]) {
|
|
||||||
throw new IIOException("Bad TIFF YCbCrSubSampling value: " + Arrays.toString(yCbCrSubsampling));
|
throw new IIOException("Bad TIFF YCbCrSubSampling value: " + Arrays.toString(yCbCrSubsampling));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (yCbCrSubsampling[0] < yCbCrSubsampling[1]) {
|
||||||
|
processWarningOccurred("TIFF PhotometricInterpretation YCbCr with bad subsampling, expected subHoriz >= subVert: " + Arrays.toString(yCbCrSubsampling));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
yCbCrSubsampling = new int[] {2, 2};
|
yCbCrSubsampling = new int[] {2, 2};
|
||||||
@ -557,7 +556,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Default to y CCIR Recommendation 601-1 values
|
// Default to y CCIR Recommendation 601-1 values
|
||||||
yCbCrCoefficients = CCIR_601_1_COEFFICIENTS;
|
yCbCrCoefficients = YCbCrUpsamplerStream.CCIR_601_1_COEFFICIENTS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -640,8 +639,8 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
// Might have something to do with subsampling?
|
// Might have something to do with subsampling?
|
||||||
// How do we pass the chroma-subsampling parameter from the TIFF structure to the JPEG reader?
|
// How do we pass the chroma-subsampling parameter from the TIFF structure to the JPEG reader?
|
||||||
|
|
||||||
// TODO: Consider splicing the TAG_JPEG_TABLES into the streams for each tile, for a more
|
// TODO: Consider splicing the TAG_JPEG_TABLES into the streams for each tile, for a
|
||||||
// compatible approach..?
|
// (slightly slower for multiple images, but) more compatible approach..?
|
||||||
|
|
||||||
jpegReader.setInput(new ByteArrayImageInputStream(tablesValue));
|
jpegReader.setInput(new ByteArrayImageInputStream(tablesValue));
|
||||||
|
|
||||||
@ -745,61 +744,45 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
case TIFFExtension.COMPRESSION_OLD_JPEG:
|
case TIFFExtension.COMPRESSION_OLD_JPEG:
|
||||||
// JPEG ('old-style' JPEG, later overridden in Technote2)
|
// JPEG ('old-style' JPEG, later overridden in Technote2)
|
||||||
|
|
||||||
// http://www.remotesensing.org/libtiff/TIFFTechNote2.html
|
// http://www.remotesensing.org/libtiff/TIFFTechNote2.html
|
||||||
// TODO: Issue warning?
|
|
||||||
|
|
||||||
int mode = getValueAsIntWithDefault(TIFF.TAG_OLD_JPEG_PROC, 1);
|
// 512/JPEGProc: 1=Baseline, 14=Lossless (with Huffman coding), no default, although 1 is assumed if absent
|
||||||
if (mode == TIFFExtension.JPEG_PROC_LOSSLESS) {
|
int mode = getValueAsIntWithDefault(TIFF.TAG_OLD_JPEG_PROC, TIFFExtension.JPEG_PROC_BASELINE);
|
||||||
throw new IIOException("Unsupported TIFF JPEGProcessingMode: Lossless (14)");
|
switch (mode) {
|
||||||
}
|
case TIFFExtension.JPEG_PROC_BASELINE:
|
||||||
else if (mode != TIFFExtension.JPEG_PROC_BASELINE) {
|
break; // Supported
|
||||||
throw new IIOException("Unknown TIFF JPEGProcessingMode value: " + mode);
|
case TIFFExtension.JPEG_PROC_LOSSLESS:
|
||||||
|
throw new IIOException("Unsupported TIFF JPEGProcessingMode: Lossless (14)");
|
||||||
|
default:
|
||||||
|
throw new IIOException("Unknown TIFF JPEGProcessingMode value: " + mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// May use normal tiling??
|
// May use normal tiling??
|
||||||
|
|
||||||
// 512/JPEGProc: 1=Baseline, 14=Lossless (with Huffman coding), no default, although 1 is assumed if absent
|
|
||||||
// 513/JPEGInterchangeFormat (may be absent...)
|
|
||||||
// 514/JPEGInterchangeFormatLength (may be absent...)
|
|
||||||
// 515/JPEGRestartInterval (may be absent)
|
|
||||||
|
|
||||||
// 517/JPEGLosslessPredictors
|
|
||||||
// 518/JPEGPointTransforms
|
|
||||||
|
|
||||||
// 519/JPEGQTables
|
|
||||||
// 520/JPEGDCTables
|
|
||||||
// 521/JPEGACTables
|
|
||||||
|
|
||||||
// This field was originally intended to point to a list of offsets to the quantization tables, one per
|
|
||||||
// component. Each table consists of 64 BYTES (one for each DCT coefficient in the 8x8 block). The
|
|
||||||
// quantization tables are stored in zigzag order, and are compatible with the quantization tables
|
|
||||||
// usually found in a JPEG stream DQT marker.
|
|
||||||
|
|
||||||
// The original specification strongly recommended that, within the TIFF file, each component be
|
|
||||||
// assigned separate tables, and labelled this field as mandatory whenever the JPEGProc field specifies
|
|
||||||
// a DCT-based process.
|
|
||||||
|
|
||||||
// We've seen old-style JPEG in TIFF files where some or all Table offsets, contained the JPEGQTables,
|
|
||||||
// JPEGDCTables, and JPEGACTables tags are incorrect values beyond EOF. However, these files do always
|
|
||||||
// seem to contain a useful JPEGInterchangeFormat tag. Therefore, we recommend a careful attempt to read
|
|
||||||
// the Tables tags only as a last resort, if no table data is found in a JPEGInterchangeFormat stream.
|
|
||||||
|
|
||||||
|
|
||||||
// TIFF is strictly ISO JPEG, so we should probably stick to the standard reader
|
// TIFF is strictly ISO JPEG, so we should probably stick to the standard reader
|
||||||
jpegReader = new JPEGImageReader(getOriginatingProvider());
|
jpegReader = new JPEGImageReader(getOriginatingProvider());
|
||||||
jpegParam = (JPEGImageReadParam) jpegReader.getDefaultReadParam();
|
jpegParam = (JPEGImageReadParam) jpegReader.getDefaultReadParam();
|
||||||
|
|
||||||
|
// 513/JPEGInterchangeFormat (may be absent...)
|
||||||
int jpegOffset = getValueAsIntWithDefault(TIFF.TAG_JPEG_INTERCHANGE_FORMAT, -1);
|
int jpegOffset = getValueAsIntWithDefault(TIFF.TAG_JPEG_INTERCHANGE_FORMAT, -1);
|
||||||
|
// 514/JPEGInterchangeFormatLength (may be absent...)
|
||||||
int jpegLenght = getValueAsIntWithDefault(TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, -1);
|
int jpegLenght = getValueAsIntWithDefault(TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, -1);
|
||||||
|
// TODO: 515/JPEGRestartInterval (may be absent)
|
||||||
|
|
||||||
|
// Currently ignored
|
||||||
|
// 517/JPEGLosslessPredictors
|
||||||
|
// 518/JPEGPointTransforms
|
||||||
|
|
||||||
ImageInputStream stream;
|
ImageInputStream stream;
|
||||||
|
|
||||||
if (jpegOffset != -1) {
|
if (jpegOffset != -1) {
|
||||||
// Straight forward case: We're good to go! We'll disregard tiling and any tables tags
|
// Straight forward case: We're good to go! We'll disregard tiling and any tables tags
|
||||||
|
|
||||||
if (currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_QTABLES) != null || currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_DCTABLES) != null || currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_ACTABLES) != null) {
|
if (currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_Q_TABLES) != null || currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_DC_TABLES) != null || currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_AC_TABLES) != null) {
|
||||||
processWarningOccurred("Old-style JPEG compressed TIFF with JFIF stream encountered. Reading as single tile, ignoring tables.");
|
processWarningOccurred("Old-style JPEG compressed TIFF with JFIF stream encountered. Ignoring JPEG tables. Reading as single tile.");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
processWarningOccurred("Old-style JPEG compressed TIFF with JFIF stream encountered. Reading as single tile.");
|
||||||
}
|
}
|
||||||
|
|
||||||
imageInput.seek(jpegOffset);
|
imageInput.seek(jpegOffset);
|
||||||
@ -831,25 +814,50 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
processWarningOccurred("Old-style JPEG compressed TIFF without JFIF stream encountered. Attempting to re-create JFIF stream.");
|
processWarningOccurred("Old-style JPEG compressed TIFF without JFIF stream encountered. Attempting to re-create JFIF stream.");
|
||||||
|
|
||||||
|
// 519/JPEGQTables
|
||||||
|
// 520/JPEGDCTables
|
||||||
|
// 521/JPEGACTables
|
||||||
|
|
||||||
|
// These fields were originally intended to point to a list of offsets to the quantization tables, one per
|
||||||
|
// component. Each table consists of 64 BYTES (one for each DCT coefficient in the 8x8 block). The
|
||||||
|
// quantization tables are stored in zigzag order, and are compatible with the quantization tables
|
||||||
|
// usually found in a JPEG stream DQT marker.
|
||||||
|
|
||||||
|
// The original specification strongly recommended that, within the TIFF file, each component be
|
||||||
|
// assigned separate tables, and labelled this field as mandatory whenever the JPEGProc field specifies
|
||||||
|
// a DCT-based process.
|
||||||
|
|
||||||
|
// We've seen old-style JPEG in TIFF files where some or all Table offsets, contained the JPEGQTables,
|
||||||
|
// JPEGDCTables, and JPEGACTables tags are incorrect values beyond EOF. However, these files do always
|
||||||
|
// seem to contain a useful JPEGInterchangeFormat tag. Therefore, we recommend a careful attempt to read
|
||||||
|
// the Tables tags only as a last resort, if no table data is found in a JPEGInterchangeFormat stream.
|
||||||
|
|
||||||
|
|
||||||
// TODO: If any of the q/dc/ac tables are equal (or have same offset, even if "spec" violation),
|
// TODO: If any of the q/dc/ac tables are equal (or have same offset, even if "spec" violation),
|
||||||
// use only the first occurrence, and update selectors in SOF0 and SOS
|
// use only the first occurrence, and update selectors in SOF0 and SOS
|
||||||
|
|
||||||
long[] qTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_QTABLES, "JPEGQTables", true);
|
long[] qTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_Q_TABLES, "JPEGQTables", true);
|
||||||
byte[][] qTables = new byte[qTablesOffsets.length][(int) (qTablesOffsets[1] - qTablesOffsets[0])]; // TODO: Using the offsets seems fragile.. Use fixed length??
|
byte[][] qTables = new byte[qTablesOffsets.length][(int) (qTablesOffsets[1] - qTablesOffsets[0])]; // TODO: Using the offsets is fragile.. Use fixed length??
|
||||||
|
// byte[][] qTables = new byte[qTablesOffsets.length][64];
|
||||||
|
// System.err.println("qTables: " + qTables[0].length);
|
||||||
for (int j = 0; j < qTables.length; j++) {
|
for (int j = 0; j < qTables.length; j++) {
|
||||||
imageInput.seek(qTablesOffsets[j]);
|
imageInput.seek(qTablesOffsets[j]);
|
||||||
imageInput.readFully(qTables[j]);
|
imageInput.readFully(qTables[j]);
|
||||||
}
|
}
|
||||||
|
|
||||||
long[] dcTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_DCTABLES, "JPEGDCTables", true);
|
long[] dcTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_DC_TABLES, "JPEGDCTables", true);
|
||||||
byte[][] dcTables = new byte[dcTablesOffsets.length][(int) (dcTablesOffsets[1] - dcTablesOffsets[0])];
|
byte[][] dcTables = new byte[dcTablesOffsets.length][(int) (dcTablesOffsets[1] - dcTablesOffsets[0])]; // TODO: Using the offsets is fragile.. Use fixed length??
|
||||||
|
// byte[][] dcTables = new byte[dcTablesOffsets.length][28];
|
||||||
|
// System.err.println("dcTables: " + dcTables[0].length);
|
||||||
for (int j = 0; j < dcTables.length; j++) {
|
for (int j = 0; j < dcTables.length; j++) {
|
||||||
imageInput.seek(dcTablesOffsets[j]);
|
imageInput.seek(dcTablesOffsets[j]);
|
||||||
imageInput.readFully(dcTables[j]);
|
imageInput.readFully(dcTables[j]);
|
||||||
}
|
}
|
||||||
|
|
||||||
long[] acTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_ACTABLES, "JPEGACTables", true);
|
long[] acTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_AC_TABLES, "JPEGACTables", true);
|
||||||
byte[][] acTables = new byte[acTablesOffsets.length][(int) (acTablesOffsets[1] - acTablesOffsets[0])];
|
byte[][] acTables = new byte[acTablesOffsets.length][(int) (acTablesOffsets[1] - acTablesOffsets[0])]; // TODO: Using the offsets is fragile.. Use fixed length??
|
||||||
|
// byte[][] acTables = new byte[acTablesOffsets.length][178];
|
||||||
|
// System.err.println("acTables: " + acTables[0].length);
|
||||||
for (int j = 0; j < acTables.length; j++) {
|
for (int j = 0; j < acTables.length; j++) {
|
||||||
imageInput.seek(acTablesOffsets[j]);
|
imageInput.seek(acTablesOffsets[j]);
|
||||||
imageInput.readFully(acTables[j]);
|
imageInput.readFully(acTables[j]);
|
||||||
|
@ -44,15 +44,18 @@ import java.util.Arrays;
|
|||||||
* @version $Id: YCbCrUpsamplerStream.java,v 1.0 31.01.13 09:25 haraldk Exp$
|
* @version $Id: YCbCrUpsamplerStream.java,v 1.0 31.01.13 09:25 haraldk Exp$
|
||||||
*/
|
*/
|
||||||
final class YCbCrUpsamplerStream extends FilterInputStream {
|
final class YCbCrUpsamplerStream extends FilterInputStream {
|
||||||
static final boolean DEBUG = false;
|
// NOTE: DO NOT MODIFY OR EXPOSE!
|
||||||
|
static final double[] CCIR_601_1_COEFFICIENTS = new double[] {299.0 / 1000.0, 587.0 / 1000.0, 114.0 / 1000.0};
|
||||||
|
|
||||||
private final int horizChromaSub;
|
private final int horizChromaSub;
|
||||||
private final int vertChromaSub;
|
private final int vertChromaSub;
|
||||||
private final int yCbCrPos;
|
private final int yCbCrPos;
|
||||||
|
private final int columns;
|
||||||
private final double[] coefficients;
|
private final double[] coefficients;
|
||||||
|
|
||||||
private final int units;
|
private final int units;
|
||||||
private final int unitSize;
|
private final int unitSize;
|
||||||
|
private final int padding;
|
||||||
private final byte[] decodedRows;
|
private final byte[] decodedRows;
|
||||||
int decodedLength;
|
int decodedLength;
|
||||||
int decodedPos;
|
int decodedPos;
|
||||||
@ -61,13 +64,14 @@ final class YCbCrUpsamplerStream extends FilterInputStream {
|
|||||||
int bufferLength;
|
int bufferLength;
|
||||||
int bufferPos;
|
int bufferPos;
|
||||||
|
|
||||||
public YCbCrUpsamplerStream(InputStream stream, int[] chromaSub, int yCbCrPos, int cols, double[] coefficients) {
|
public YCbCrUpsamplerStream(InputStream stream, int[] chromaSub, int yCbCrPos, int columns, double[] coefficients) {
|
||||||
super(stream);
|
super(stream);
|
||||||
|
|
||||||
this.horizChromaSub = chromaSub[0];
|
this.horizChromaSub = chromaSub[0];
|
||||||
this.vertChromaSub = chromaSub[1];
|
this.vertChromaSub = chromaSub[1];
|
||||||
this.yCbCrPos = yCbCrPos;
|
this.yCbCrPos = yCbCrPos;
|
||||||
this.coefficients = Arrays.equals(TIFFImageReader.CCIR_601_1_COEFFICIENTS, coefficients) ? null : coefficients;
|
this.columns = columns;
|
||||||
|
this.coefficients = Arrays.equals(CCIR_601_1_COEFFICIENTS, coefficients) ? null : coefficients;
|
||||||
|
|
||||||
// In TIFF, subsampled streams are stored in "units" of horiz * vert pixels.
|
// In TIFF, subsampled streams are stored in "units" of horiz * vert pixels.
|
||||||
// For a 4:2 subsampled stream like this:
|
// For a 4:2 subsampled stream like this:
|
||||||
@ -77,29 +81,13 @@ final class YCbCrUpsamplerStream extends FilterInputStream {
|
|||||||
//
|
//
|
||||||
// In the stream, the order is: Y0,Y1,Y2..Y7,Cb0,Cr0, Y8...Y15,Cb1,Cr1, Y16...
|
// In the stream, the order is: Y0,Y1,Y2..Y7,Cb0,Cr0, Y8...Y15,Cb1,Cr1, Y16...
|
||||||
|
|
||||||
units = cols / horizChromaSub;
|
|
||||||
unitSize = horizChromaSub * vertChromaSub + 2;
|
unitSize = horizChromaSub * vertChromaSub + 2;
|
||||||
decodedRows = new byte[cols * vertChromaSub * 3];
|
units = (columns + horizChromaSub - 1) / horizChromaSub; // If columns % horizChromasSub != 0...
|
||||||
|
padding = units * horizChromaSub - columns; // ...each coded row will be padded to fill unit
|
||||||
|
decodedRows = new byte[columns * vertChromaSub * 3];
|
||||||
buffer = new byte[unitSize * units];
|
buffer = new byte[unitSize * units];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read() throws IOException {
|
|
||||||
if (decodedLength < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (decodedPos >= decodedLength) {
|
|
||||||
fetch();
|
|
||||||
|
|
||||||
if (decodedLength < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return decodedRows[decodedPos++];
|
|
||||||
}
|
|
||||||
|
|
||||||
private void fetch() throws IOException {
|
private void fetch() throws IOException {
|
||||||
if (bufferPos >= bufferLength) {
|
if (bufferPos >= bufferLength) {
|
||||||
int pos = 0;
|
int pos = 0;
|
||||||
@ -125,8 +113,6 @@ final class YCbCrUpsamplerStream extends FilterInputStream {
|
|||||||
private void decodeRows() throws EOFException {
|
private void decodeRows() throws EOFException {
|
||||||
decodedLength = decodedRows.length;
|
decodedLength = decodedRows.length;
|
||||||
|
|
||||||
int rowOff = horizChromaSub * units;
|
|
||||||
|
|
||||||
for (int u = 0; u < units; u++) {
|
for (int u = 0; u < units; u++) {
|
||||||
if (bufferPos >= bufferLength) {
|
if (bufferPos >= bufferLength) {
|
||||||
throw new EOFException("Unexpected end of stream");
|
throw new EOFException("Unexpected end of stream");
|
||||||
@ -138,7 +124,14 @@ final class YCbCrUpsamplerStream extends FilterInputStream {
|
|||||||
|
|
||||||
for (int y = 0; y < vertChromaSub; y++) {
|
for (int y = 0; y < vertChromaSub; y++) {
|
||||||
for (int x = 0; x < horizChromaSub; x++) {
|
for (int x = 0; x < horizChromaSub; x++) {
|
||||||
int pixelOff = 3 * (rowOff * y + horizChromaSub * u + x);
|
// Skip padding at end of row
|
||||||
|
int column = horizChromaSub * u + x;
|
||||||
|
if (column >= columns) {
|
||||||
|
bufferPos += padding;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pixelOff = 3 * (column + columns * y);
|
||||||
|
|
||||||
decodedRows[pixelOff] = buffer[bufferPos++];
|
decodedRows[pixelOff] = buffer[bufferPos++];
|
||||||
decodedRows[pixelOff + 1] = cb;
|
decodedRows[pixelOff + 1] = cb;
|
||||||
@ -154,7 +147,7 @@ final class YCbCrUpsamplerStream extends FilterInputStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bufferPos+= 2;
|
bufferPos += 2; // CbCr bytes at end of unit
|
||||||
}
|
}
|
||||||
|
|
||||||
bufferPos = bufferLength;
|
bufferPos = bufferLength;
|
||||||
@ -162,8 +155,20 @@ final class YCbCrUpsamplerStream extends FilterInputStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final int read(byte[] b) throws IOException {
|
public int read() throws IOException {
|
||||||
return read(b, 0, b.length);
|
if (decodedLength < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decodedPos >= decodedLength) {
|
||||||
|
fetch();
|
||||||
|
|
||||||
|
if (decodedLength < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return decodedRows[decodedPos++];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -180,6 +185,7 @@ final class YCbCrUpsamplerStream extends FilterInputStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Read no longer than until row boundary....
|
||||||
int read = Math.min(decodedLength - decodedPos, len);
|
int read = Math.min(decodedLength - decodedPos, len);
|
||||||
System.arraycopy(decodedRows, decodedPos, b, off, read);
|
System.arraycopy(decodedRows, decodedPos, b, off, read);
|
||||||
decodedPos += read;
|
decodedPos += read;
|
||||||
@ -259,7 +265,7 @@ final class YCbCrUpsamplerStream extends FilterInputStream {
|
|||||||
* Initializes tables for YCC->RGB color space conversion.
|
* Initializes tables for YCC->RGB color space conversion.
|
||||||
*/
|
*/
|
||||||
private static void buildYCCtoRGBtable() {
|
private static void buildYCCtoRGBtable() {
|
||||||
if (DEBUG) {
|
if (TIFFImageReader.DEBUG) {
|
||||||
System.err.println("Building YCC conversion table");
|
System.err.println("Building YCC conversion table");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user