Merge remote-tracking branch 'origin/master'

This commit is contained in:
Harald Kuhr 2018-09-07 21:07:23 +02:00
commit 5a3945c411
12 changed files with 372 additions and 129 deletions

View File

@ -480,12 +480,12 @@ To depend on the JPEG and TIFF plugin using Maven, add the following to your POM
<dependency> <dependency>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-jpeg</artifactId> <artifactId>imageio-jpeg</artifactId>
<version>3.3.2</version> <!-- Alternatively, build your own version --> <version>3.4</version> <!-- Alternatively, build your own version -->
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-tiff</artifactId> <artifactId>imageio-tiff</artifactId>
<version>3.3.2</version> <!-- Alternatively, build your own version --> <version>3.4</version> <!-- Alternatively, build your own version -->
</dependency> </dependency>
</dependencies> </dependencies>
@ -493,52 +493,52 @@ To depend on the JPEG and TIFF plugin using Maven, add the following to your POM
To depend on the JPEG and TIFF plugin in your IDE or program, add all of the following JARs to your class path: To depend on the JPEG and TIFF plugin in your IDE or program, add all of the following JARs to your class path:
twelvemonkeys-common-lang-3.3.2.jar twelvemonkeys-common-lang-3.4.jar
twelvemonkeys-common-io-3.3.2.jar twelvemonkeys-common-io-3.4.jar
twelvemonkeys-common-image-3.3.2.jar twelvemonkeys-common-image-3.4.jar
twelvemonkeys-imageio-core-3.3.2.jar twelvemonkeys-imageio-core-3.4.jar
twelvemonkeys-imageio-metadata-3.3.2.jar twelvemonkeys-imageio-metadata-3.4.jar
twelvemonkeys-imageio-jpeg-3.3.2.jar twelvemonkeys-imageio-jpeg-3.4.jar
twelvemonkeys-imageio-tiff-3.3.2.jar twelvemonkeys-imageio-tiff-3.4.jar
### Links to prebuilt binaries ### Links to prebuilt binaries
##### Latest version (3.2.x) ##### Latest version (3.4)
Requires Java 7 or later. Requires Java 7 or later.
Common dependencies Common dependencies
* [common-lang-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.3.2/common-lang-3.3.2.jar) * [common-lang-3.4.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.4/common-lang-3.4.jar)
* [common-io-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.3.2/common-io-3.3.2.jar) * [common-io-3.4.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.4/common-io-3.4.jar)
* [common-image-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.3.2/common-image-3.3.2.jar) * [common-image-3.4.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.4/common-image-3.4.jar)
ImageIO dependencies ImageIO dependencies
* [imageio-core-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.3.2/imageio-core-3.3.2.jar) * [imageio-core-3.4.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.4/imageio-core-3.4.jar)
* [imageio-metadata-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.3.2/imageio-metadata-3.3.2.jar) * [imageio-metadata-3.4.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.4/imageio-metadata-3.4.jar)
ImageIO plugins ImageIO plugins
* [imageio-bmp-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.3.2/imageio-bmp-3.3.2.jar) * [imageio-bmp-3.4.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.4/imageio-bmp-3.4.jar)
* [imageio-jpeg-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.3.2/imageio-jpeg-3.3.2.jar) * [imageio-jpeg-3.4.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.4/imageio-jpeg-3.4.jar)
* [imageio-tiff-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.3.2/imageio-tiff-3.3.2.jar) * [imageio-tiff-3.4.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.4/imageio-tiff-3.4.jar)
* [imageio-pnm-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pnm/3.3.2/imageio-pnm-3.3.2.jar) * [imageio-pnm-3.4.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pnm/3.4/imageio-pnm-3.4.jar)
* [imageio-psd-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.3.2/imageio-psd-3.3.2.jar) * [imageio-psd-3.4.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.4/imageio-psd-3.4.jar)
* [imageio-hdr-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-hdr/3.3.2/imageio-hdr-3.3.2.jar) * [imageio-hdr-3.4.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-hdr/3.4/imageio-hdr-3.4.jar)
* [imageio-iff-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.3.2/imageio-iff-3.3.2.jar) * [imageio-iff-3.4.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.4/imageio-iff-3.4.jar)
* [imageio-pcx-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pcx/3.3.2/imageio-pcx-3.3.2.jar) * [imageio-pcx-3.4.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pcx/3.4/imageio-pcx-3.4.jar)
* [imageio-pict-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.3.2/imageio-pict-3.3.2.jar) * [imageio-pict-3.4.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.4/imageio-pict-3.4.jar)
* [imageio-sgi-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-sgi/3.3.2/imageio-sgi-3.3.2.jar) * [imageio-sgi-3.4.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-sgi/3.4/imageio-sgi-3.4.jar)
* [imageio-tga-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.3.2/imageio-tga-3.3.2.jar) * [imageio-tga-3.4.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.4/imageio-tga-3.4.jar)
* [imageio-icns-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.3.2/imageio-icns-3.3.2.jar) * [imageio-icns-3.4.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.4/imageio-icns-3.4.jar)
* [imageio-thumbsdb-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.3.2/imageio-thumbsdb-3.3.2.jar) * [imageio-thumbsdb-3.4.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.4/imageio-thumbsdb-3.4.jar)
ImageIO plugins requiring 3rd party libs ImageIO plugins requiring 3rd party libs
* [imageio-batik-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.3.2/imageio-batik-3.3.2.jar) * [imageio-batik-3.4.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.4/imageio-batik-3.4.jar)
Photoshop Path support for ImageIO Photoshop Path support for ImageIO
* [imageio-clippath-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.3.2/imageio-clippath-3.3.2.jar) * [imageio-clippath-3.4.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.4/imageio-clippath-3.4.jar)
Servlet support Servlet support
* [servlet-3.3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.3.2/servlet-3.3.2.jar) * [servlet-3.4.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.4/servlet-3.4.jar)
##### Old version (3.0.x) ##### Old version (3.0.x)

