mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-03 11:35:29 -04:00
TMI-TIFF: Added support for premultiplied alpha (ExtraSamples == 1) + code clean-up.
This commit is contained in:
parent
46b53a824c
commit
3b15653a10
@ -280,23 +280,25 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 4:
|
case 4:
|
||||||
// TODO: Consult ExtraSamples!
|
|
||||||
if (bitsPerSample == 8 || bitsPerSample == 16) {
|
if (bitsPerSample == 8 || bitsPerSample == 16) {
|
||||||
|
// ExtraSamples 0=unspecified, 1=associated (premultiplied), 2=unassociated (TODO: Support unspecified, not alpha)
|
||||||
|
long[] extraSamples = getValueAsLongArray(TIFF.TAG_EXTRA_SAMPLES, "ExtraSamples", true);
|
||||||
|
|
||||||
switch (planarConfiguration) {
|
switch (planarConfiguration) {
|
||||||
case TIFFBaseline.PLANARCONFIG_CHUNKY:
|
case TIFFBaseline.PLANARCONFIG_CHUNKY:
|
||||||
if (bitsPerSample == 8 && cs.isCS_sRGB()) {
|
if (bitsPerSample == 8 && cs.isCS_sRGB()) {
|
||||||
return ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR);
|
return ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ImageTypeSpecifier.createInterleaved(cs, new int[] {0, 1, 2, 3}, dataType, true, false);
|
return ImageTypeSpecifier.createInterleaved(cs, new int[] {0, 1, 2, 3}, dataType, true, extraSamples[0] == 1);
|
||||||
|
|
||||||
case TIFFExtension.PLANARCONFIG_PLANAR:
|
case TIFFExtension.PLANARCONFIG_PLANAR:
|
||||||
return ImageTypeSpecifier.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, dataType, true, false);
|
return ImageTypeSpecifier.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, dataType, true, extraSamples[0] == 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: More samples might be ok, if multiple alpha or unknown samples
|
// TODO: More samples might be ok, if multiple alpha or unknown samples
|
||||||
default:
|
default:
|
||||||
throw new IIOException(String.format("Unsupported SamplesPerPixels/BitsPerSample combination for RGB TIF (expected 3/8, 4/8, 3/16 or 4/16): %d/%d", samplesPerPixel, bitsPerSample));
|
throw new IIOException(String.format("Unsupported SamplesPerPixels/BitsPerSample combination for RGB TIFF (expected 3/8, 4/8, 3/16 or 4/16): %d/%d", samplesPerPixel, bitsPerSample));
|
||||||
}
|
}
|
||||||
case TIFFBaseline.PHOTOMETRIC_PALETTE:
|
case TIFFBaseline.PHOTOMETRIC_PALETTE:
|
||||||
// Palette
|
// Palette
|
||||||
@ -345,11 +347,14 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
case 5:
|
case 5:
|
||||||
if (bitsPerSample == 8 || bitsPerSample == 16) {
|
if (bitsPerSample == 8 || bitsPerSample == 16) {
|
||||||
|
// ExtraSamples 0=unspecified, 1=associated (premultiplied), 2=unassociated (TODO: Support unspecified, not alpha)
|
||||||
|
long[] extraSamples = getValueAsLongArray(TIFF.TAG_EXTRA_SAMPLES, "ExtraSamples", true);
|
||||||
|
|
||||||
switch (planarConfiguration) {
|
switch (planarConfiguration) {
|
||||||
case TIFFBaseline.PLANARCONFIG_CHUNKY:
|
case TIFFBaseline.PLANARCONFIG_CHUNKY:
|
||||||
return ImageTypeSpecifier.createInterleaved(cs, new int[] {0, 1, 2, 3, 4}, dataType, true, false);
|
return ImageTypeSpecifier.createInterleaved(cs, new int[] {0, 1, 2, 3, 4}, dataType, true, extraSamples[0] == 1);
|
||||||
case TIFFExtension.PLANARCONFIG_PLANAR:
|
case TIFFExtension.PLANARCONFIG_PLANAR:
|
||||||
return ImageTypeSpecifier.createBanded(cs, new int[] {0, 1, 2, 3, 4}, new int[] {0, 0, 0, 0, 0}, dataType, true, false);
|
return ImageTypeSpecifier.createBanded(cs, new int[] {0, 1, 2, 3, 4}, new int[] {0, 0, 0, 0, 0}, dataType, true, extraSamples[0] == 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -833,15 +838,12 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
imageInput.readFully(qTables[j]);
|
imageInput.readFully(qTables[j]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// System.err.println("qTables: " + qTables[0].length);
|
|
||||||
|
|
||||||
long[] dcTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_DCTABLES, "JPEGDCTables", true);
|
long[] dcTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_DCTABLES, "JPEGDCTables", true);
|
||||||
byte[][] dcTables = new byte[3][(int) (dcTablesOffsets[1] - dcTablesOffsets[0])];
|
byte[][] dcTables = new byte[3][(int) (dcTablesOffsets[1] - dcTablesOffsets[0])];
|
||||||
for (int j = 0; j < 3; j++) {
|
for (int j = 0; j < 3; j++) {
|
||||||
imageInput.seek(dcTablesOffsets[j]);
|
imageInput.seek(dcTablesOffsets[j]);
|
||||||
imageInput.readFully(dcTables[j]);
|
imageInput.readFully(dcTables[j]);
|
||||||
}
|
}
|
||||||
// System.err.println("dcTables: " + dcTables[0].length);
|
|
||||||
|
|
||||||
long[] acTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_ACTABLES, "JPEGACTables", true);
|
long[] acTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_ACTABLES, "JPEGACTables", true);
|
||||||
byte[][] acTables = new byte[3][(int) (acTablesOffsets[1] - acTablesOffsets[0])];
|
byte[][] acTables = new byte[3][(int) (acTablesOffsets[1] - acTablesOffsets[0])];
|
||||||
@ -849,7 +851,6 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
imageInput.seek(acTablesOffsets[j]);
|
imageInput.seek(acTablesOffsets[j]);
|
||||||
imageInput.readFully(acTables[j]);
|
imageInput.readFully(acTables[j]);
|
||||||
}
|
}
|
||||||
// System.err.println("acTables: " + acTables[0].length);
|
|
||||||
|
|
||||||
// Read data
|
// Read data
|
||||||
processImageStarted(imageIndex);
|
processImageStarted(imageIndex);
|
||||||
@ -863,73 +864,9 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
int i = y * tilesAcross + x;
|
int i = y * tilesAcross + x;
|
||||||
|
|
||||||
imageInput.seek(stripTileOffsets[i]);
|
imageInput.seek(stripTileOffsets[i]);
|
||||||
FastByteArrayOutputStream jfifBytes = new FastByteArrayOutputStream(
|
|
||||||
2 + 2 + 2 + 6 + 3 * raster.getNumBands() +
|
|
||||||
5 * qTables.length + qTables.length * qTables[0].length +
|
|
||||||
5 * dcTables.length + dcTables.length * dcTables[0].length +
|
|
||||||
5 * acTables.length + acTables.length * acTables[0].length +
|
|
||||||
8 + 2 * raster.getNumBands()
|
|
||||||
);
|
|
||||||
DataOutputStream out = new DataOutputStream(jfifBytes);
|
|
||||||
|
|
||||||
out.writeShort(JPEG.SOI);
|
|
||||||
out.writeShort(JPEG.SOF0);
|
|
||||||
out.writeShort(2 + 6 + 3 * raster.getNumBands()); // SOF0 len
|
|
||||||
out.writeByte(8); // bits TODO: Consult raster/transfer type for 12/16 bits support
|
|
||||||
out.writeShort(stripTileHeight); // height
|
|
||||||
out.writeShort(stripTileWidth); // width
|
|
||||||
out.writeByte(raster.getNumBands()); // Number of components
|
|
||||||
|
|
||||||
for (int comp = 0; comp < raster.getNumBands(); comp++) {
|
|
||||||
out.writeByte(comp); // Component id
|
|
||||||
out.writeByte(comp == 0 ? 0x22 : 0x11); // h/v subsampling TODO: FixMe, consult YCbCrSubsampling
|
|
||||||
out.writeByte(comp); // Q table selector TODO: Consider merging if tables are equal
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Consider merging if tables are equal
|
|
||||||
for (int tableIndex = 0; tableIndex < qTables.length; tableIndex++) {
|
|
||||||
byte[] table = qTables[tableIndex];
|
|
||||||
out.writeShort(JPEG.DQT);
|
|
||||||
out.writeShort(3 + table.length); // DQT length
|
|
||||||
out.writeByte(tableIndex); // Q table id
|
|
||||||
out.write(table); // Table data
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Consider merging if tables are equal
|
|
||||||
for (int tableIndex = 0; tableIndex < dcTables.length; tableIndex++) {
|
|
||||||
byte[] table = dcTables[tableIndex];
|
|
||||||
out.writeShort(JPEG.DHT);
|
|
||||||
out.writeShort(3 + table.length); // DHT length
|
|
||||||
out.writeByte(tableIndex); // Huffman table id
|
|
||||||
out.write(table); // Table data
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Consider merging if tables are equal
|
|
||||||
for (int tableIndex = 0; tableIndex < acTables.length; tableIndex++) {
|
|
||||||
byte[] table = acTables[tableIndex];
|
|
||||||
out.writeShort(JPEG.DHT);
|
|
||||||
out.writeShort(3 + table.length); // DHT length
|
|
||||||
out.writeByte(0x10 + (tableIndex & 0xf)); // Huffman table id
|
|
||||||
out.write(table); // Table data
|
|
||||||
}
|
|
||||||
|
|
||||||
out.writeShort(JPEG.SOS);
|
|
||||||
out.writeShort(6 + 2 * raster.getNumBands()); // SOS length
|
|
||||||
out.writeByte(raster.getNumBands()); // Num comp
|
|
||||||
|
|
||||||
for (int component = 0; component < raster.getNumBands(); component++) {
|
|
||||||
out.writeByte(component); // Comp id
|
|
||||||
out.writeByte(component == 0 ? component : 0x10 + (component & 0xf)); // dc/ac selector
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unknown 3 bytes pad... TODO: Figure out what the last 3 bytes are...
|
|
||||||
out.writeByte(0);
|
|
||||||
out.writeByte(0);
|
|
||||||
out.writeByte(0);
|
|
||||||
|
|
||||||
stream = ImageIO.createImageInputStream(new SequenceInputStream(Collections.enumeration(
|
stream = ImageIO.createImageInputStream(new SequenceInputStream(Collections.enumeration(
|
||||||
Arrays.asList(
|
Arrays.asList(
|
||||||
jfifBytes.createInputStream(),
|
createJFIFStream(raster, stripTileWidth, stripTileHeight, qTables, dcTables, acTables),
|
||||||
IIOUtil.createStreamAdapter(imageInput, stripTileByteCounts != null ? (int) stripTileByteCounts[i] : Short.MAX_VALUE),
|
IIOUtil.createStreamAdapter(imageInput, stripTileByteCounts != null ? (int) stripTileByteCounts[i] : Short.MAX_VALUE),
|
||||||
new ByteArrayInputStream(new byte[] {(byte) 0xff, (byte) 0xd9}) // EOI
|
new ByteArrayInputStream(new byte[] {(byte) 0xff, (byte) 0xd9}) // EOI
|
||||||
)
|
)
|
||||||
@ -987,6 +924,75 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
return destination;
|
return destination;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static InputStream createJFIFStream(WritableRaster raster, int stripTileWidth, int stripTileHeight, byte[][] qTables, byte[][] dcTables, byte[][] acTables) throws IOException {
|
||||||
|
FastByteArrayOutputStream stream = new FastByteArrayOutputStream(
|
||||||
|
2 + 2 + 2 + 6 + 3 * raster.getNumBands() +
|
||||||
|
5 * qTables.length + qTables.length * qTables[0].length +
|
||||||
|
5 * dcTables.length + dcTables.length * dcTables[0].length +
|
||||||
|
5 * acTables.length + acTables.length * acTables[0].length +
|
||||||
|
8 + 2 * raster.getNumBands()
|
||||||
|
);
|
||||||
|
|
||||||
|
DataOutputStream out = new DataOutputStream(stream);
|
||||||
|
|
||||||
|
out.writeShort(JPEG.SOI);
|
||||||
|
out.writeShort(JPEG.SOF0);
|
||||||
|
out.writeShort(2 + 6 + 3 * raster.getNumBands()); // SOF0 len
|
||||||
|
out.writeByte(8); // bits TODO: Consult raster/transfer type or BitsPerSample for 12/16 bits support
|
||||||
|
out.writeShort(stripTileHeight); // height
|
||||||
|
out.writeShort(stripTileWidth); // width
|
||||||
|
out.writeByte(raster.getNumBands()); // Number of components
|
||||||
|
|
||||||
|
for (int comp = 0; comp < raster.getNumBands(); comp++) {
|
||||||
|
out.writeByte(comp); // Component id
|
||||||
|
out.writeByte(comp == 0 ? 0x22 : 0x11); // h/v subsampling TODO: FixMe, consult YCbCrSubsampling
|
||||||
|
out.writeByte(comp); // Q table selector TODO: Consider merging if tables are equal
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Consider merging if tables are equal
|
||||||
|
for (int tableIndex = 0; tableIndex < qTables.length; tableIndex++) {
|
||||||
|
byte[] table = qTables[tableIndex];
|
||||||
|
out.writeShort(JPEG.DQT);
|
||||||
|
out.writeShort(3 + table.length); // DQT length
|
||||||
|
out.writeByte(tableIndex); // Q table id
|
||||||
|
out.write(table); // Table data
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Consider merging if tables are equal
|
||||||
|
for (int tableIndex = 0; tableIndex < dcTables.length; tableIndex++) {
|
||||||
|
byte[] table = dcTables[tableIndex];
|
||||||
|
out.writeShort(JPEG.DHT);
|
||||||
|
out.writeShort(3 + table.length); // DHT length
|
||||||
|
out.writeByte(tableIndex); // Huffman table id
|
||||||
|
out.write(table); // Table data
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Consider merging if tables are equal
|
||||||
|
for (int tableIndex = 0; tableIndex < acTables.length; tableIndex++) {
|
||||||
|
byte[] table = acTables[tableIndex];
|
||||||
|
out.writeShort(JPEG.DHT);
|
||||||
|
out.writeShort(3 + table.length); // DHT length
|
||||||
|
out.writeByte(0x10 + (tableIndex & 0xf)); // Huffman table id
|
||||||
|
out.write(table); // Table data
|
||||||
|
}
|
||||||
|
|
||||||
|
out.writeShort(JPEG.SOS);
|
||||||
|
out.writeShort(6 + 2 * raster.getNumBands()); // SOS length
|
||||||
|
out.writeByte(raster.getNumBands()); // Num comp
|
||||||
|
|
||||||
|
for (int component = 0; component < raster.getNumBands(); component++) {
|
||||||
|
out.writeByte(component); // Comp id
|
||||||
|
out.writeByte(component == 0 ? component : 0x10 + (component & 0xf)); // dc/ac selector
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unknown 3 bytes pad... TODO: Figure out what the last 3 bytes are...
|
||||||
|
out.writeByte(0);
|
||||||
|
out.writeByte(0);
|
||||||
|
out.writeByte(0);
|
||||||
|
|
||||||
|
return stream.createInputStream();
|
||||||
|
}
|
||||||
|
|
||||||
private void readStripTileData(final WritableRaster rowRaster, final int interpretation, final int predictor,
|
private void readStripTileData(final WritableRaster rowRaster, final int interpretation, final int predictor,
|
||||||
final WritableRaster raster, final int numBands, final int col, final int startRow,
|
final WritableRaster raster, final int numBands, final int col, final int startRow,
|
||||||
final int colsInStrip, final int rowsInStrip, final DataInput input)
|
final int colsInStrip, final int rowsInStrip, final DataInput input)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user