View File

@ -45,11 +45,9 @@ import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream; import javax.imageio.stream.ImageOutputStream;
import java.awt.geom.AffineTransform; import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream; import java.io.*;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -358,13 +356,21 @@ public final class TIFFUtilities {
} }
} }
int compression = -1;
Entry compressionEntry = IFD.getEntryById(TIFF.TAG_COMPRESSION);
if (compressionEntry != null && compressionEntry.getValue() instanceof Number) {
compression = ((Number) compressionEntry.getValue()).shortValue();
}
boolean rearrangedByteStrips = false; boolean rearrangedByteStrips = false;
Entry oldJpegData = IFD.getEntryById(TIFF.TAG_JPEG_INTERCHANGE_FORMAT); Entry oldJpegData = IFD.getEntryById(TIFF.TAG_JPEG_INTERCHANGE_FORMAT);
Entry oldJpegDataLength = IFD.getEntryById(TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH); Entry oldJpegDataLength = IFD.getEntryById(TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
long[] jpegByteCounts = null;
long[] jpegOffsets = null;
if (oldJpegData != null && oldJpegData.valueCount() > 0) { if (oldJpegData != null && oldJpegData.valueCount() > 0) {
// convert JPEGInterchangeFormat to new-style-JPEG // convert JPEGInterchangeFormat to new-style-JPEG
long[] jpegByteCounts = new long[0]; jpegByteCounts = new long[0];
long[] jpegOffsets = getValueAsLongArray(oldJpegData); jpegOffsets = getValueAsLongArray(oldJpegData);
if (oldJpegDataLength != null && oldJpegDataLength.valueCount() > 0) { if (oldJpegDataLength != null && oldJpegDataLength.valueCount() > 0) {
jpegByteCounts = getValueAsLongArray(oldJpegDataLength); jpegByteCounts = getValueAsLongArray(oldJpegDataLength);
} }
@ -388,6 +394,20 @@ public final class TIFFUtilities {
newIFD.remove(oldJpegDataLength); newIFD.remove(oldJpegDataLength);
rearrangedByteStrips = true; rearrangedByteStrips = true;
} }
else if (offsets.length == 1 && oldJpegDataLength != null && (jpegOffsets[0] < offsets[0]) && (jpegOffsets[0] + jpegByteCounts[0]) > (offsets[0] + byteCounts[0])) {
// ByteStrip contains only a part of JPEGInterchangeFormat
newOffsets = writeData(jpegOffsets, jpegByteCounts, outputStream);
newIFD.remove(stripOffsetsEntry);
newIFD.add(new TIFFEntry(useTiles ? TIFF.TAG_TILE_OFFSETS : TIFF.TAG_STRIP_OFFSETS, newOffsets));
newIFD.remove(stripByteCountsEntry);
newIFD.add(new TIFFEntry(useTiles ? TIFF.TAG_TILE_BYTE_COUNTS : TIFF.TAG_STRIP_BYTE_COUNTS, new int[]{(int) (jpegByteCounts[0])}));
newIFD.remove(oldJpegData);
newIFD.remove(oldJpegDataLength);
rearrangedByteStrips = true;
}
else if (oldJpegDataLength != null) { else if (oldJpegDataLength != null) {
// multiple bytestrips // multiple bytestrips
// search for SOF on first strip and copy to each if needed // search for SOF on first strip and copy to each if needed
@ -423,7 +443,7 @@ public final class TIFFUtilities {
byte[] buffer = new byte[(int) byteCounts[i]]; byte[] buffer = new byte[(int) byteCounts[i]];
newByteCounts[i] = (int) (jpegInterchangeData.length + byteCounts[i]); newByteCounts[i] = (int) (jpegInterchangeData.length + byteCounts[i]);
stream.readFully(buffer); stream.readFully(buffer);
if (buffer[0] != 0xff && buffer[1] != 0xda) { if (buffer[0] != ((byte) 0xff) || buffer[1] != ((byte) 0xda)) {
outputStream.write(sosMarker); outputStream.write(sosMarker);
newByteCounts[i] += sosMarker.length; newByteCounts[i] += sosMarker.length;
} }
@ -440,7 +460,58 @@ public final class TIFFUtilities {
rearrangedByteStrips = true; rearrangedByteStrips = true;
} }
} }
else if (compression == TIFFExtension.COMPRESSION_OLD_JPEG) {
// old-style but no JPEGInterchangeFormat
long[] yCbCrSubSampling = getValueAsLongArray(IFD.getEntryById(TIFF.TAG_YCBCR_SUB_SAMPLING));
int subsampling = yCbCrSubSampling != null
? (int) ((yCbCrSubSampling[0] & 0xf) << 4 | yCbCrSubSampling[1] & 0xf)
: 0x22;
int bands = ((Number) IFD.getEntryById(TIFF.TAG_SAMPLES_PER_PIXEL).getValue()).intValue();
int w = ((Number) IFD.getEntryById(TIFF.TAG_IMAGE_WIDTH).getValue()).intValue();
int h = ((Number) IFD.getEntryById(TIFF.TAG_IMAGE_HEIGHT).getValue()).intValue();
int r = ((Number) (useTiles ? IFD.getEntryById(TIFF.TAG_TILE_HEIGTH) : IFD.getEntryById(TIFF.TAG_ROWS_PER_STRIP)).getValue()).intValue();
int c = useTiles ? ((Number) IFD.getEntryById(TIFF.TAG_TILE_WIDTH).getValue()).intValue() : w;
newOffsets = new int[offsets.length];
int[] newByteCounts = new int[byteCounts.length];
// No JPEGInterchangeFormat
for (int i = 0; i < offsets.length; i++) {
byte[] start = new byte[2];
stream.seek(offsets[i]);
stream.readFully(start);
newOffsets[i] = (int) outputStream.getStreamPosition();
if (start[0] == ((byte) 0xff) && start[1] == ((byte) 0xd8)) {
// full image stream, nothing to do
writeData(stream, outputStream, offsets[i], byteCounts[i]);
}
else if (start[0] == ((byte) 0xff) && start[1] == ((byte) 0xda)) {
// starts with SOS
outputStream.writeShort(JPEG.SOI);
writeSOF0(outputStream, bands, c, r, subsampling);
writeData(stream, outputStream, offsets[i], byteCounts[i]);
outputStream.writeShort(JPEG.EOI);
}
else {
// raw data
outputStream.writeShort(JPEG.SOI);
writeSOF0(outputStream, bands, c, r, subsampling);
writeSOS(outputStream, bands);
writeData(stream, outputStream, offsets[i], byteCounts[i]);
outputStream.writeShort(JPEG.EOI);
}
newByteCounts[i] = ((int) outputStream.getStreamPosition()) - newOffsets[i];
}
newIFD.remove(stripOffsetsEntry);
newIFD.add(new TIFFEntry(useTiles ? TIFF.TAG_TILE_OFFSETS : TIFF.TAG_STRIP_OFFSETS, newOffsets));
newIFD.remove(stripByteCountsEntry);
newIFD.add(new TIFFEntry(useTiles ? TIFF.TAG_TILE_BYTE_COUNTS : TIFF.TAG_STRIP_BYTE_COUNTS, newByteCounts));
rearrangedByteStrips = true;
}
if (!rearrangedByteStrips && stripOffsetsEntry != null && stripByteCountsEntry != null) { if (!rearrangedByteStrips && stripOffsetsEntry != null && stripByteCountsEntry != null) {
newOffsets = writeData(offsets, byteCounts, outputStream); newOffsets = writeData(offsets, byteCounts, outputStream);
@ -457,12 +528,21 @@ public final class TIFFUtilities {
oldJpegTableQ = IFD.getEntryById(TIFF.TAG_OLD_JPEG_Q_TABLES); oldJpegTableQ = IFD.getEntryById(TIFF.TAG_OLD_JPEG_Q_TABLES);
oldJpegTableDC = IFD.getEntryById(TIFF.TAG_OLD_JPEG_DC_TABLES); oldJpegTableDC = IFD.getEntryById(TIFF.TAG_OLD_JPEG_DC_TABLES);
oldJpegTableAC = IFD.getEntryById(TIFF.TAG_OLD_JPEG_AC_TABLES); oldJpegTableAC = IFD.getEntryById(TIFF.TAG_OLD_JPEG_AC_TABLES);
if (oldJpegTableQ != null || oldJpegTableDC != null || oldJpegTableAC != null) { if ((oldJpegTableQ != null) || (oldJpegTableDC != null) || (oldJpegTableAC != null)) {
if (IFD.getEntryById(TIFF.TAG_JPEG_TABLES) != null) { if (IFD.getEntryById(TIFF.TAG_JPEG_TABLES) != null) {
throw new IOException("Found old-style and new-style JPEGTables"); throw new IOException("Found old-style and new-style JPEGTables");
} }
newIFD.add(mergeTables(oldJpegTableQ, oldJpegTableDC, oldJpegTableAC)); boolean tablesInStream = jfifContainsTables(oldJpegTableQ, jpegOffsets, jpegByteCounts);
tablesInStream &= jfifContainsTables(oldJpegTableDC, jpegOffsets, jpegByteCounts);
tablesInStream &= jfifContainsTables(oldJpegTableAC, jpegOffsets, jpegByteCounts);
if (!tablesInStream) {
// merge them only to JPEGTables if they are not already contained within the stream
Entry jpegTables = mergeTables(oldJpegTableQ, oldJpegTableDC, oldJpegTableAC);
if (jpegTables != null) {
newIFD.add(jpegTables);
}
}
if (oldJpegTableQ != null) { if (oldJpegTableQ != null) {
newIFD.remove(oldJpegTableQ); newIFD.remove(oldJpegTableQ);
} }
@ -474,17 +554,66 @@ public final class TIFFUtilities {
} }
} }
Entry compressionEntry = IFD.getEntryById(TIFF.TAG_COMPRESSION); if (compressionEntry != null && compression == TIFFExtension.COMPRESSION_OLD_JPEG) {
if(compressionEntry != null) { newIFD.remove(compressionEntry);
Number compression = (Number) compressionEntry.getValue(); newIFD.add(new TIFFEntry(TIFF.TAG_COMPRESSION, TIFF.TYPE_SHORT, TIFFExtension.COMPRESSION_JPEG));
if (compression.shortValue() == TIFFExtension.COMPRESSION_OLD_JPEG) {
newIFD.remove(compressionEntry);
newIFD.add(new TIFFEntry(TIFF.TAG_COMPRESSION, TIFF.TYPE_SHORT, TIFFExtension.COMPRESSION_JPEG));
}
} }
return newIFD; return newIFD;
} }
//TODO merge/extract from TIFFReader Jpeg/6 stream reconstruction
private void writeSOF0(ImageOutputStream outputStream, int bands, int width, int height, int subsampling) throws IOException {
outputStream.writeShort(JPEG.SOF0); // TODO: Use correct process for data
outputStream.writeShort(2 + 6 + 3 * bands); // SOF0 len
outputStream.writeByte(8); // bits TODO: Consult raster/transfer type or BitsPerSample for 12/16 bits support
outputStream.writeShort(height); // height
outputStream.writeShort(width); // width
outputStream.writeByte(bands); // Number of components
for (int comp = 0; comp < bands; comp++) {
outputStream.writeByte(comp); // Component id
outputStream.writeByte(comp == 0 ? subsampling : 0x11); // h/v subsampling
outputStream.writeByte(comp); // Q table selector TODO: Consider merging if tables are equal, correct selection if only 1 or 2 valid tables are contained
}
}
//TODO merge/extract from TIFFReader Jpeg/6 stream reconstruction
private void writeSOS(ImageOutputStream outputStream, int bands) throws IOException {
outputStream.writeShort(JPEG.SOS);
outputStream.writeShort(6 + 2 * bands); // SOS length
outputStream.writeByte(bands); // Num comp
for (int component = 0; component < bands; component++) {
outputStream.writeByte(component); // Comp id
outputStream.writeByte(component == 0 ? component : 0x10 + (component & 0xf)); // dc/ac selector TODO: correct selection if only 1 or 2 valid tables are contained
}
outputStream.writeByte(0); // Spectral selection start
outputStream.writeByte(0); // Spectral selection end
outputStream.writeByte(0); // Approx high & low
}
private void writeData(ImageInputStream input, ImageOutputStream output, long offset, long length) throws IOException {
input.seek(offset);
byte[] buffer = new byte[(int) length];
stream.readFully(buffer);
output.write(buffer);
}
private boolean jfifContainsTables(Entry tableEntry, long[] jpegOffsets, long[] jpegLengths) throws IOException {
if (jpegLengths == null || jpegOffsets == null || jpegLengths.length == 0) return false;
if (tableEntry != null) {
long[] tableOffsets = getValueAsLongArray(tableEntry);
for (long offset : tableOffsets) {
if (offset < jpegOffsets[0] || offset > (jpegOffsets[0] + jpegLengths[0])) {
return false;
}
}
}
return true;
}
//TODO merge/extract from TIFFReader Jpeg/6 stream reconstruction
private Entry mergeTables(Entry qEntry, Entry dcEntry, Entry acEntry) throws IOException { private Entry mergeTables(Entry qEntry, Entry dcEntry, Entry acEntry) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos); DataOutputStream dos = new DataOutputStream(bos);
@ -494,12 +623,16 @@ public final class TIFFUtilities {
long[] off = getValueAsLongArray(qEntry); long[] off = getValueAsLongArray(qEntry);
byte[] table = new byte[64]; byte[] table = new byte[64];
for (int tableId = 0; tableId < off.length; tableId++) { for (int tableId = 0; tableId < off.length; tableId++) {
stream.seek(off[tableId]); try {
stream.readFully(table); stream.seek(off[tableId]);
dos.writeShort(JPEG.DQT); stream.readFully(table);
dos.writeShort(3 + 64); dos.writeShort(JPEG.DQT);
dos.writeByte(tableId); dos.writeShort(3 + 64);
dos.write(table); dos.writeByte(tableId);
dos.write(table);
} catch (EOFException e) {
// invalid table pointer, ignore
}
} }
} }
@ -507,30 +640,50 @@ public final class TIFFUtilities {
if (dcEntry != null && dcEntry.valueCount() > 0) { if (dcEntry != null && dcEntry.valueCount() > 0) {
long[] off = getValueAsLongArray(dcEntry); long[] off = getValueAsLongArray(dcEntry);
for (int tableId = 0; tableId < off.length; tableId++) { for (int tableId = 0; tableId < off.length; tableId++) {
stream.seek(off[tableId]); try {
byte[] table = readHUFFTable(); stream.seek(off[tableId]);
dos.writeShort(JPEG.DHT); byte[] table = readHUFFTable();
dos.writeShort(3 + table.length); if (table.length > (16 + 17)) {
dos.writeByte(tableId); // to long, table is invalid, just ignoe
dos.write(table); continue;
}
dos.writeShort(JPEG.DHT);
dos.writeShort(3 + table.length);
dos.writeByte(tableId);
dos.write(table);
} catch (EOFException e) {
// invalid table pointer, ignore
}
} }
} }
if (acEntry != null && acEntry.valueCount() > 0) { if (acEntry != null && acEntry.valueCount() > 0) {
long[] off = getValueAsLongArray(acEntry); long[] off = getValueAsLongArray(acEntry);
for (int tableId = 0; tableId < off.length; tableId++) { for (int tableId = 0; tableId < off.length; tableId++) {
stream.seek(off[tableId]); try {
byte[] table = readHUFFTable(); stream.seek(off[tableId]);
dos.writeShort(JPEG.DHT); byte[] table = readHUFFTable();
dos.writeShort(3 + table.length); if (table.length > (16 + 256)) {
dos.writeByte(16 | tableId); // to long, table is invalid, just ignoe
dos.write(table); continue;
}
dos.writeShort(JPEG.DHT);
dos.writeShort(3 + table.length);
dos.writeByte(16 | tableId);
dos.write(table);
} catch (EOFException e) {
// invalid table pointer, ignore
}
} }
} }
dos.writeShort(JPEG.EOI); dos.writeShort(JPEG.EOI);
bos.close(); bos.close();
if (bos.size() == 4) {
// no valid tables, don't add
return null;
}
return new TIFFEntry(TIFF.TAG_JPEG_TABLES, TIFF.TYPE_UNDEFINED, bos.toByteArray()); return new TIFFEntry(TIFF.TAG_JPEG_TABLES, TIFF.TYPE_UNDEFINED, bos.toByteArray());
} }
@ -540,15 +693,11 @@ public final class TIFFUtilities {
stream.readFully(lengths); stream.readFully(lengths);
int numCodes = 0; int numCodes = 0;
for (int i = 0; i < lengths.length; i++) { for (int i = 0; i < lengths.length; i++) {
numCodes += lengths[i]; numCodes += ((int) lengths[i]) & 0xff;
} }
byte table[] = new byte[16 + numCodes]; byte table[] = new byte[16 + numCodes];
System.arraycopy(lengths, 0, table, 0, 16); System.arraycopy(lengths, 0, table, 0, 16);
int off = 16; stream.readFully(table, 16, numCodes);
for (int i = 0; i < lengths.length; i++) {
stream.read(table, off, lengths[i]);
off += lengths[i];
}
return table; return table;
} }
@ -559,7 +708,11 @@ public final class TIFFUtilities {
stream.seek(offsets[i]); stream.seek(offsets[i]);
byte[] buffer = new byte[(int) byteCounts[i]]; byte[] buffer = new byte[(int) byteCounts[i]];
stream.readFully(buffer); try {
stream.readFully(buffer);
} catch (EOFException e) {
// invalid strip length
}
outputStream.write(buffer); outputStream.write(buffer);
} }
return newOffsets; return newOffsets;
@ -571,7 +724,7 @@ public final class TIFFUtilities {
if (entry.valueCount() == 1) { if (entry.valueCount() == 1) {
// For single entries, this will be a boxed type // For single entries, this will be a boxed type
value = new long[] {((Number) entry.getValue()).longValue()}; value = new long[]{((Number) entry.getValue()).longValue()};
} }
else if (entry.getValue() instanceof short[]) { else if (entry.getValue() instanceof short[]) {
short[] shorts = (short[]) entry.getValue(); short[] shorts = (short[]) entry.getValue();

View File

@ -202,36 +202,39 @@ public class TIFFUtilitiesTest {
} }
@Test @Test
public void testMergeBogusInterchangeFormatLength() throws IOException { public void testOldStyleJPEGTransform() throws IOException {
String[] testFiles = new String[] { String[] testFiles = new String[]{
"/tiff/old-style-jpeg-bogus-jpeginterchangeformatlength.tif", // InterchangeFormat before StripOffset, length not including StripOffset "/tiff/old-style-jpeg-bogus-jpeginterchangeformatlength.tif", // InterchangeFormat before StripOffset, length not including StripOffset
"/tiff/old-style-jpeg-no-jpeginterchangeformatlength.tif", // missing JPEGInterChangeFormatLength and JPEGInterchangeFormat == StipOffset "/tiff/old-style-jpeg-no-jpeginterchangeformatlength.tif", // missing JPEGInterChangeFormatLength and JPEGInterchangeFormat == StipOffset
"/tiff/old-style-jpeg-multiple-strips.tif" // InterchangeFormat with multiple strips "/tiff/old-style-jpeg-multiple-strips.tif", // InterchangeFormat with multiple strips
"/contrib/tiff/old-style-jpeg-invalid-tables.tif", // AC/DC Tables are invalid (to long) and lie within the JPEGInterchangeFormat stream
"/contrib/tiff/smallliz.tif", // InterchangeFormat contains whole JPEG, ByteStrip only raw ImageData after SOS
"/contrib/tiff/WangJPEG.tif", // multiple strips, first strip contains SOS
"/contrib/tiff/zackthecat.tif" // No JPEGInterchangeFormat, ByteStrip contains only raw image data
}; };
for (String testFile : testFiles) { for (String testFile : testFiles) {
File output = File.createTempFile("imageiotest", ".tif"); try {
ImageOutputStream outputStream = ImageIO.createImageOutputStream(output); File output = File.createTempFile("imageiotest", ".tif");
InputStream inputStream1 = getClassLoaderResource(testFile).openStream(); ImageOutputStream outputStream = ImageIO.createImageOutputStream(output);
ImageInputStream imageInput1 = ImageIO.createImageInputStream(inputStream1); InputStream inputStream = getClassLoaderResource(testFile).openStream();
InputStream inputStream2 = getClassLoaderResource(testFile).openStream(); ImageInputStream imageInput = ImageIO.createImageInputStream(inputStream);
ImageInputStream imageInput2 = ImageIO.createImageInputStream(inputStream2); List<TIFFUtilities.TIFFPage> pages = TIFFUtilities.getPages(imageInput);
ArrayList<TIFFUtilities.TIFFPage> pages = new ArrayList<>(); TIFFUtilities.writePages(outputStream, pages);
pages.addAll(TIFFUtilities.getPages(imageInput1));
pages.addAll(TIFFUtilities.getPages(imageInput2));
TIFFUtilities.writePages(outputStream, pages);
ImageInputStream testOutput = ImageIO.createImageInputStream(output); ImageInputStream testOutput = ImageIO.createImageInputStream(output);
ImageReader reader = ImageIO.getImageReaders(testOutput).next(); ImageReader reader = ImageIO.getImageReaders(testOutput).next();
reader.setInput(testOutput); reader.setInput(testOutput);
int numImages = reader.getNumImages(true); int numImages = reader.getNumImages(true);
for (int i = 0; i < numImages; i++) { for (int i = 0; i < numImages; i++) {
reader.read(i); reader.read(i);
}
imageInput.close();
outputStream.close();
} catch (Exception exc) {
throw new IOException(testFile, exc);
} }
imageInput1.close();
imageInput2.close();
outputStream.close();
} }
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -130,7 +130,7 @@ public abstract class AbstractMetadata extends IIOMetadata implements Cloneable
} }
throw new IllegalArgumentException( throw new IllegalArgumentException(
String.format("Bad format name: \"%s\". Expected one of %s", formatName, Arrays.toString(metadataFormatNames)) String.format("Unsupported format name: \"%s\". Expected one of %s", formatName, Arrays.toString(metadataFormatNames))
); );
} }

View File

@ -101,8 +101,16 @@ public final class ColorSpaces {
private static final Map<Key, ICC_ColorSpace> cache = new LRUHashMap<>(10); private static final Map<Key, ICC_ColorSpace> cache = new LRUHashMap<>(10);
static { static {
// Force invocation of ProfileDeferralMgr.activateProfiles() to avoid JDK-6986863 try {
ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData(); // Force invocation of ProfileDeferralMgr.activateProfiles() to avoid JDK-6986863
ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData();
}
catch (Throwable disasters) {
System.err.println("ICC Color Profile not properly activated due to the exception below.");
System.err.println("Expect to see JDK-6986863 in action, and consider filing a bug report to your JRE provider.");
disasters.printStackTrace();
}
} }
private ColorSpaces() {} private ColorSpaces() {}

View File

@ -68,10 +68,7 @@ final class JPEGLosslessDecoder {
private int xLoc; private int xLoc;
private int yLoc; private int yLoc;
private int mask; private int mask;
private int[] outputData; private int[][] outputData;
private int[] outputRedData;
private int[] outputGreenData;
private int[] outputBlueData;
private static final int IDCT_P[] = { private static final int IDCT_P[] = {
0, 5, 40, 16, 45, 2, 7, 42, 0, 5, 40, 16, 45, 2, 7, 42,
@ -148,8 +145,6 @@ final class JPEGLosslessDecoder {
int[][] decode() throws IOException { int[][] decode() throws IOException {
int current, scanNum = 0; int current, scanNum = 0;
final int pred[] = new int[10];
int[][] outputRef;
xLoc = 0; xLoc = 0;
yLoc = 0; yLoc = 0;
@ -218,32 +213,27 @@ final class JPEGLosslessDecoder {
xDim = frame.samplesPerLine; xDim = frame.samplesPerLine;
yDim = frame.lines; yDim = frame.lines;
outputRef = new int[numComp][]; outputData = new int[numComp][];
// TODO: Support 4 components (RGBA/YCCA/CMYK/YCCK), others? for (int componentIndex = 0; componentIndex < numComp; ++componentIndex) {
if (numComp == 1) { // not a good use of memory, but I had trouble packing bytes into int. some values exceeded 255.
outputData = new int[xDim * yDim]; outputData[componentIndex] = new int[xDim * yDim];
outputRef[0] = outputData;
} }
else {
outputRedData = new int[xDim * yDim]; // not a good use of memory, but I had trouble packing bytes into int. some values exceeded 255.
outputGreenData = new int[xDim * yDim];
outputBlueData = new int[xDim * yDim];
outputRef[0] = outputRedData; final int firstValue[] = new int[numComp];
outputRef[1] = outputGreenData; for (int i = 0; i < numComp; i++) {
outputRef[2] = outputBlueData; firstValue[i] = (1 << (precision - 1));
} }
final int pred[] = new int[numComp];
scanNum++; scanNum++;
while (true) { // Decode one scan while (true) { // Decode one scan
int temp[] = new int[1]; // to store remainder bits int temp[] = new int[1]; // to store remainder bits
int index[] = new int[1]; int index[] = new int[1];
for (int i = 0; i < 10; i++) { System.arraycopy(firstValue, 0, pred, 0, numComp);
pred[i] = (1 << (precision - 1));
}
if (restartInterval == 0) { if (restartInterval == 0) {
current = decode(pred, temp, index); current = decode(pred, temp, index);
@ -285,9 +275,10 @@ final class JPEGLosslessDecoder {
readNumber(); readNumber();
current = input.readUnsignedShort(); current = input.readUnsignedShort();
} }
// TODO oe: 05.05.2018 Is it correct loop? Content of outputData from previous iteration is always lost.
} while ((current != JPEG.EOI) && ((xLoc < xDim) && (yLoc < yDim)) && (scanNum == 0)); } while ((current != JPEG.EOI) && ((xLoc < xDim) && (yLoc < yDim)) && (scanNum == 0));
return outputRef; return outputData;
} }
private void processWarningOccured(String warning) { private void processWarningOccured(String warning) {
@ -341,7 +332,7 @@ final class JPEGLosslessDecoder {
return decodeRGB(prev, temp, index); return decodeRGB(prev, temp, index);
} }
else { else {
return -1; return decodeAny(prev, temp, index);
} }
} }
@ -353,6 +344,7 @@ final class JPEGLosslessDecoder {
prev[0] = (1 << (frame.samplePrecision - 1)); prev[0] = (1 << (frame.samplePrecision - 1));
} }
else { else {
final int[] outputData = this.outputData[0];
switch (selection) { switch (selection) {
case 2: case 2:
prev[0] = getPreviousY(outputData); prev[0] = getPreviousY(outputData);
@ -399,6 +391,9 @@ final class JPEGLosslessDecoder {
} }
private int decodeRGB(final int prev[], final int temp[], final int index[]) throws IOException { private int decodeRGB(final int prev[], final int temp[], final int index[]) throws IOException {
final int[] outputRedData = outputData[0];
final int[] outputGreenData = outputData[1];
final int[] outputBlueData = outputData[2];
switch (selection) { switch (selection) {
case 2: case 2:
prev[0] = getPreviousY(outputRedData); prev[0] = getPreviousY(outputRedData);
@ -437,6 +432,43 @@ final class JPEGLosslessDecoder {
break; break;
} }
return decode0(prev, temp, index);
}
private int decodeAny(final int prev[], final int temp[], final int index[]) throws IOException {
for (int componentIndex = 0; componentIndex < outputData.length; ++componentIndex) {
final int[] outputData = this.outputData[componentIndex];
final int previous;
switch (selection) {
case 2:
previous = getPreviousY(outputData);
break;
case 3:
previous = getPreviousXY(outputData);
break;
case 4:
previous = (getPreviousX(outputData) + getPreviousY(outputData)) - getPreviousXY(outputData);
break;
case 5:
previous = getPreviousX(outputData) + ((getPreviousY(outputData) - getPreviousXY(outputData)) >> 1);
break;
case 6:
previous = getPreviousY(outputData) + ((getPreviousX(outputData) - getPreviousXY(outputData)) >> 1);
break;
case 7:
previous = (int) (((long) getPreviousX(outputData) + getPreviousY(outputData)) / 2);
break;
default:
previous = getPreviousX(outputData);
break;
}
prev[componentIndex] = previous;
}
return decode0(prev, temp, index);
}
private int decode0(int[] prev, int[] temp, int[] index) throws IOException {
int value, actab[], dctab[]; int value, actab[], dctab[];
int qtab[]; int qtab[];
@ -694,14 +726,17 @@ final class JPEGLosslessDecoder {
if (numComp == 1) { if (numComp == 1) {
outputSingle(pred); outputSingle(pred);
} }
else { else if (numComp == 3) {
outputRGB(pred); outputRGB(pred);
} }
else {
outputAny(pred);
}
} }
private void outputSingle(final int pred[]) { private void outputSingle(final int pred[]) {
if ((xLoc < xDim) && (yLoc < yDim)) { if ((xLoc < xDim) && (yLoc < yDim)) {
outputData[(yLoc * xDim) + xLoc] = mask & pred[0]; outputData[0][(yLoc * xDim) + xLoc] = mask & pred[0];
xLoc++; xLoc++;
if (xLoc >= xDim) { if (xLoc >= xDim) {
@ -713,9 +748,25 @@ final class JPEGLosslessDecoder {
private void outputRGB(final int pred[]) { private void outputRGB(final int pred[]) {
if ((xLoc < xDim) && (yLoc < yDim)) { if ((xLoc < xDim) && (yLoc < yDim)) {
outputRedData[(yLoc * xDim) + xLoc] = pred[0]; final int index = (yLoc * xDim) + xLoc;
outputGreenData[(yLoc * xDim) + xLoc] = pred[1]; outputData[0][index] = pred[0];
outputBlueData[(yLoc * xDim) + xLoc] = pred[2]; outputData[1][index] = pred[1];
outputData[2][index] = pred[2];
xLoc++;
if (xLoc >= xDim) {
yLoc++;
xLoc = 0;
}
}
}
private void outputAny(final int pred[]) {
if ((xLoc < xDim) && (yLoc < yDim)) {
final int index = (yLoc * xDim) + xLoc;
for (int componentIndex = 0; componentIndex < outputData.length; ++componentIndex) {
outputData[componentIndex][index] = pred[componentIndex];
}
xLoc++; xLoc++;
if (xLoc >= xDim) { if (xLoc >= xDim) {

View File

@ -39,9 +39,12 @@ import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.ImageOutputStream; import javax.imageio.stream.ImageOutputStream;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.util.Arrays;
import static com.twelvemonkeys.lang.Validate.notNull; import static com.twelvemonkeys.lang.Validate.notNull;
import static java.lang.String.format;
import static java.nio.ByteOrder.BIG_ENDIAN; import static java.nio.ByteOrder.BIG_ENDIAN;
import static java.util.Arrays.asList;
/** /**
* TIFFStreamMetadata. * TIFFStreamMetadata.
@ -113,7 +116,7 @@ public final class TIFFStreamMetadata extends IIOMetadata {
} }
} }
private ByteOrder getByteOrder(final String value) throws IIOInvalidTreeException { private ByteOrder getByteOrder(final String value) {
switch (value) { switch (value) {
case "BIG_ENDIAN": case "BIG_ENDIAN":
return ByteOrder.BIG_ENDIAN; return ByteOrder.BIG_ENDIAN;
@ -138,6 +141,11 @@ public final class TIFFStreamMetadata extends IIOMetadata {
} }
else if (streamMetadata != null) { else if (streamMetadata != null) {
TIFFStreamMetadata metadata = new TIFFStreamMetadata(); TIFFStreamMetadata metadata = new TIFFStreamMetadata();
Validate.isTrue(asList(streamMetadata.getMetadataFormatNames()).contains(metadata.nativeMetadataFormatName),
format("Unsupported stream metadata format, expected %s: %s", metadata.nativeMetadataFormatName,
Arrays.toString(streamMetadata.getMetadataFormatNames())));
// Will throw exception if stream format differs from native // Will throw exception if stream format differs from native
metadata.mergeTree(metadata.nativeMetadataFormatName, streamMetadata.getAsTree(metadata.nativeMetadataFormatName)); metadata.mergeTree(metadata.nativeMetadataFormatName, streamMetadata.getAsTree(metadata.nativeMetadataFormatName));
imageOutput.setByteOrder(metadata.byteOrder); imageOutput.setByteOrder(metadata.byteOrder);

View File

@ -40,6 +40,7 @@ import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.ImageOutputStream; import javax.imageio.stream.ImageOutputStream;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import static com.twelvemonkeys.imageio.plugins.tiff.TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
import static com.twelvemonkeys.imageio.plugins.tiff.TIFFStreamMetadata.SUN_NATIVE_STREAM_METADATA_FORMAT_NAME; import static com.twelvemonkeys.imageio.plugins.tiff.TIFFStreamMetadata.SUN_NATIVE_STREAM_METADATA_FORMAT_NAME;
import static java.nio.ByteOrder.BIG_ENDIAN; import static java.nio.ByteOrder.BIG_ENDIAN;
import static java.nio.ByteOrder.LITTLE_ENDIAN; import static java.nio.ByteOrder.LITTLE_ENDIAN;
@ -85,9 +86,10 @@ public class TIFFStreamMetadataTest {
} }
@Test @Test
public void testConfigureStreamForegin() throws IIOInvalidTreeException { public void testConfigureStreamForeign() throws IIOInvalidTreeException {
ImageOutputStream stream = mock(ImageOutputStream.class); ImageOutputStream stream = mock(ImageOutputStream.class);
IIOMetadata metadata = mock(IIOMetadata.class); IIOMetadata metadata = mock(IIOMetadata.class);
when(metadata.getMetadataFormatNames()).thenReturn(new String[]{SUN_NATIVE_STREAM_METADATA_FORMAT_NAME, "com_foo_supertiff_9.42"});
when(metadata.getAsTree(eq(SUN_NATIVE_STREAM_METADATA_FORMAT_NAME))).thenReturn(createForeignTree(LITTLE_ENDIAN)); when(metadata.getAsTree(eq(SUN_NATIVE_STREAM_METADATA_FORMAT_NAME))).thenReturn(createForeignTree(LITTLE_ENDIAN));
TIFFStreamMetadata.configureStreamByteOrder(metadata, stream); TIFFStreamMetadata.configureStreamByteOrder(metadata, stream);
@ -95,6 +97,24 @@ public class TIFFStreamMetadataTest {
verify(stream, only()).setByteOrder(LITTLE_ENDIAN); verify(stream, only()).setByteOrder(LITTLE_ENDIAN);
} }
@Test
public void testConfigureStreamImageMetadata() throws IIOInvalidTreeException {
ImageOutputStream stream = mock(ImageOutputStream.class);
IIOMetadata metadata = mock(IIOMetadata.class);
when(metadata.getMetadataFormatNames()).thenReturn(new String[]{SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME});
try {
TIFFStreamMetadata.configureStreamByteOrder(metadata, stream);
fail("Expected IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertNotNull(expected.getMessage());
assertTrue(expected.getMessage().toLowerCase().contains("unsupported stream metadata format"));
assertTrue(expected.getMessage().contains("expected " + SUN_NATIVE_STREAM_METADATA_FORMAT_NAME));
assertTrue(expected.getMessage().contains(SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME));
}
}
private IIOMetadataNode createForeignTree(ByteOrder order) { private IIOMetadataNode createForeignTree(ByteOrder order) {
IIOMetadataNode root = new IIOMetadataNode(SUN_NATIVE_STREAM_METADATA_FORMAT_NAME); IIOMetadataNode root = new IIOMetadataNode(SUN_NATIVE_STREAM_METADATA_FORMAT_NAME);
IIOMetadataNode byteOrder = new IIOMetadataNode("ByteOrder"); IIOMetadataNode byteOrder = new IIOMetadataNode("ByteOrder");