From 59b91918e0be0ed667d40bb9cf28767d0890a7b3 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Tue, 29 Jan 2013 21:01:46 +0100 Subject: [PATCH 01/58] TMI-TIFF: Fixed minor bug in type spec for ARGB images + implemented support for "old-style" (reversed) LZW compression from libtiff. --- .../imageio/plugins/tiff/LZWDecoder.java | 127 +++++++++++------- .../imageio/plugins/tiff/TIFFImageReader.java | 5 +- .../imageio/plugins/tiff/LZWDecoderTest.java | 10 -- .../plugins/tiff/TIFFImageReaderTest.java | 1 + .../test/resources/lzw/lzw-short-45-49.bin | Bin 507 -> 0 bytes .../resources/lzw/unpacked-short-45-49.bin | Bin 7680 -> 0 bytes .../src/test/resources/tiff/quad-lzw.tif | Bin 0 -> 214342 bytes 7 files changed, 79 insertions(+), 64 deletions(-) delete mode 100755 imageio/imageio-tiff/src/test/resources/lzw/lzw-short-45-49.bin delete mode 100644 imageio/imageio-tiff/src/test/resources/lzw/unpacked-short-45-49.bin create mode 100644 imageio/imageio-tiff/src/test/resources/tiff/quad-lzw.tif diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java index a8cf4617..9a86946b 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java @@ -43,6 +43,8 @@ import java.util.Arrays; * @version $Id: LZWDecoder.java,v 1.0 08.05.12 21:11 haraldk Exp$ */ final class LZWDecoder implements Decoder { + // TODO: Break out compatibility handling to subclass, to avoid code branching? + /** Clear: Re-initialize tables. */ static final int CLEAR_CODE = 256; /** End of Information. */ @@ -53,17 +55,16 @@ final class LZWDecoder implements Decoder { private final boolean reverseBitOrder; - private int currentByte = -1; - private int bitPos; - // TODO: Consider speeding things up with a "string" type (instead of the inner byte[]), // that uses variable size/dynamic allocation, to avoid the excessive array copying? // private final byte[][] table = new byte[4096][0]; // libTiff adds another 1024 "for compatibility"... private final byte[][] table = new byte[4096 + 1024][0]; // libTiff adds another 1024 "for compatibility"... +// private final Entry[] tableToo = new Entry[4096 + 1024]; private int tableLength; private int bitsPerCode; private int oldCode = CLEAR_CODE; private int maxCode; + private int bitMask; private int maxString; private boolean eofReached; @@ -74,6 +75,10 @@ final class LZWDecoder implements Decoder { table[i] = new byte[] {(byte) i}; } +// for (int i = 0; i < 256; i++) { +// tableToo[i] = new Entry((byte) i); +// } +// init(); } @@ -81,14 +86,15 @@ final class LZWDecoder implements Decoder { this(false); } - private int maxCodeFor(final int bits) { - return reverseBitOrder ? (1 << bits) - 2 : (1 << bits) - 1; + private static int maxCodeFor(final int bits) { + return (1 << bits) - 1; } private void init() { tableLength = 258; bitsPerCode = MIN_BITS; - maxCode = maxCodeFor(bitsPerCode); + bitMask = maxCodeFor(bitsPerCode); + maxCode = reverseBitOrder ? bitMask : bitMask - 1; maxString = 1; } @@ -151,7 +157,7 @@ final class LZWDecoder implements Decoder { private void addStringToTable(final byte[] string) throws IOException { table[tableLength++] = string; - if (tableLength >= maxCode) { + if (tableLength > maxCode) { bitsPerCode++; if (bitsPerCode > MAX_BITS) { @@ -163,7 +169,8 @@ final class LZWDecoder implements Decoder { } } - maxCode = maxCodeFor(bitsPerCode); + bitMask = maxCodeFor(bitsPerCode); + maxCode = reverseBitOrder ? bitMask : bitMask - 1; } if (string.length > maxString) { @@ -191,68 +198,65 @@ final class LZWDecoder implements Decoder { return code < tableLength; } + + int nextData, nextBits; + private int getNextCode(final InputStream stream) throws IOException { if (eofReached) { return EOI_CODE; } - int bitsToFill = bitsPerCode; - int value = 0; + int code; + int read = stream.read(); + if (read < 0) { + eofReached = true; + return EOI_CODE; + } - while (bitsToFill > 0) { - int nextBits; - if (bitPos == 0) { - nextBits = stream.read(); + if (reverseBitOrder) { + // NOTE: This is a spec violation. However, libTiff reads such files. + // TIFF 6.0 Specification, Section 13: "LZW Compression"/"The Algorithm", page 61, says: + // "LZW compression codes are stored into bytes in high-to-low-order fashion, i.e., FillOrder + // is assumed to be 1. The compressed codes are written as bytes (not words) so that the + // compressed data will be identical whether it is an ‘II’ or ‘MM’ file." + nextData |= read << nextBits; + nextBits += 8; - if (nextBits == -1) { - // This is really a bad stream, but should be safe to handle this way, rather than throwing an EOFException. - // An EOFException will be thrown by the decoder stream later, if further reading is attempted. + if (nextBits < bitsPerCode) { + read = stream.read(); + if (read < 0) { eofReached = true; return EOI_CODE; } - } - else { - nextBits = currentByte; + + nextData |= read << nextBits; + nextBits += 8; } - int bitsFromHere = 8 - bitPos; - if (bitsFromHere > bitsToFill) { - bitsFromHere = bitsToFill; - } + code = (nextData & bitMask); + nextData >>= bitsPerCode; + nextBits -= bitsPerCode; + } + else { + nextData = (nextData << 8) | read; + nextBits += 8; - if (reverseBitOrder) { - // NOTE: This is a spec violation. However, libTiff reads such files. - // TIFF 6.0 Specification, Section 13: "LZW Compression"/"The Algorithm", page 61, says: - // "LZW compression codes are stored into bytes in high-to-low-order fashion, i.e., FillOrder - // is assumed to be 1. The compressed codes are written as bytes (not words) so that the - // compressed data will be identical whether it is an ‘II’ or ‘MM’ file." - - // Fill bytes from right-to-left - for (int i = 0; i < bitsFromHere; i++) { - int destBitPos = bitsPerCode - bitsToFill + i; - int srcBitPos = bitPos + i; - value |= ((nextBits & (1 << srcBitPos)) >> srcBitPos) << destBitPos; + if (nextBits < bitsPerCode) { + read = stream.read(); + if (read < 0) { + eofReached = true; + return EOI_CODE; } - } - else { - value |= (nextBits >> 8 - bitPos - bitsFromHere & 0xff >> 8 - bitsFromHere) << bitsToFill - bitsFromHere; + + nextData = (nextData << 8) | read; + nextBits += 8; } - bitsToFill -= bitsFromHere; - bitPos += bitsFromHere; - - if (bitPos >= 8) { - bitPos = 0; - } - - currentByte = nextBits; + code = ((nextData >> (nextBits - bitsPerCode)) & bitMask); + nextBits -= bitsPerCode; } - if (value == EOI_CODE) { - eofReached = true; - } - - return value; + return code; } static boolean isOldBitReversedStream(final InputStream stream) throws IOException { @@ -267,5 +271,24 @@ final class LZWDecoder implements Decoder { stream.reset(); } } + + private class Entry { + final Entry next; + + final int length; + final byte value; + final byte firstChar; + + public Entry(byte code) { + this(code, code, 1, null); + } + + public Entry(byte value, byte firstChar, int length, Entry next) { + this.length = length; + this.value = value; + this.firstChar = firstChar; + this.next = next; + } + } } diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java index d45d73e5..b35d847f 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java @@ -109,6 +109,7 @@ public class TIFFImageReader extends ImageReaderBase { // TODO: Implement readAsRenderedImage to allow tiled renderImage? // For some layouts, we could do reads super-fast with a memory mapped buffer. // TODO: Implement readAsRaster directly + // TODO: IIOMetadata // TODOs Full BaseLine support: // TODO: Support ExtraSamples (an array, if multiple extra samples!) @@ -282,7 +283,7 @@ public class TIFFImageReader extends ImageReaderBase { return ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR); } - return ImageTypeSpecifier.createInterleaved(cs, new int[] {0, 1, 2, 3}, dataType, false, false); + return ImageTypeSpecifier.createInterleaved(cs, new int[] {0, 1, 2, 3}, dataType, true, false); case TIFFExtension.PLANARCONFIG_PLANAR: return ImageTypeSpecifier.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, dataType, false, false); @@ -704,7 +705,7 @@ public class TIFFImageReader extends ImageReaderBase { // } // catch (IOException e) { // Arrays.fill(rowData, k, rowData.length, (byte) -1); -// System.err.printf("Unexpected EOF or bad data at [%d %d]\n", col + k, row); +// System.err.printf("Unexpected EOF or bad data at [%d, %d]\n", col + k, row); // break; // } // } diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoderTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoderTest.java index e2d8dc62..b4a82787 100644 --- a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoderTest.java +++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoderTest.java @@ -30,7 +30,6 @@ import com.twelvemonkeys.io.enc.Decoder; import com.twelvemonkeys.io.enc.DecoderAbstractTestCase; import com.twelvemonkeys.io.enc.DecoderStream; import com.twelvemonkeys.io.enc.Encoder; -import org.junit.Ignore; import org.junit.Test; import java.io.ByteArrayInputStream; @@ -66,15 +65,6 @@ public class LZWDecoderTest extends DecoderAbstractTestCase { assertSameStreamContents(unpacked, stream); } - @Ignore("Known issue") - @Test - public void testShortBitReversedStreamLine45To49() throws IOException { - InputStream stream = new DecoderStream(getClass().getResourceAsStream("/lzw/lzw-short-45-49.bin"), new LZWDecoder(true), 128); - InputStream unpacked = getClass().getResourceAsStream("/lzw/unpacked-short-45-49.bin"); - - assertSameStreamContents(unpacked, stream); - } - @Test public void testLongStream() throws IOException { InputStream stream = new DecoderStream(getClass().getResourceAsStream("/lzw/lzw-long.bin"), new LZWDecoder(), 1024); diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java index ced889d5..8200accd 100644 --- a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java +++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java @@ -52,6 +52,7 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTestCaseoJq5$&6_xL>fFh*r_Y~2g9;r=w5ZXeNRujE%CxD|r%w&=o}Y5Zy&|A-%1{ zHWS+tC4O8Ldg90*u3ozqf%EH&rGI2D{X>(M4cj>)y?~x9{J;gMa)m*qnD+;J?)??mTaws^pMLzc=;Dho#wg>AG}dS% zQ5pR3#|dEklEWbX^kB$8KqT@H5sWbB$a4%%ah(;HX`z`H+`VN6U22%;hIw;zQkR!} zc=?BygLr9(my37_iFuXKl?h#(ymjM581(Q54S)QDgCIc;8HABVB$>pLP&_yVby!sL x4;Pw&kro+jq45?Qbj3lJ9p>rbr5|1fVkUg%Ibt3qbYb$=CTz{A>Z+_R6%e{w0cQXJ diff --git a/imageio/imageio-tiff/src/test/resources/lzw/unpacked-short-45-49.bin b/imageio/imageio-tiff/src/test/resources/lzw/unpacked-short-45-49.bin deleted file mode 100644 index 9fb859fe612b3ba85232e6c42f2e2c825ec1a7ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7680 zcmeIz&1#cD6o>I%fg3lvicm_WxDpq3QQ}9Ui3Sk^^%EOQ2qBbU3^tHThNc*?X+xU4 z$@|WnnQO_VJzD8SOy$fK{GZ=q&f1zj6;J^cPyrQC0ToaI6;J^cPyrRV#sc~Eo2GQv zl()=_d#lbfvM<;Mu7ztOU8IK`AqE*DzmPF*id|rBeq$rrZ1a5cGODz-`dVbUM_MOq zVh?Z~+#%9O2FNk)C+-wC!OqFjn2VyN)?q530@q8xJk6Oxj&XtW64MIvHzd0(-;?cO z_i+uRg|zWqd=GzwAK-`h5&jgJ;u36XURL6%&fmFVp5#`I`;3d6m$`VwtV;5hY}+>Y(??d>Jk-Df&W0jX6zPTt1^xgJcm!qu diff --git a/imageio/imageio-tiff/src/test/resources/tiff/quad-lzw.tif b/imageio/imageio-tiff/src/test/resources/tiff/quad-lzw.tif new file mode 100644 index 0000000000000000000000000000000000000000..ec614bf6fbc3e595fb2e6b55064716c5ae379e1a GIT binary patch literal 214342 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 z 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%0A#Y?BK-vnUA~ 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 Dj?60$W|Cg4jYm`b6<}3sSO{DwvnDpXKAPlA8 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 literal 0 HcmV?d00001 From dd849aeea6e824546c23a7a09f6496ca158d2138 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Tue, 29 Jan 2013 21:24:51 +0100 Subject: [PATCH 02/58] TMI-TIFF: Now uses subclasses instead of if-branching for LZW compatibility decoding. --- .../imageio/plugins/tiff/LZWDecoder.java | 186 ++++++++++-------- .../imageio/plugins/tiff/TIFFImageReader.java | 2 +- .../imageio/plugins/tiff/LZWDecoderTest.java | 6 +- 3 files changed, 112 insertions(+), 82 deletions(-) diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java index 9a86946b..8a24ae5a 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java @@ -42,9 +42,7 @@ import java.util.Arrays; * @author last modified by $Author: haraldk$ * @version $Id: LZWDecoder.java,v 1.0 08.05.12 21:11 haraldk Exp$ */ -final class LZWDecoder implements Decoder { - // TODO: Break out compatibility handling to subclass, to avoid code branching? - +abstract class LZWDecoder implements Decoder { /** Clear: Re-initialize tables. */ static final int CLEAR_CODE = 256; /** End of Information. */ @@ -53,23 +51,25 @@ final class LZWDecoder implements Decoder { private static final int MIN_BITS = 9; private static final int MAX_BITS = 12; - private final boolean reverseBitOrder; + private final boolean compatibilityMode; - // TODO: Consider speeding things up with a "string" type (instead of the inner byte[]), - // that uses variable size/dynamic allocation, to avoid the excessive array copying? -// private final byte[][] table = new byte[4096][0]; // libTiff adds another 1024 "for compatibility"... - private final byte[][] table = new byte[4096 + 1024][0]; // libTiff adds another 1024 "for compatibility"... + private final byte[][] table; // private final Entry[] tableToo = new Entry[4096 + 1024]; private int tableLength; - private int bitsPerCode; + int bitsPerCode; private int oldCode = CLEAR_CODE; private int maxCode; - private int bitMask; + int bitMask; private int maxString; - private boolean eofReached; + boolean eofReached; + int nextData; + int nextBits; - LZWDecoder(final boolean reverseBitOrder) { - this.reverseBitOrder = reverseBitOrder; + + protected LZWDecoder(final boolean compatibilityMode) { + this.compatibilityMode = compatibilityMode; + + table = new byte[compatibilityMode ? 4096 + 1024 : 4096][0]; // libTiff adds another 1024 "for compatibility"... for (int i = 0; i < 256; i++) { table[i] = new byte[] {(byte) i}; @@ -82,19 +82,15 @@ final class LZWDecoder implements Decoder { init(); } - LZWDecoder() { - this(false); - } - - private static int maxCodeFor(final int bits) { + private static int bitmaskFor(final int bits) { return (1 << bits) - 1; } private void init() { tableLength = 258; bitsPerCode = MIN_BITS; - bitMask = maxCodeFor(bitsPerCode); - maxCode = reverseBitOrder ? bitMask : bitMask - 1; + bitMask = bitmaskFor(bitsPerCode); + maxCode = maxCode(); maxString = 1; } @@ -116,14 +112,6 @@ final class LZWDecoder implements Decoder { bufferPos += writeString(table[code], buffer, bufferPos); } else { - if (code > tableLength + 1 || oldCode >= tableLength) { - // TODO: FixMe for old, borked streams - System.err.println("code: " + code); - System.err.println("oldCode: " + oldCode); - System.err.println("tableLength: " + tableLength); - throw new DecodeException("Corrupted LZW table"); - } - if (isInTable(code)) { bufferPos += writeString(table[code], buffer, bufferPos); addStringToTable(concatenate(table[oldCode], table[code][0])); @@ -161,7 +149,7 @@ final class LZWDecoder implements Decoder { bitsPerCode++; if (bitsPerCode > MAX_BITS) { - if (reverseBitOrder) { + if (compatibilityMode) { bitsPerCode--; } else { @@ -169,8 +157,8 @@ final class LZWDecoder implements Decoder { } } - bitMask = maxCodeFor(bitsPerCode); - maxCode = reverseBitOrder ? bitMask : bitMask - 1; + bitMask = bitmaskFor(bitsPerCode); + maxCode = maxCode(); } if (string.length > maxString) { @@ -178,6 +166,8 @@ final class LZWDecoder implements Decoder { } } + protected abstract int maxCode(); + private static int writeString(final byte[] string, final byte[] buffer, final int bufferPos) { if (string.length == 0) { return 0; @@ -198,27 +188,98 @@ final class LZWDecoder implements Decoder { return code < tableLength; } + protected abstract int getNextCode(final InputStream stream) throws IOException; - int nextData, nextBits; - private int getNextCode(final InputStream stream) throws IOException { - if (eofReached) { - return EOI_CODE; + static boolean isOldBitReversedStream(final InputStream stream) throws IOException { + stream.mark(2); + try { + int one = stream.read(); + int two = stream.read(); + + return one == 0 && (two & 0x1) == 1; // => (reversed) 1 00000000 == 256 (CLEAR_CODE) + } + finally { + stream.reset(); + } + } + + public static LZWDecoder create(boolean oldBitReversedStream) { + return oldBitReversedStream ? new LZWCompatibilityDecoder() : new LZWSpecDecoder(); + } + + private static final class LZWSpecDecoder extends LZWDecoder { + + protected LZWSpecDecoder() { + super(false); } - int code; - int read = stream.read(); - if (read < 0) { - eofReached = true; - return EOI_CODE; + @Override + protected int maxCode() { + return bitMask - 1; } - if (reverseBitOrder) { - // NOTE: This is a spec violation. However, libTiff reads such files. - // TIFF 6.0 Specification, Section 13: "LZW Compression"/"The Algorithm", page 61, says: - // "LZW compression codes are stored into bytes in high-to-low-order fashion, i.e., FillOrder - // is assumed to be 1. The compressed codes are written as bytes (not words) so that the - // compressed data will be identical whether it is an ‘II’ or ‘MM’ file." + protected final int getNextCode(final InputStream stream) throws IOException { + if (eofReached) { + return EOI_CODE; + } + + int code; + int read = stream.read(); + if (read < 0) { + eofReached = true; + return EOI_CODE; + } + + nextData = (nextData << 8) | read; + nextBits += 8; + + if (nextBits < bitsPerCode) { + read = stream.read(); + if (read < 0) { + eofReached = true; + return EOI_CODE; + } + + nextData = (nextData << 8) | read; + nextBits += 8; + } + + code = ((nextData >> (nextBits - bitsPerCode)) & bitMask); + nextBits -= bitsPerCode; + + return code; + } + } + + private static final class LZWCompatibilityDecoder extends LZWDecoder { + // NOTE: This is a spec violation. However, libTiff reads such files. + // TIFF 6.0 Specification, Section 13: "LZW Compression"/"The Algorithm", page 61, says: + // "LZW compression codes are stored into bytes in high-to-low-order fashion, i.e., FillOrder + // is assumed to be 1. The compressed codes are written as bytes (not words) so that the + // compressed data will be identical whether it is an ‘II’ or ‘MM’ file." + + protected LZWCompatibilityDecoder() { + super(true); + } + + @Override + protected int maxCode() { + return bitMask; + } + + protected final int getNextCode(final InputStream stream) throws IOException { + if (eofReached) { + return EOI_CODE; + } + + int code; + int read = stream.read(); + if (read < 0) { + eofReached = true; + return EOI_CODE; + } + nextData |= read << nextBits; nextBits += 8; @@ -236,39 +297,8 @@ final class LZWDecoder implements Decoder { code = (nextData & bitMask); nextData >>= bitsPerCode; nextBits -= bitsPerCode; - } - else { - nextData = (nextData << 8) | read; - nextBits += 8; - if (nextBits < bitsPerCode) { - read = stream.read(); - if (read < 0) { - eofReached = true; - return EOI_CODE; - } - - nextData = (nextData << 8) | read; - nextBits += 8; - } - - code = ((nextData >> (nextBits - bitsPerCode)) & bitMask); - nextBits -= bitsPerCode; - } - - return code; - } - - static boolean isOldBitReversedStream(final InputStream stream) throws IOException { - stream.mark(2); - try { - int one = stream.read(); - int two = stream.read(); - - return one == 0 && (two & 0x1) == 1; // => (reversed) 1 00000000 == 256 (CLEAR_CODE) - } - finally { - stream.reset(); + return code; } } diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java index b35d847f..39389c72 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java @@ -864,7 +864,7 @@ public class TIFFImageReader extends ImageReaderBase { case TIFFBaseline.COMPRESSION_PACKBITS: return new DecoderStream(stream, new PackBitsDecoder(), 1024); case TIFFExtension.COMPRESSION_LZW: - return new DecoderStream(stream, new LZWDecoder(LZWDecoder.isOldBitReversedStream(stream)), 1024); + return new DecoderStream(stream, LZWDecoder.create(LZWDecoder.isOldBitReversedStream(stream)), 1024); case TIFFExtension.COMPRESSION_ZLIB: case TIFFExtension.COMPRESSION_DEFLATE: // TIFFphotoshop.pdf (aka TIFF specification, supplement 2) says ZLIB (8) and DEFLATE (32946) algorithms are identical diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoderTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoderTest.java index b4a82787..f826ec82 100644 --- a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoderTest.java +++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoderTest.java @@ -59,7 +59,7 @@ public class LZWDecoderTest extends DecoderAbstractTestCase { @Test public void testShortBitReversedStream() throws IOException { - InputStream stream = new DecoderStream(getClass().getResourceAsStream("/lzw/lzw-short.bin"), new LZWDecoder(true), 128); + InputStream stream = new DecoderStream(getClass().getResourceAsStream("/lzw/lzw-short.bin"), LZWDecoder.create(true), 128); InputStream unpacked = new ByteArrayInputStream(new byte[512 * 3 * 5]); // Should be all 0's assertSameStreamContents(unpacked, stream); @@ -67,7 +67,7 @@ public class LZWDecoderTest extends DecoderAbstractTestCase { @Test public void testLongStream() throws IOException { - InputStream stream = new DecoderStream(getClass().getResourceAsStream("/lzw/lzw-long.bin"), new LZWDecoder(), 1024); + InputStream stream = new DecoderStream(getClass().getResourceAsStream("/lzw/lzw-long.bin"), LZWDecoder.create(false), 1024); InputStream unpacked = getClass().getResourceAsStream("/lzw/unpacked-long.bin"); assertSameStreamContents(unpacked, stream); @@ -101,7 +101,7 @@ public class LZWDecoderTest extends DecoderAbstractTestCase { @Override public Decoder createDecoder() { - return new LZWDecoder(); + return LZWDecoder.create(false); } @Override From e68b3aa9e3f17bf5af5133410eafc5ebd8b887ea Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Tue, 29 Jan 2013 22:26:11 +0100 Subject: [PATCH 03/58] TMI-TIFF: Now uses String class instead of byte[], to avoid excessive array concatenation and copying. --- .../imageio/plugins/tiff/LZWDecoder.java | 88 +++++++++---------- 1 file changed, 42 insertions(+), 46 deletions(-) diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java index 8a24ae5a..c8152b91 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java @@ -33,7 +33,6 @@ import com.twelvemonkeys.io.enc.Decoder; import java.io.IOException; import java.io.InputStream; -import java.util.Arrays; /** * LZWDecoder @@ -53,8 +52,7 @@ abstract class LZWDecoder implements Decoder { private final boolean compatibilityMode; - private final byte[][] table; -// private final Entry[] tableToo = new Entry[4096 + 1024]; + private final String[] table; private int tableLength; int bitsPerCode; private int oldCode = CLEAR_CODE; @@ -69,16 +67,12 @@ abstract class LZWDecoder implements Decoder { protected LZWDecoder(final boolean compatibilityMode) { this.compatibilityMode = compatibilityMode; - table = new byte[compatibilityMode ? 4096 + 1024 : 4096][0]; // libTiff adds another 1024 "for compatibility"... + table = new String[compatibilityMode ? 4096 + 1024 : 4096]; // libTiff adds another 1024 "for compatibility"... for (int i = 0; i < 256; i++) { - table[i] = new byte[] {(byte) i}; + table[i] = new String((byte) i); } -// for (int i = 0; i < 256; i++) { -// tableToo[i] = new Entry((byte) i); -// } -// init(); } @@ -109,17 +103,17 @@ abstract class LZWDecoder implements Decoder { break; } - bufferPos += writeString(table[code], buffer, bufferPos); + bufferPos += table[code].writeTo(buffer, bufferPos); } else { if (isInTable(code)) { - bufferPos += writeString(table[code], buffer, bufferPos); - addStringToTable(concatenate(table[oldCode], table[code][0])); + bufferPos += table[code].writeTo(buffer, bufferPos); + addStringToTable(table[oldCode].concatenate(table[code].firstChar)); } else { - byte[] outString = concatenate(table[oldCode], table[oldCode][0]); + String outString = table[oldCode].concatenate(table[oldCode].firstChar); - bufferPos += writeString(outString, buffer, bufferPos); + bufferPos += outString.writeTo(buffer, bufferPos); addStringToTable(outString); } } @@ -135,14 +129,7 @@ abstract class LZWDecoder implements Decoder { return bufferPos; } - private static byte[] concatenate(final byte[] string, final byte firstChar) { - byte[] result = Arrays.copyOf(string, string.length + 1); - result[string.length] = firstChar; - - return result; - } - - private void addStringToTable(final byte[] string) throws IOException { + private void addStringToTable(final String string) throws IOException { table[tableLength++] = string; if (tableLength > maxCode) { @@ -153,7 +140,7 @@ abstract class LZWDecoder implements Decoder { bitsPerCode--; } else { - throw new DecodeException(String.format("TIFF LZW with more than %d bits per code encountered (table overflow)", MAX_BITS)); + throw new DecodeException(java.lang.String.format("TIFF LZW with more than %d bits per code encountered (table overflow)", MAX_BITS)); } } @@ -168,22 +155,6 @@ abstract class LZWDecoder implements Decoder { protected abstract int maxCode(); - private static int writeString(final byte[] string, final byte[] buffer, final int bufferPos) { - if (string.length == 0) { - return 0; - } - else if (string.length == 1) { - buffer[bufferPos] = string[0]; - - return 1; - } - else { - System.arraycopy(string, 0, buffer, bufferPos, string.length); - - return string.length; - } - } - private boolean isInTable(int code) { return code < tableLength; } @@ -302,22 +273,47 @@ abstract class LZWDecoder implements Decoder { } } - private class Entry { - final Entry next; + private static final class String { + final String previous; final int length; final byte value; - final byte firstChar; + final byte firstChar; // Copied forward for fast access - public Entry(byte code) { + public String(final byte code) { this(code, code, 1, null); } - public Entry(byte value, byte firstChar, int length, Entry next) { - this.length = length; + private String(final byte value, final byte firstChar, final int length, final String previous) { this.value = value; this.firstChar = firstChar; - this.next = next; + this.length = length; + this.previous = previous; + } + + public final String concatenate(final byte firstChar) { + return new String(firstChar, this.firstChar, length + 1, this); + } + + public final int writeTo(final byte[] buffer, final int offset) { + if (length == 0) { + return 0; + } + else if (length == 1) { + buffer[offset] = value; + + return 1; + } + else { + String e = this; + + for (int i = length - 1; i >= 0; i--) { + buffer[offset + i] = e.value; + e = e.previous; + } + + return length; + } } } } From 8c4f9d3ed6183b48498d13002f90e690c67cf30b Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Thu, 31 Jan 2013 14:49:27 +0100 Subject: [PATCH 04/58] TMI-XXX: More leniency --- .../java/com/twelvemonkeys/io/InputStreamAbstractTestCase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/InputStreamAbstractTestCase.java b/common/common-io/src/test/java/com/twelvemonkeys/io/InputStreamAbstractTestCase.java index 22f5defa..9f21a06d 100755 --- a/common/common-io/src/test/java/com/twelvemonkeys/io/InputStreamAbstractTestCase.java +++ b/common/common-io/src/test/java/com/twelvemonkeys/io/InputStreamAbstractTestCase.java @@ -167,7 +167,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase input.mark(100); // Should be a no-op int read = input.read(); - assertEquals(0, read); + assertTrue(read >= 0); // TODO: According to InputStream#reset, it is allowed to do some // implementation specific reset, and still be correct... From 47fbf473dbdebceefe23ca1e6dbe84c7a04cd2b7 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Thu, 31 Jan 2013 15:38:45 +0100 Subject: [PATCH 05/58] TMI-TIFF: Implemented YCbCr reading. --- .../imageio/plugins/tiff/LZWDecoder.java | 6 +- .../imageio/plugins/tiff/TIFFImageReader.java | 95 ++++- .../plugins/tiff/YCbCrUpsamplerStream.java | 336 ++++++++++++++++++ .../tiff/YCbCrUpsamplerStreamTest.java | 51 +++ 4 files changed, 467 insertions(+), 21 deletions(-) create mode 100644 imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java create mode 100644 imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStreamTest.java diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java index c8152b91..0d76ab1f 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java @@ -35,7 +35,8 @@ import java.io.IOException; import java.io.InputStream; /** - * LZWDecoder + * Implements Lempel-Ziv & Welch (LZW) decompression. + * Inspired by libTiff's LZW decompression. * * @author Harald Kuhr * @author last modified by $Author: haraldk$ @@ -67,8 +68,9 @@ abstract class LZWDecoder implements Decoder { protected LZWDecoder(final boolean compatibilityMode) { this.compatibilityMode = compatibilityMode; - table = new String[compatibilityMode ? 4096 + 1024 : 4096]; // libTiff adds another 1024 "for compatibility"... + table = new String[compatibilityMode ? 4096 + 1024 : 4096]; // libTiff adds 1024 "for compatibility"... + // First 258 entries of table is always fixed for (int i = 0; i < 256; i++) { table[i] = new String((byte) i); } diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java index 39389c72..996f6a3d 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java @@ -35,6 +35,7 @@ import com.twelvemonkeys.imageio.metadata.CompoundDirectory; import com.twelvemonkeys.imageio.metadata.Directory; import com.twelvemonkeys.imageio.metadata.Entry; import com.twelvemonkeys.imageio.metadata.exif.EXIFReader; +import com.twelvemonkeys.imageio.metadata.exif.Rational; import com.twelvemonkeys.imageio.metadata.exif.TIFF; import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream; import com.twelvemonkeys.imageio.stream.SubImageInputStream; @@ -129,6 +130,9 @@ public class TIFFImageReader extends ImageReaderBase { private 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 Directory currentIFD; @@ -217,7 +221,7 @@ public class TIFFImageReader extends ImageReaderBase { getSampleFormat(); // We don't support anything but SAMPLEFORMAT_UINT at the moment, just sanity checking input int planarConfiguration = getValueAsIntWithDefault(TIFF.TAG_PLANAR_CONFIGURATION, TIFFExtension.PLANARCONFIG_PLANAR); int interpretation = getValueAsInt(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, "PhotometricInterpretation"); - int samplesPerPixel = getValueAsIntWithDefault(TIFF.TAG_SAMPLES_PER_PIXELS, 1); + int samplesPerPixel = getValueAsIntWithDefault(TIFF.TAG_SAMPLES_PER_PIXEL, 1); int bitsPerSample = getBitsPerSample(); int dataType = bitsPerSample <= 8 ? DataBuffer.TYPE_BYTE : bitsPerSample <= 16 ? DataBuffer.TYPE_USHORT : DataBuffer.TYPE_INT; @@ -254,6 +258,7 @@ public class TIFFImageReader extends ImageReaderBase { case TIFFExtension.PHOTOMETRIC_YCBCR: // JPEG reader will handle YCbCr to RGB for us, we'll have to do it ourselves if not 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: // RGB @@ -427,6 +432,10 @@ public class TIFFImageReader extends ImageReaderBase { public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException { readIFD(imageIndex); + System.err.println("currentIFD.getEntryById(TIFF.TAG_REFERENCE_BLACK_WHITE): " + currentIFD.getEntryById(TIFF.TAG_REFERENCE_BLACK_WHITE)); + System.err.println("currentIFD.getEntryById(TIFF.TAG_TRANSFER_FUNCTION): " + currentIFD.getEntryById(TIFF.TAG_TRANSFER_FUNCTION)); + System.err.println("currentIFD.getEntryById(TIFF.TAG_TRANSFER_RANGE): " + currentIFD.getEntryById(TIFF.TAG_TRANSFER_RANGE)); + int width = getWidth(imageIndex); int height = getHeight(imageIndex); @@ -496,8 +505,53 @@ public class TIFFImageReader extends ImageReaderBase { case TIFFExtension.COMPRESSION_ZLIB: // 'Adobe-style' Deflate - // TODO: Read only tiles that lies within region + int[] yCbCrSubsampling = null; + int yCbCrPos = 1; + double[] yCbCrCoefficients = null; + if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR) { + // getRawImageType does the lookup/conversion for these + if (raster.getNumBands() != 3) { + throw new IIOException("TIFF PhotometricInterpreatation YCbCr requires SamplesPerPixel == 3: " + raster.getNumBands()); + } + if (raster.getTransferType() != DataBuffer.TYPE_BYTE) { + throw new IIOException("TIFF PhotometricInterpreatation YCbCr requires BitsPerSample == [8,8,8]"); + } + yCbCrPos = getValueAsIntWithDefault(TIFF.TAG_YCBCR_POSITIONING, 1); + + Entry subSampling = currentIFD.getEntryById(TIFF.TAG_YCBCR_SUB_SAMPLING); + + if (subSampling != null) { + try { + yCbCrSubsampling = (int[]) subSampling.getValue(); + } + catch (ClassCastException e) { + throw new IIOException("Unknown TIFF YCbCrSubSampling value type: " + subSampling.getTypeName(), e); + } + + if (yCbCrSubsampling.length != 2 || + yCbCrSubsampling[0] != 1 && yCbCrSubsampling[0] != 2 && yCbCrSubsampling[0] != 4 || + yCbCrSubsampling[1] != 1 && yCbCrSubsampling[1] != 2 && yCbCrSubsampling[1] != 4 || + yCbCrSubsampling[0] < yCbCrSubsampling[1]) { + throw new IIOException("Bad TIFF YCbCrSubSampling value: " + Arrays.toString(yCbCrSubsampling)); + } + } + else { + yCbCrSubsampling = new int[] {2, 2}; + } + + Entry coefficients = currentIFD.getEntryById(TIFF.TAG_YCBCR_COEFFICIENTS); + if (coefficients != null) { + Rational[] value = (Rational[]) coefficients.getValue(); + yCbCrCoefficients = new double[] {value[0].doubleValue(), value[1].doubleValue(), value[2].doubleValue()}; + } + else { + // Default to y CCIR Recommendation 601-1 values + yCbCrCoefficients = CCIR_601_1_COEFFICIENTS; + } + } + + // TODO: Read only tiles that lies within region // General uncompressed/compressed reading for (int y = 0; y < tilesDown; y++) { int col = 0; @@ -510,7 +564,8 @@ public class TIFFImageReader extends ImageReaderBase { imageInput.seek(stripTileOffsets[i]); DataInput input; - if (compression == TIFFBaseline.COMPRESSION_NONE) { + if (compression == TIFFBaseline.COMPRESSION_NONE && interpretation != TIFFExtension.PHOTOMETRIC_YCBCR) { + // No need for transformation, fast forward input = imageInput; } else { @@ -518,11 +573,16 @@ public class TIFFImageReader extends ImageReaderBase { ? IIOUtil.createStreamAdapter(imageInput, stripTileByteCounts[i]) : IIOUtil.createStreamAdapter(imageInput); + adapter = createDecoderInputStream(compression, adapter); + + if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR) { + adapter = new YCbCrUpsamplerStream(adapter, yCbCrSubsampling, colsInTile, yCbCrCoefficients); + } + // According to the spec, short/long/etc should follow order of containing stream input = imageInput.getByteOrder() == ByteOrder.BIG_ENDIAN - ? new DataInputStream(createDecoderInputStream(compression, adapter)) - : new LittleEndianDataInputStream(createDecoderInputStream(compression, adapter)); - + ? new DataInputStream(adapter) + : new LittleEndianDataInputStream(adapter); } // Read a full strip/tile @@ -566,6 +626,9 @@ public class TIFFImageReader extends ImageReaderBase { // Might have something to do with subsampling? // 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 + // compatible approach..? + jpegReader.setInput(new ByteArrayImageInputStream(tablesValue)); // NOTE: This initializes the tables AND MORE secret internal settings for the reader (as if by magic). @@ -690,6 +753,7 @@ public class TIFFImageReader extends ImageReaderBase { switch (rowRaster.getTransferType()) { case DataBuffer.TYPE_BYTE: byte[] rowData = ((DataBufferByte) rowRaster.getDataBuffer()).getData(); + for (int j = 0; j < rowsInStrip; j++) { int row = startRow + j; @@ -699,17 +763,6 @@ public class TIFFImageReader extends ImageReaderBase { input.readFully(rowData); -// for (int k = 0; k < rowData.length; k++) { -// try { -// rowData[k] = input.readByte(); -// } -// catch (IOException e) { -// Arrays.fill(rowData, k, rowData.length, (byte) -1); -// System.err.printf("Unexpected EOF or bad data at [%d, %d]\n", col + k, row); -// break; -// } -// } - unPredict(predictor, colsInStrip, 1, numBands, rowData); normalizeBlack(interpretation, rowData); @@ -725,6 +778,7 @@ public class TIFFImageReader extends ImageReaderBase { break; case DataBuffer.TYPE_USHORT: short [] rowDataShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData(); + for (int j = 0; j < rowsInStrip; j++) { int row = startRow + j; @@ -751,6 +805,7 @@ public class TIFFImageReader extends ImageReaderBase { break; case DataBuffer.TYPE_INT: int [] rowDataInt = ((DataBufferInt) rowRaster.getDataBuffer()).getData(); + for (int j = 0; j < rowsInStrip; j++) { int row = startRow + j; @@ -861,6 +916,8 @@ public class TIFFImageReader extends ImageReaderBase { private InputStream createDecoderInputStream(final int compression, final InputStream stream) throws IOException { switch (compression) { + case TIFFBaseline.COMPRESSION_NONE: + return stream; case TIFFBaseline.COMPRESSION_PACKBITS: return new DecoderStream(stream, new PackBitsDecoder(), 1024); case TIFFExtension.COMPRESSION_LZW: @@ -894,7 +951,7 @@ public class TIFFImageReader extends ImageReaderBase { short[] shorts = (short[]) entry.getValue(); value = new long[shorts.length]; - for (int i = 0, stripOffsetsValueLength = value.length; i < stripOffsetsValueLength; i++) { + for (int i = 0, length = value.length; i < length; i++) { value[i] = shorts[i]; } } @@ -902,7 +959,7 @@ public class TIFFImageReader extends ImageReaderBase { int[] ints = (int[]) entry.getValue(); value = new long[ints.length]; - for (int i = 0, stripOffsetsValueLength = value.length; i < stripOffsetsValueLength; i++) { + for (int i = 0, length = value.length; i < length; i++) { value[i] = ints[i]; } } diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java new file mode 100644 index 00000000..2a6c1051 --- /dev/null +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java @@ -0,0 +1,336 @@ +/* + * Copyright (c) 2013, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.tiff; + +import java.awt.image.DataBufferByte; +import java.awt.image.Raster; +import java.io.EOFException; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +/** + * YCbCrUpsamplerStream + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: YCbCrUpsamplerStream.java,v 1.0 31.01.13 09:25 haraldk Exp$ + */ +final class YCbCrUpsamplerStream extends FilterInputStream { + static final boolean DEBUG = false; + + private final int horizChromaSub; + private final int vertChromaSub; + private final double[] coefficients; + + private final int units; + private final int unitSize; + private final byte[] decodedRows; + int decodedLength; + int decodedPos; + + private final byte[] buffer; + int bufferLength; + int bufferPos; + + public YCbCrUpsamplerStream(InputStream stream, int[] chromaSub, int cols, double[] coefficients) { + super(stream); + + this.horizChromaSub = chromaSub[0]; + this.vertChromaSub = chromaSub[1]; + this.coefficients = Arrays.equals(TIFFImageReader.CCIR_601_1_COEFFICIENTS, coefficients) ? null : coefficients; + + // In TIFF, subsampled streams are stored in "units" of horiz * vert pixels. + // For a 4:2 subsampled stream like this: + // + // Y0 Y1 Y2 Y3 Cb0 Cr0 Y8 Y9 Y10 Y11 Cb1 Cr1 + // Y4 Y5 Y6 Y7 Y12Y13Y14 Y15 + // + // In the stream, the order is: Y0,Y1,Y2..Y7,Cb0,Cr0, Y8...Y15,Cb1,Cr1, Y16... + + units = cols / horizChromaSub; + unitSize = horizChromaSub * vertChromaSub + 2; + decodedRows = new byte[cols * vertChromaSub * 3]; + 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 { + if (bufferPos >= bufferLength) { + int pos = 0; + int read; + + // This *SHOULD* read an entire row of units into the buffer, otherwise decodeRows will throw EOFException + while (pos < buffer.length && (read = super.read(buffer, pos, buffer.length - pos)) > 0) { + pos += read; + } + + bufferLength = pos; + bufferPos = 0; + } + + if (bufferLength > 0) { + decodeRows(); + } + else { + decodedLength = -1; + } + } + + private void decodeRows() throws EOFException { + decodedLength = decodedRows.length; + + int rowOff = horizChromaSub * units; + + for (int u = 0; u < units; u++) { + if (bufferPos >= bufferLength) { + throw new EOFException("Unexpected end of stream"); + } + + // Decode one unit + byte cb = buffer[bufferPos + unitSize - 2]; + byte cr = buffer[bufferPos + unitSize - 1]; + + for (int y = 0; y < vertChromaSub; y++) { + for (int x = 0; x < horizChromaSub; x++) { + int pixelOff = 3 * (rowOff * y + horizChromaSub * u + x); + + decodedRows[pixelOff] = buffer[bufferPos++]; + decodedRows[pixelOff + 1] = cb; + decodedRows[pixelOff + 2] = cr; + + // Convert to RGB + if (coefficients == null) { + YCbCrConverter.convertYCbCr2RGB(decodedRows, decodedRows, pixelOff); + } + else { + convertYCbCr2RGB(decodedRows, decodedRows, coefficients, pixelOff); + } + } + } + + bufferPos+= 2; + } + + bufferPos = bufferLength; + decodedPos = 0; + } + + @Override + public final int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (decodedLength < 0) { + return -1; + } + + if (decodedPos >= decodedLength) { + fetch(); + + if (decodedLength < 0) { + return -1; + } + } + + int read = Math.min(decodedLength - decodedPos, len); + System.arraycopy(decodedRows, decodedPos, b, off, read); + decodedPos += read; + + return read; + } + + @Override + public long skip(long n) throws IOException { + if (decodedLength < 0) { + return -1; + } + + if (decodedPos >= decodedLength) { + fetch(); + + if (decodedLength < 0) { + return -1; + } + } + + int skipped = (int) Math.min(decodedLength - decodedPos, n); + decodedPos += skipped; + + return skipped; + } + + @Override + public boolean markSupported() { + return false; + } + + @Override + public synchronized void reset() throws IOException { + throw new IOException("mark/reset not supported"); + } + + private void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final double[] coefficients, final int offset) { + // TODO: FixMe: This is bogus... + double y = yCbCr[offset ] & 0xff; + double cb = yCbCr[offset + 1] & 0xff; + double cr = yCbCr[offset + 2] & 0xff; + + double lumaRed = coefficients[0]; + double lumaGreen = coefficients[1]; + double lumaBlue = coefficients[2]; + + rgb[offset ] = clamp((int) Math.round(cr * (2 - 2 * lumaRed) + y)); + rgb[offset + 2] = clamp((int) Math.round(cb * (2 - 2 * lumaBlue) + y) - 128); + rgb[offset + 1] = clamp((int) Math.round((y - lumaRed * (rgb[offset] & 0xff) - lumaBlue * (rgb[offset + 2] & 0xff)) / lumaGreen)); + } + + private static byte clamp(int val) { + return (byte) Math.max(0, Math.min(255, val)); + } + + // TODO: This code is copied from JPEG package, make it "more" public: com.tm.imageio.color package? + /** + * Static inner class for lazy-loading of conversion tables. + */ + static final class YCbCrConverter { + /** Define tables for YCC->RGB color space conversion. */ + private final static int SCALEBITS = 16; + private final static int MAXJSAMPLE = 255; + private final static int CENTERJSAMPLE = 128; + private final static int ONE_HALF = 1 << (SCALEBITS - 1); + + private final static int[] Cr_R_LUT = new int[MAXJSAMPLE + 1]; + private final static int[] Cb_B_LUT = new int[MAXJSAMPLE + 1]; + private final static int[] Cr_G_LUT = new int[MAXJSAMPLE + 1]; + private final static int[] Cb_G_LUT = new int[MAXJSAMPLE + 1]; + + /** + * Initializes tables for YCC->RGB color space conversion. + */ + private static void buildYCCtoRGBtable() { + if (DEBUG) { + System.err.println("Building YCC conversion table"); + } + + for (int i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) { + // i is the actual input pixel value, in the range 0..MAXJSAMPLE + // The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE + // Cr=>R value is nearest int to 1.40200 * x + Cr_R_LUT[i] = (int) ((1.40200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS; + // Cb=>B value is nearest int to 1.77200 * x + Cb_B_LUT[i] = (int) ((1.77200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS; + // Cr=>G value is scaled-up -0.71414 * x + Cr_G_LUT[i] = -(int) (0.71414 * (1 << SCALEBITS) + 0.5) * x; + // Cb=>G value is scaled-up -0.34414 * x + // We also add in ONE_HALF so that need not do it in inner loop + Cb_G_LUT[i] = -(int) ((0.34414) * (1 << SCALEBITS) + 0.5) * x + ONE_HALF; + } + } + + static { + buildYCCtoRGBtable(); + } + + static void convertYCbCr2RGB(final Raster raster) { + final int height = raster.getHeight(); + final int width = raster.getWidth(); + final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData(); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + convertYCbCr2RGB(data, data, (x + y * width) * 3); + } + } + } + + static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final int offset) { + int y = yCbCr[offset ] & 0xff; + int cr = yCbCr[offset + 2] & 0xff; + int cb = yCbCr[offset + 1] & 0xff; + + rgb[offset ] = clamp(y + Cr_R_LUT[cr]); + rgb[offset + 1] = clamp(y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS)); + rgb[offset + 2] = clamp(y + Cb_B_LUT[cb]); + } + + static void convertYCCK2CMYK(final Raster raster) { + final int height = raster.getHeight(); + final int width = raster.getWidth(); + final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData(); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + convertYCCK2CMYK(data, data, (x + y * width) * 4); + } + } + } + + private static void convertYCCK2CMYK(byte[] ycck, byte[] cmyk, int offset) { + // Inverted + int y = 255 - ycck[offset ] & 0xff; + int cb = 255 - ycck[offset + 1] & 0xff; + int cr = 255 - ycck[offset + 2] & 0xff; + int k = 255 - ycck[offset + 3] & 0xff; + + int cmykC = MAXJSAMPLE - (y + Cr_R_LUT[cr]); + int cmykM = MAXJSAMPLE - (y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS)); + int cmykY = MAXJSAMPLE - (y + Cb_B_LUT[cb]); + + cmyk[offset ] = clamp(cmykC); + cmyk[offset + 1] = clamp(cmykM); + cmyk[offset + 2] = clamp(cmykY); + cmyk[offset + 3] = (byte) k; // K passes through unchanged + } + +// private static byte clamp(int val) { +// return (byte) Math.max(0, Math.min(255, val)); +// } + } + +} diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStreamTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStreamTest.java new file mode 100644 index 00000000..6a031c93 --- /dev/null +++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStreamTest.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2013, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.tiff; + +import com.twelvemonkeys.io.InputStreamAbstractTestCase; +import org.junit.Ignore; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +/** + * YCbCrUpsamplerStreamTest + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: YCbCrUpsamplerStreamTest.java,v 1.0 31.01.13 14:35 haraldk Exp$ + */ +@Ignore +public class YCbCrUpsamplerStreamTest extends InputStreamAbstractTestCase { + // TODO: Implement + @Override + protected InputStream makeInputStream(byte[] pBytes) { + return new YCbCrUpsamplerStream(new ByteArrayInputStream(pBytes), new int[] {2, 2}, pBytes.length / 4, null); + } +} From f666610184f893fc7c858d53e633e8d4bd4151cf Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Thu, 31 Jan 2013 15:40:08 +0100 Subject: [PATCH 06/58] TMI-TIFF: New tag + fixed spelling for tag. --- .../imageio/plugins/jpeg/EXIFThumbnailReader.java | 2 +- .../com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java | 2 +- .../java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/EXIFThumbnailReader.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/EXIFThumbnailReader.java index d18dbdf6..1eaba702 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/EXIFThumbnailReader.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/EXIFThumbnailReader.java @@ -147,7 +147,7 @@ final class EXIFThumbnailReader extends ThumbnailReader { } Entry bitsPerSample = ifd.getEntryById(TIFF.TAG_BITS_PER_SAMPLE); - Entry samplesPerPixel = ifd.getEntryById(TIFF.TAG_SAMPLES_PER_PIXELS); + Entry samplesPerPixel = ifd.getEntryById(TIFF.TAG_SAMPLES_PER_PIXEL); Entry photometricInterpretation = ifd.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION); // Required diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java index efa13180..d2c77af6 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java @@ -88,7 +88,7 @@ final class EXIFEntry extends AbstractEntry { return "StripOffsets"; case TIFF.TAG_ORIENTATION: return "Orientation"; - case TIFF.TAG_SAMPLES_PER_PIXELS: + case TIFF.TAG_SAMPLES_PER_PIXEL: return "SamplesPerPixels"; case TIFF.TAG_ROWS_PER_STRIP: return "RowsPerStrip"; diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java index b60e2955..db3521d2 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java @@ -99,7 +99,7 @@ public interface TIFF { int TAG_COMPRESSION = 259; int TAG_PHOTOMETRIC_INTERPRETATION = 262; int TAG_ORIENTATION = 274; - int TAG_SAMPLES_PER_PIXELS = 277; + int TAG_SAMPLES_PER_PIXEL = 277; int TAG_PLANAR_CONFIGURATION = 284; int TAG_SAMPLE_FORMAT = 339; int TAG_YCBCR_SUB_SAMPLING = 530; @@ -124,6 +124,7 @@ public interface TIFF { int TAG_PRIMARY_CHROMATICITIES = 319; int TAG_COLOR_MAP = 320; int TAG_EXTRA_SAMPLES = 338; + int TAG_TRANSFER_RANGE = 342; int TAG_YCBCR_COEFFICIENTS = 529; int TAG_REFERENCE_BLACK_WHITE = 532; From 00f47e81a45b125de16280c7ba69288263844667 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Thu, 31 Jan 2013 15:41:59 +0100 Subject: [PATCH 07/58] TMI-XXX: New code style + minor housekeeping changes. --- .../com/twelvemonkeys/image/ImageUtil.java | 65 ++++++++----------- .../util/AbstractTokenIterator.java | 2 +- .../com/twelvemonkeys/util/LRUHashMap.java | 1 + .../java/com/twelvemonkeys/util/LRUMap.java | 5 +- .../com/twelvemonkeys/util/LinkedMap.java | 32 ++++----- 5 files changed, 48 insertions(+), 57 deletions(-) diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/ImageUtil.java b/common/common-image/src/main/java/com/twelvemonkeys/image/ImageUtil.java index 9f08f0fd..f9159a9e 100644 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/ImageUtil.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/ImageUtil.java @@ -175,10 +175,6 @@ public final class ImageUtil { /** Our static image tracker */ private static MediaTracker sTracker = new MediaTracker(NULL_COMPONENT); - //private static Object sTrackerMutex = new Object(); - - /** Image id used by the image tracker */ - //private static int sTrackerId = 0; /** */ protected static final AffineTransform IDENTITY_TRANSFORM = new AffineTransform(); @@ -1140,26 +1136,26 @@ public final class ImageUtil { * Sharpens an image using a convolution matrix. * The sharpen kernel used, is defined by the following 3 by 3 matrix: * - * - * - * - * - * + * + * + * + * + * *
0.0-{@code pAmmount}0.0
-{@code pAmmount}4.0 * {@code pAmmount} + 1.0-{@code pAmmount}
0.0-{@code pAmmount}0.0
0.0-{@code pAmount}0.0
-{@code pAmount}4.0 * {@code pAmount} + 1.0-{@code pAmount}
0.0-{@code pAmount}0.0
* * @param pOriginal the BufferedImage to sharpen - * @param pAmmount the ammount of sharpening + * @param pAmount the amount of sharpening * * @return a BufferedImage, containing the sharpened image. */ - public static BufferedImage sharpen(BufferedImage pOriginal, float pAmmount) { - if (pAmmount == 0f) { + public static BufferedImage sharpen(BufferedImage pOriginal, float pAmount) { + if (pAmount == 0f) { return pOriginal; } // Create the convolution matrix float[] data = new float[] { - 0.0f, -pAmmount, 0.0f, -pAmmount, 4f * pAmmount + 1f, -pAmmount, 0.0f, -pAmmount, 0.0f + 0.0f, -pAmount, 0.0f, -pAmount, 4f * pAmount + 1f, -pAmount, 0.0f, -pAmount, 0.0f }; // Do the filtering @@ -1185,7 +1181,7 @@ public final class ImageUtil { * Creates a blurred version of the given image. * * @param pOriginal the original image - * @param pRadius the ammount to blur + * @param pRadius the amount to blur * * @return a new {@code BufferedImage} with a blurred version of the given image */ @@ -1198,18 +1194,18 @@ public final class ImageUtil { // See: http://en.wikipedia.org/wiki/Gaussian_blur#Implementation // Also see http://www.jhlabs.com/ip/blurring.html - // TODO: Rethink... Fixed ammount and scale matrix instead? -// pAmmount = 1f - pAmmount; -// float pAmmount = 1f - pRadius; + // TODO: Rethink... Fixed amount and scale matrix instead? +// pAmount = 1f - pAmount; +// float pAmount = 1f - pRadius; // -// // Normalize ammount -// float normAmt = (1f - pAmmount) / 24; +// // Normalize amount +// float normAmt = (1f - pAmount) / 24; // // // Create the convolution matrix // float[] data = new float[] { // normAmt / 2, normAmt, normAmt, normAmt, normAmt / 2, // normAmt, normAmt, normAmt * 2, normAmt, normAmt, -// normAmt, normAmt * 2, pAmmount, normAmt * 2, normAmt, +// normAmt, normAmt * 2, pAmount, normAmt * 2, normAmt, // normAmt, normAmt, normAmt * 2, normAmt, normAmt, // normAmt / 2, normAmt, normAmt, normAmt, normAmt / 2 // }; @@ -1391,18 +1387,18 @@ public final class ImageUtil { * Changes the contrast of the image * * @param pOriginal the {@code Image} to change - * @param pAmmount the ammount of contrast in the range [-1.0..1.0]. + * @param pAmount the amount of contrast in the range [-1.0..1.0]. * * @return an {@code Image}, containing the contrasted image. */ - public static Image contrast(Image pOriginal, float pAmmount) { + public static Image contrast(Image pOriginal, float pAmount) { // No change, return original - if (pAmmount == 0f) { + if (pAmount == 0f) { return pOriginal; } // Create filter - RGBImageFilter filter = new BrightnessContrastFilter(0f, pAmmount); + RGBImageFilter filter = new BrightnessContrastFilter(0f, pAmount); // Return contrast adjusted image return filter(pOriginal, filter); @@ -1413,18 +1409,18 @@ public final class ImageUtil { * Changes the brightness of the original image. * * @param pOriginal the {@code Image} to change - * @param pAmmount the ammount of brightness in the range [-2.0..2.0]. + * @param pAmount the amount of brightness in the range [-2.0..2.0]. * * @return an {@code Image} */ - public static Image brightness(Image pOriginal, float pAmmount) { + public static Image brightness(Image pOriginal, float pAmount) { // No change, return original - if (pAmmount == 0f) { + if (pAmount == 0f) { return pOriginal; } // Create filter - RGBImageFilter filter = new BrightnessContrastFilter(pAmmount, 0f); + RGBImageFilter filter = new BrightnessContrastFilter(pAmount, 0f); // Return brightness adjusted image return filter(pOriginal, filter); @@ -1465,7 +1461,7 @@ public final class ImageUtil { } /** - * Tries to use H/W-accellerated code for an image for display purposes. + * Tries to use H/W-accelerated code for an image for display purposes. * Note that transparent parts of the image might be replaced by solid * color. Additional image information not used by the current diplay * hardware may be discarded, like extra bith depth etc. @@ -1478,7 +1474,7 @@ public final class ImageUtil { } /** - * Tries to use H/W-accellerated code for an image for display purposes. + * Tries to use H/W-accelerated code for an image for display purposes. * Note that transparent parts of the image might be replaced by solid * color. Additional image information not used by the current diplay * hardware may be discarded, like extra bith depth etc. @@ -1494,7 +1490,7 @@ public final class ImageUtil { } /** - * Tries to use H/W-accellerated code for an image for display purposes. + * Tries to use H/W-accelerated code for an image for display purposes. * Note that transparent parts of the image will be replaced by solid * color. Additional image information not used by the current diplay * hardware may be discarded, like extra bith depth etc. @@ -1825,13 +1821,6 @@ public final class ImageUtil { // Create a local id for use with the mediatracker int imageId; - // NOTE: The synchronization throws IllegalMonitorStateException if - // using JIT on J2SE 1.2 (tested version Sun JRE 1.2.2_017). - // Works perfectly interpreted... Hmmm... - //synchronized (sTrackerMutex) { - //imageId = ++sTrackerId; - //} - // NOTE: This is very experimental... imageId = pImages.length == 1 ? System.identityHashCode(pImages[0]) : System.identityHashCode(pImages); diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/AbstractTokenIterator.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/AbstractTokenIterator.java index edfa51bf..90e56161 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/AbstractTokenIterator.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/AbstractTokenIterator.java @@ -46,7 +46,7 @@ public abstract class AbstractTokenIterator implements TokenIterator { public void remove() { // TODO: This is not difficult: // - Convert String to StringBuilder in constructor - // - delete(pos, mNext.lenght()) + // - delete(pos, next.lenght()) // - Add toString() method // BUT: Would it ever be useful? :-) diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/LRUHashMap.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/LRUHashMap.java index cbaa015f..eee68eeb 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/LRUHashMap.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/LRUHashMap.java @@ -205,6 +205,7 @@ public class LRUHashMap extends LinkedHashMap implements ExpiringMap */ public void removeLRU() { int removeCount = (int) Math.max((size() * trimFactor), 1); + Iterator> entries = entrySet().iterator(); while ((removeCount--) > 0 && entries.hasNext()) { entries.next(); diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/LRUMap.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/LRUMap.java index d5057046..040ed014 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/LRUMap.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/LRUMap.java @@ -45,7 +45,7 @@ import java.util.Map; * * * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/LRUMap.java#1 $ + * @version $Id: com/twelvemonkeys/util/LRUMap.java#1 $ */ public class LRUMap extends LinkedMap implements ExpiringMap { @@ -222,8 +222,9 @@ public class LRUMap extends LinkedMap implements ExpiringMap { */ public void removeLRU() { int removeCount = (int) Math.max((size() * trimFactor), 1); + while ((removeCount--) > 0) { - removeEntry(head.mNext); + removeEntry(head.next); } } } diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/LinkedMap.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/LinkedMap.java index b493ff9d..e9a071a0 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/LinkedMap.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/LinkedMap.java @@ -181,19 +181,19 @@ public class LinkedMap extends AbstractDecoratedMap implements Seria return "head"; } }; - head.mPrevious = head.mNext = head; + head.previous = head.next = head; } public boolean containsValue(Object pValue) { // Overridden to take advantage of faster iterator if (pValue == null) { - for (LinkedEntry e = head.mNext; e != head; e = e.mNext) { + for (LinkedEntry e = head.next; e != head; e = e.next) { if (e.mValue == null) { return true; } } } else { - for (LinkedEntry e = head.mNext; e != head; e = e.mNext) { + for (LinkedEntry e = head.next; e != head; e = e.next) { if (pValue.equals(e.mValue)) { return true; } @@ -215,7 +215,7 @@ public class LinkedMap extends AbstractDecoratedMap implements Seria } private abstract class LinkedMapIterator implements Iterator { - LinkedEntry mNextEntry = head.mNext; + LinkedEntry mNextEntry = head.next; LinkedEntry mLastReturned = null; /** @@ -254,7 +254,7 @@ public class LinkedMap extends AbstractDecoratedMap implements Seria } LinkedEntry e = mLastReturned = mNextEntry; - mNextEntry = e.mNext; + mNextEntry = e.next; return e; } @@ -309,7 +309,7 @@ public class LinkedMap extends AbstractDecoratedMap implements Seria oldValue = null; // Remove eldest entry if instructed, else grow capacity if appropriate - LinkedEntry eldest = head.mNext; + LinkedEntry eldest = head.next; if (removeEldestEntry(eldest)) { removeEntry(eldest); } @@ -407,13 +407,13 @@ public class LinkedMap extends AbstractDecoratedMap implements Seria * Linked list implementation of {@code Map.Entry}. */ protected static class LinkedEntry extends BasicEntry implements Serializable { - LinkedEntry mPrevious; - LinkedEntry mNext; + LinkedEntry previous; + LinkedEntry next; LinkedEntry(K pKey, V pValue, LinkedEntry pNext) { super(pKey, pValue); - mNext = pNext; + next = pNext; } /** @@ -423,19 +423,19 @@ public class LinkedMap extends AbstractDecoratedMap implements Seria * @param pExisting the entry to add before */ void addBefore(LinkedEntry pExisting) { - mNext = pExisting; - mPrevious = pExisting.mPrevious; + next = pExisting; + previous = pExisting.previous; - mPrevious.mNext = this; - mNext.mPrevious = this; + previous.next = this; + next.previous = this; } /** * Removes this entry from the linked list. */ void remove() { - mPrevious.mNext = mNext; - mNext.mPrevious = mPrevious; + previous.next = next; + next.previous = previous; } /** @@ -456,7 +456,7 @@ public class LinkedMap extends AbstractDecoratedMap implements Seria /** * Removes this entry from the linked list. * - * @param pMap the map to record remoal from + * @param pMap the map to record removal from */ protected void recordRemoval(Map pMap) { // TODO: Is this REALLY correct? From b834a32b01af881e37d375121c8508f704e2692e Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Thu, 31 Jan 2013 16:35:37 +0100 Subject: [PATCH 08/58] TMI-TIFF: Minor bug introduced by testing.. --- .../imageio/plugins/tiff/YCbCrUpsamplerStream.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java index 2a6c1051..cb6c37b5 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java @@ -226,7 +226,7 @@ final class YCbCrUpsamplerStream extends FilterInputStream { double lumaBlue = coefficients[2]; rgb[offset ] = clamp((int) Math.round(cr * (2 - 2 * lumaRed) + y)); - rgb[offset + 2] = clamp((int) Math.round(cb * (2 - 2 * lumaBlue) + y) - 128); + rgb[offset + 2] = clamp((int) Math.round(cb * (2 - 2 * lumaBlue) + y)); rgb[offset + 1] = clamp((int) Math.round((y - lumaRed * (rgb[offset] & 0xff) - lumaBlue * (rgb[offset + 2] & 0xff)) / lumaGreen)); } From 41a08761ba3ec0af5abaaf133f65c7287f28464a Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Thu, 31 Jan 2013 16:36:47 +0100 Subject: [PATCH 09/58] TMI-TIFF: Removed leftover debug output. --- .../twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java index 996f6a3d..d4f210d7 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java @@ -432,10 +432,6 @@ public class TIFFImageReader extends ImageReaderBase { public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException { readIFD(imageIndex); - System.err.println("currentIFD.getEntryById(TIFF.TAG_REFERENCE_BLACK_WHITE): " + currentIFD.getEntryById(TIFF.TAG_REFERENCE_BLACK_WHITE)); - System.err.println("currentIFD.getEntryById(TIFF.TAG_TRANSFER_FUNCTION): " + currentIFD.getEntryById(TIFF.TAG_TRANSFER_FUNCTION)); - System.err.println("currentIFD.getEntryById(TIFF.TAG_TRANSFER_RANGE): " + currentIFD.getEntryById(TIFF.TAG_TRANSFER_RANGE)); - int width = getWidth(imageIndex); int height = getHeight(imageIndex); From fcd15a9e367a64e8d0dbf280b9bf968380621543 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Wed, 6 Feb 2013 10:44:49 +0100 Subject: [PATCH 10/58] TMI-META: Now correctly reads/parses SOS marker segment. Slightly stricter checking of markers. --- .../twelvemonkeys/imageio/metadata/jpeg/JPEG.java | 4 ++-- .../imageio/metadata/jpeg/JPEGSegmentUtil.java | 13 +++++++++---- .../imageio/metadata/jpeg/JPEGSegmentUtilTest.java | 6 ++++-- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEG.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEG.java index 8753c26e..aa9a3489 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEG.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEG.java @@ -89,6 +89,6 @@ public interface JPEG { // "Adobe" APP14 // Possibly - // "http://ns.adobe.com/xap/1.0/" (XMP) - // "Photoshop 3.0" (Contains IPTC) + // "http://ns.adobe.com/xap/1.0/" (XMP) APP1 + // "Photoshop 3.0" (may contain IPTC) APP13 } diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java index 6372737b..98a8f548 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java @@ -95,7 +95,8 @@ public final class JPEGSegmentUtil { JPEGSegment segment; try { - while (!isImageDone(segment = readSegment(stream, segmentIdentifiers))) { + do { + segment = readSegment(stream, segmentIdentifiers); // System.err.println("segment: " + segment); if (isRequested(segment, segmentIdentifiers)) { @@ -106,6 +107,7 @@ public final class JPEGSegmentUtil { segments.add(segment); } } + while (!isImageDone(segment)); } catch (EOFException ignore) { // Just end here, in case of malformed stream @@ -153,6 +155,9 @@ public final class JPEGSegmentUtil { static JPEGSegment readSegment(final ImageInputStream stream, Map> segmentIdentifiers) throws IOException { int marker = stream.readUnsignedShort(); + if ((marker >> 8 & 0xff) != 0xff) { + throw new IIOException(String.format("Bad marker: %04x", marker)); + } int length = stream.readUnsignedShort(); // Length including length field itself byte[] data; @@ -192,7 +197,7 @@ public final class JPEGSegmentUtil { @Override public boolean contains(Object o) { - return true; + return o instanceof String; } } @@ -204,13 +209,13 @@ public final class JPEGSegmentUtil { @Override public List get(Object key) { - return key instanceof Integer && JPEGSegment.isAppSegmentMarker((Integer) key) ? ALL_IDS : null; + return containsKey(key) && JPEGSegment.isAppSegmentMarker((Integer) key) ? ALL_IDS : null; } @Override public boolean containsKey(Object key) { - return true; + return key instanceof Integer; } } diff --git a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtilTest.java b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtilTest.java index 2a69d8b1..443341dd 100644 --- a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtilTest.java +++ b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtilTest.java @@ -151,19 +151,21 @@ public class JPEGSegmentUtilTest { @Test public void testReadAll() throws IOException { List segments = JPEGSegmentUtil.readSegments(getData("/jpeg/9788245605525.jpg"), JPEGSegmentUtil.ALL_SEGMENTS); - assertEquals(6, segments.size()); + assertEquals(7, segments.size()); assertEquals(segments.toString(), JPEG.SOF0, segments.get(3).marker()); assertEquals(segments.toString(), null, segments.get(3).identifier()); + assertEquals(segments.toString(), JPEG.SOS, segments.get(segments.size() - 1).marker()); } @Test public void testReadAllAlt() throws IOException { List segments = JPEGSegmentUtil.readSegments(getData("/jpeg/ts_open_300dpi.jpg"), JPEGSegmentUtil.ALL_SEGMENTS); - assertEquals(26, segments.size()); + assertEquals(27, segments.size()); assertEquals(segments.toString(), JPEG.SOF0, segments.get(23).marker()); assertEquals(segments.toString(), null, segments.get(23).identifier()); + assertEquals(segments.toString(), JPEG.SOS, segments.get(segments.size() - 1).marker()); } @Test From c394f8a4bc8a687b6721a5faee1faf31f1ae6f34 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Wed, 6 Feb 2013 11:20:42 +0100 Subject: [PATCH 11/58] TMI-TIFF: Fixed bug in YCbCr reading. Implemented "old-style" JPEG reading for two test images. More work needed. --- .../imageio/metadata/exif/TIFF.java | 6 + .../imageio/plugins/tiff/JPEGTables.java | 2 +- .../imageio/plugins/tiff/TIFFExtension.java | 4 + .../imageio/plugins/tiff/TIFFImageReader.java | 254 +++++++++++++++++- .../plugins/tiff/YCbCrUpsamplerStream.java | 17 +- .../plugins/tiff/TIFFImageReaderTest.java | 9 +- .../tiff/YCbCrUpsamplerStreamTest.java | 2 +- .../src/test/resources/tiff/smallliz.tif | Bin 0 -> 5052 bytes .../src/test/resources/tiff/ycbcr-cat.tif | Bin 0 -> 72766 bytes .../src/test/resources/tiff/zackthecat.tif | Bin 0 -> 8258 bytes 10 files changed, 273 insertions(+), 21 deletions(-) create mode 100755 imageio/imageio-tiff/src/test/resources/tiff/smallliz.tif create mode 100644 imageio/imageio-tiff/src/test/resources/tiff/ycbcr-cat.tif create mode 100644 imageio/imageio-tiff/src/test/resources/tiff/zackthecat.tif diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java index db3521d2..ef086ad8 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java @@ -163,4 +163,10 @@ public interface TIFF { int TAG_TILE_BYTE_COUNTS = 325; int TAG_JPEG_TABLES = 347; + + // "Old-style" JPEG (Obsolete) + int TAG_JPEG_PROC = 512; + int TAG_JPEG_QTABLES = 519; + int TAG_JPEG_DCTABLES = 520; + int TAG_JPEG_ACTABLES = 521; } diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/JPEGTables.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/JPEGTables.java index a806ec11..b1aba18f 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/JPEGTables.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/JPEGTables.java @@ -112,7 +112,7 @@ class JPEGTables { // Read lengths as short array short[] lengths = new short[DHT_LENGTH]; - for (int i = 0, lengthsLength = lengths.length; i < lengthsLength; i++) { + for (int i = 0; i < DHT_LENGTH; i++) { lengths[i] = (short) data.readUnsignedByte(); } read += lengths.length; diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFExtension.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFExtension.java index 4ee2b28e..2ce098d9 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFExtension.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFExtension.java @@ -65,4 +65,8 @@ interface TIFFExtension { int SAMPLEFORMAT_INT = 2; int SAMPLEFORMAT_FP = 3; int SAMPLEFORMAT_UNDEFINED = 4; + + // "Old-style" JPEG (obsolete) + int JPEG_PROC_BASELINE = 1; + int JPEG_PROC_LOSSLESS = 14; } diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java index d4f210d7..1da96254 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java @@ -59,9 +59,7 @@ import java.awt.color.ICC_Profile; import java.awt.image.*; import java.io.*; import java.nio.ByteOrder; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; +import java.util.*; import java.util.List; import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; @@ -485,9 +483,6 @@ public class TIFFImageReader extends ImageReaderBase { WritableRaster rowRaster = rawType.getColorModel().createCompatibleWritableRaster(stripTileWidth, 1); int row = 0; - // Read data - processImageStarted(imageIndex); - switch (compression) { // TIFF Baseline case TIFFBaseline.COMPRESSION_NONE: @@ -547,6 +542,9 @@ public class TIFFImageReader extends ImageReaderBase { } } + // Read data + processImageStarted(imageIndex); + // TODO: Read only tiles that lies within region // General uncompressed/compressed reading for (int y = 0; y < tilesDown; y++) { @@ -606,6 +604,7 @@ public class TIFFImageReader extends ImageReaderBase { case TIFFExtension.COMPRESSION_JPEG: // JPEG ('new-style' JPEG) // TODO: Refactor all JPEG reading out to separate JPEG support class? + // TODO: Cache the JPEG reader for later use? Remember to reset to avoid resource leaks // TIFF is strictly ISO JPEG, so we should probably stick to the standard reader ImageReader jpegReader = new JPEGImageReader(getOriginatingProvider()); @@ -680,6 +679,9 @@ public class TIFFImageReader extends ImageReaderBase { // ...and the JPEG reader will probably choke on missing tables... } + // Read data + processImageStarted(imageIndex); + for (int y = 0; y < tilesDown; y++) { int col = 0; int rowsInTile = Math.min(stripTileHeight, height - row); @@ -689,14 +691,14 @@ public class TIFFImageReader extends ImageReaderBase { int colsInTile = Math.min(stripTileWidth, width - col); imageInput.seek(stripTileOffsets[i]); - SubImageInputStream subStream = new SubImageInputStream(imageInput, stripTileByteCounts != null ? (int) stripTileByteCounts[i] : Short.MAX_VALUE); + ImageInputStream subStream = new SubImageInputStream(imageInput, stripTileByteCounts != null ? (int) stripTileByteCounts[i] : Short.MAX_VALUE); try { jpegReader.setInput(subStream); jpegParam.setSourceRegion(new Rectangle(0, 0, colsInTile, rowsInTile)); jpegParam.setDestinationOffset(new Point(col, row)); jpegParam.setDestination(destination); // TODO: This works only if Gray/YCbCr/RGB, not CMYK/LAB/etc... - // In the latter case we will have to use readAsRaster + // In the latter case we will have to use readAsRaster and do color conversion ourselves jpegReader.read(0, jpegParam); } finally { @@ -722,6 +724,240 @@ public class TIFFImageReader extends ImageReaderBase { break; + case TIFFExtension.COMPRESSION_OLD_JPEG: + // JPEG ('old-style' JPEG, later overridden in Technote2) + + // http://www.remotesensing.org/libtiff/TIFFTechNote2.html + // TODO: Issue warning? + + int mode = getValueAsIntWithDefault(TIFF.TAG_JPEG_PROC, 1); + if (mode == TIFFExtension.JPEG_PROC_LOSSLESS) { + throw new IIOException("Unsupported TIFF JPEGProcessingMode: Lossless (14)"); + } + else if (mode != TIFFExtension.JPEG_PROC_BASELINE) { + throw new IIOException("Unknown TIFF JPEGProcessingMode value: " + mode); + } + + // 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 + jpegReader = new JPEGImageReader(getOriginatingProvider()); + jpegParam = (JPEGImageReadParam) jpegReader.getDefaultReadParam(); + + int jpegOffset = getValueAsIntWithDefault(TIFF.TAG_JPEG_INTERCHANGE_FORMAT, -1); + int jpegLenght = getValueAsIntWithDefault(TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, -1); + + ImageInputStream subStream; + + if (jpegOffset != -1) { + // Straight forward case: We're good to go! We'll disregard tiling and any tables tags + + imageInput.seek(jpegOffset); + subStream = new SubImageInputStream(imageInput, jpegLenght != -1 ? jpegLenght : Short.MAX_VALUE); + jpegReader.setInput(subStream); + + // Read data + processImageStarted(imageIndex); + + try { + jpegParam.setSourceRegion(new Rectangle(0, 0, width, height)); + jpegParam.setDestination(destination); + // TODO: This works only if Gray/YCbCr/RGB, not CMYK/LAB/etc... + // In the latter case we will have to use readAsRaster and do color conversion ourselves + jpegReader.read(0, jpegParam); + } + finally { + subStream.close(); + } + + processImageProgress(100f * row / (float) height); + + if (abortRequested()) { + processReadAborted(); + } + } + else { + // The hard way: Read tables and re-create a full JFIF stream + + // 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 + + long[] qTablesOffsets = getValueAsLongArray(TIFF.TAG_JPEG_QTABLES, "JPEGQTables", true); + byte[][] qTables = new byte[3][(int) (qTablesOffsets[1] - qTablesOffsets[0])]; // TODO: Using the offsets seems fragile.. Use fixed length?? + for (int j = 0; j < 3; j++) { + imageInput.seek(qTablesOffsets[j]); + imageInput.readFully(qTables[j]); + } +// System.err.println("qTables: " + qTables[0].length); + + long[] dcTablesOffsets = getValueAsLongArray(TIFF.TAG_JPEG_DCTABLES, "JPEGDCTables", true); + byte[][] dcTables = new byte[3][(int) (dcTablesOffsets[1] - dcTablesOffsets[0])]; + for (int j = 0; j < 3; j++) { + imageInput.seek(dcTablesOffsets[j]); + imageInput.readFully(dcTables[j]); + } +// System.err.println("dcTables: " + dcTables[0].length); + + long[] acTablesOffsets = getValueAsLongArray(TIFF.TAG_JPEG_ACTABLES, "JPEGACTables", true); + byte[][] acTables = new byte[3][(int) (acTablesOffsets[1] - acTablesOffsets[0])]; + for (int j = 0; j < 3; j++) { + imageInput.seek(acTablesOffsets[j]); + imageInput.readFully(acTables[j]); + } +// System.err.println("acTables: " + acTables[0].length); + + // Read data + processImageStarted(imageIndex); + + for (int y = 0; y < tilesDown; y++) { + int col = 0; + int rowsInTile = Math.min(stripTileHeight, height - row); + + for (int x = 0; x < tilesAcross; x++) { + int colsInTile = Math.min(stripTileWidth, width - col); + int i = y * tilesAcross + x; + + imageInput.seek(stripTileOffsets[i]); + subStream = ImageIO.createImageInputStream(new SequenceInputStream(Collections.enumeration( + Arrays.asList( + // TODO; Get rid of hardcoded data + extract method/class... + // TODO: + // - Create a BAIS with size large enough to keep JFIF structure incl tables and SOS, + // - Wrap in DataInput, + // - Insert width/height, component ids etc at correct place + // - Insert tables at correct place + + new ByteArrayInputStream(new byte[] {(byte) 0xff, (byte) 0xd8, // SOI + // SOF0 (short), length (short) + (byte) 0xff, (byte) 0xc0, 0x00, 0x11, // SOF0, 17 bytes + // bits (byte), width (short), height (short) + 0x08, 0x00, (byte) 0xe0, 0x00, (byte) 0xf0, + // num comp (byte), (id (byte) h/vsub (byte), qtsel (byte) * num comp) + 0x03, 0x00, 0x22, 0x00, 0x01, 0x11, 0x01, 0x02, 0x11, 0x02, +// 0x03, 0x00, 0x22, 0x00, 0x01, 0x11, 0x01, 0x02, 0x11, 0x01, + // DQT + (byte) 0xff, (byte) 0xdb, 0x00, 0x43, 0x00, + }), + // ... table data + new ByteArrayInputStream(qTables[0]), + new ByteArrayInputStream(new byte[] { + (byte) 0xff, (byte) 0xdb, 0x00, 0x43, 0x01, + }), + // ... table data + new ByteArrayInputStream(qTables[1]), + new ByteArrayInputStream(new byte[] { + (byte) 0xff, (byte) 0xdb, 0x00, 0x43, 0x02, + }), + // ... table data + new ByteArrayInputStream(qTables[2]), + + // DHT (DC) + new ByteArrayInputStream(new byte[] { + (byte) 0xff, (byte) 0xc4, 0x00, 0x1f, 0x00, + }), + // ... table data + new ByteArrayInputStream(dcTables[0]), + new ByteArrayInputStream(new byte[] { + (byte) 0xff, (byte) 0xc4, 0x00, 0x1f, 0x01, + }), + // ... table data + new ByteArrayInputStream(dcTables[1]), + new ByteArrayInputStream(new byte[] { + (byte) 0xff, (byte) 0xc4, 0x00, 0x1f, 0x02, + }), + // ... table data + new ByteArrayInputStream(dcTables[2]), + + // DHT (AC) + new ByteArrayInputStream(new byte[] { + (byte) 0xff, (byte) 0xc4, 0x00, (byte) 0xb5, 0x10, + }), + // ... table data + new ByteArrayInputStream(acTables[0]), + new ByteArrayInputStream(new byte[] { + (byte) 0xff, (byte) 0xc4, 0x00, (byte) 0xb5, 0x11, + }), + // ... table data + new ByteArrayInputStream(acTables[1]), + new ByteArrayInputStream(new byte[] { + (byte) 0xff, (byte) 0xc4, 0x00, (byte) 0xb5, 0x12, + }), + // ... table data + new ByteArrayInputStream(acTables[2]), + + new ByteArrayInputStream(new byte[] { + (byte) 0xff, (byte) 0xda, // SOS + // TODO: Figure out what the last 3 bytes are... + // Length: 12 (short), num comp (byte), (id (byte), dc/ac sel (byte) * num comp), ?? byte, ?? byte ?? byte + 0x00, 0x0C, 0x03, 0x00, 0x00, 0x01, 0x11, 0x02, 0x11, 0x00, 0x00, 0x00 +// 0x00, 0x0C, 0x03, 0x00, 0x00, 0x01, 0x11, 0x02, 0x12, 0x00, 0x63, 0x00 + }), + IIOUtil.createStreamAdapter(imageInput, stripTileByteCounts != null ? (int) stripTileByteCounts[i] : Short.MAX_VALUE), + new ByteArrayInputStream(new byte[] {(byte) 0xff, (byte) 0xd9}) // EOI + ) + ))); + + jpegReader.setInput(subStream); + + try { + jpegParam.setSourceRegion(new Rectangle(0, 0, colsInTile, rowsInTile)); + jpegParam.setDestinationOffset(new Point(col, row)); + jpegParam.setDestination(destination); + // TODO: This works only if Gray/YCbCr/RGB, not CMYK/LAB/etc... + // In the latter case we will have to use readAsRaster and do color conversion ourselves + jpegReader.read(0, jpegParam); + } + finally { + subStream.close(); + } + + if (abortRequested()) { + break; + } + + col += colsInTile; + } + + processImageProgress(100f * row / (float) height); + + if (abortRequested()) { + processReadAborted(); + break; + } + + row += rowsInTile; + } + } + + break; + case TIFFBaseline.COMPRESSION_CCITT_HUFFMAN: // CCITT modified Huffman // Additionally, the specification defines these values as part of the TIFF extensions: @@ -729,8 +965,6 @@ public class TIFFImageReader extends ImageReaderBase { // CCITT Group 3 fax encoding case TIFFExtension.COMPRESSION_CCITT_T6: // CCITT Group 4 fax encoding - case TIFFExtension.COMPRESSION_OLD_JPEG: - // JPEG ('old-style' JPEG, later overridden in Technote2) throw new IIOException("Unsupported TIFF Compression value: " + compression); default: diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java index cb6c37b5..967e1ea7 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java @@ -216,18 +216,21 @@ final class YCbCrUpsamplerStream extends FilterInputStream { } private void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final double[] coefficients, final int offset) { - // TODO: FixMe: This is bogus... - double y = yCbCr[offset ] & 0xff; - double cb = yCbCr[offset + 1] & 0xff; - double cr = yCbCr[offset + 2] & 0xff; + double y = (yCbCr[offset ] & 0xff); + double cb = (yCbCr[offset + 1] & 0xff) - 128; // TODO: The -128 part seems bogus... Consult ReferenceBlackWhite??? But default to these values? + double cr = (yCbCr[offset + 2] & 0xff) - 128; double lumaRed = coefficients[0]; double lumaGreen = coefficients[1]; double lumaBlue = coefficients[2]; - rgb[offset ] = clamp((int) Math.round(cr * (2 - 2 * lumaRed) + y)); - rgb[offset + 2] = clamp((int) Math.round(cb * (2 - 2 * lumaBlue) + y)); - rgb[offset + 1] = clamp((int) Math.round((y - lumaRed * (rgb[offset] & 0xff) - lumaBlue * (rgb[offset + 2] & 0xff)) / lumaGreen)); + int red = (int) Math.round(cr * (2 - 2 * lumaRed) + y); + int blue = (int) Math.round(cb * (2 - 2 * lumaBlue) + y); + int green = (int) Math.round((y - lumaRed * (rgb[offset] & 0xff) - lumaBlue * (rgb[offset + 2] & 0xff)) / lumaGreen); + + rgb[offset ] = clamp(red); + rgb[offset + 2] = clamp(blue); + rgb[offset + 1] = clamp(green); } private static byte clamp(int val) { diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java index 8200accd..1280af1a 100644 --- a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java +++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java @@ -52,11 +52,14 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTestCase getMIMETypes() { return Arrays.asList("image/tiff"); } + + // TODO: Test YCbCr colors } diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStreamTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStreamTest.java index 6a031c93..0713f5c7 100644 --- a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStreamTest.java +++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStreamTest.java @@ -43,7 +43,7 @@ import java.io.InputStream; */ @Ignore public class YCbCrUpsamplerStreamTest extends InputStreamAbstractTestCase { - // TODO: Implement + // TODO: Implement + add @Ignore for all tests that makes no sense for this class. @Override protected InputStream makeInputStream(byte[] pBytes) { return new YCbCrUpsamplerStream(new ByteArrayInputStream(pBytes), new int[] {2, 2}, pBytes.length / 4, null); diff --git a/imageio/imageio-tiff/src/test/resources/tiff/smallliz.tif b/imageio/imageio-tiff/src/test/resources/tiff/smallliz.tif new file mode 100755 index 0000000000000000000000000000000000000000..ee9dbb0ea336530a05a8750ebd29fc96a77aadeb GIT binary patch literal 5052 zcmbW5XIN9sx`tO02)zUd5PFpmKv+`5Bb+57xE=ghj^d7o?MnVEam`n6sI0{{g84CKFNe{}*da-aYxfB<{| z2nGVfK)*f$8UQ^NH7zwIJuM9_1B{-L70JfR%*-mmFUW~hlu}Vvkdl|Xa@F4Oil&v8 zoV>B0iPbe{4^IzOgTR{st`YWb9xmsbfM5&^tZ>##Y;2cY)aBJ({?F~#8-N=CK%}7G z4fv~pzz`BrC>c2gCDpkB1VH|rC-#3j{->dd9sq+N5HJaZl$3;oxLq`H9w32{GVn^M zK^aZ1lko*INycId$@$gmyWpl1`vOwVL2(q6%q*-3Ho*%*NMV$;jI5lzf})0|7FzqN zj;@)xg{76XjjfBTo4bdnmv?YTXjpi}&B*w>35iL`_fpcZ=^2?>**UpI#U-U>@Y~Vx$secRiT7%P z5wXD}BoGp^b8HYeoEQ)o2`R4xltIme?0O(0pJXgKlRBobzKeoi%5)#@95g}6EFirs zcyLbs8~lIA9rynN|A+fO*mD3ig!qPGU>NaBbfE&D>iUU>^K8WLjq(NdXlHi1nrV$dX53grPqgCT8e|W`c1RCvm#_N0PPpia*lCcyu zrppe)hq*aOPBt4UA6HccFw@g%ac(Q{Jo%U8Soob*#&tE{@9kBlPR zhZUI|eb%9H<(WK5%c>+4<+03jKOfDCQa`B0eXZf$ZIP72cH3pt zK1$?qWt4j^R)>vbjOBHWa!G^$=ZB-}8g~kt+}zNvhqHIHJo|N1w!WS?u@wV^xAiJf z#st?GCalTuO?rH-PZqO#=wL5;=ZzaJjTT2$n;PpzF3yQjsMMgkd&V_jiw+^IxEh)A z91BID2UJw5P)?(VE-9|2FT^q~CwOjNs7*o&)uS(Lp`5%k*<=T*C{WL8w=gpsS#cHV zH`&FympK>>aSoG-W|LKkWG|HzsyRa2wl3uGjdV58zy>CUxL=}4g^LDileB%1nZV-n zhK7ReUbA-sR5!+@u@XUc%8{00A2sr_7F`oou-i8814(@=e&EOX`EM1KJNY+;BCd3?oQbzFpc#ukB#Zit$AtF zf?N&xTznn%WA(}R$;Ru>9%~03tVJK!F&TM6O;;!4=?6C8)IfA0T4Gx-q^D52zLuhY z<%$(j?hu!*_*7iT*x=ejmH4p9z{ygFFUjd`CM0aP9?FUHMx(l|?GgmEla6FO#vk_* z@?Sfa6?x_)uKUhx33H4EMK!XW7-$S(a)+bj%qH8(OLffF$-*bA$(JY&FJ7iJu0nos zQ)F7*ohe5;VWPEE<#?8>$_QffI8t|SSs&Jm*}b+^qs6FvRqfq|eA`$Tk+W`6HNdnd zS~La{6Rd0dC4AR00Rxyj1w~oJQ%Q`;S7(#kq_^nL{LnAseYWac*l&h7>tGTz$cIK( zvUoA53a)vQuS5@$1yGtGwOyaZAjfNLh6pm_W z1~k%r>$q*XY{+{gdR5+vla<}kO@3n(oKos8I~*4dQ-;41%j8aqHpM1 z3QP^F2-I_yMK@JTJda&D5~Y>_o}WaS05(e;ey)@=+vs%+zKUajZBlE%+fzr zXyqYXP3{6Mt^<8hht|S(P?#3TVjj)()Z|mqr4};-12LTt*YEipmLgnCsKc+8xBHU* zxOs#x)|gH(ENV`LKT-^J^%yL(Ye+kYiPnbJ`%C9s^2@KW<}8V;aZq`;(~PN~(5e0x z)J&{8^*Z6zo!aOvH_{P|agBU`_R3gR$b5&` z?{)V4p4w!=i>5Fw8*}JN;AVS~PH}@gUZ=FaU%ac=rn%jh=;{cgMwXL~pp_OG5&3XYHCqI3@

qzkT`*AGRwN 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 X-u$=x&n*JaOMTk^r{pF5pU3|LwBkwT literal 0 HcmV?d00001 diff --git a/imageio/imageio-tiff/src/test/resources/tiff/zackthecat.tif b/imageio/imageio-tiff/src/test/resources/tiff/zackthecat.tif new file mode 100644 index 0000000000000000000000000000000000000000..15185b6881b2ed68aa2a9a979ca31fecb61288c2 GIT binary patch literal 8258 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 literal 0 HcmV?d00001 From 8b9d5c7abcdf26478140c15de1ff2a6b94f3a29f Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Wed, 6 Feb 2013 15:20:27 +0100 Subject: [PATCH 12/58] TMI-TIFF: Renamed "Old-style" JPEG constants, to discourage use. Removed hardcoding of JFIF-stream for "Old-style" JPEG reading + cleaned up code. Fixed a bug in getRawImageType for planar RGBA --- .../imageio/metadata/exif/TIFF.java | 12 +- .../imageio/plugins/tiff/TIFFImageReader.java | 170 +++++++++--------- 2 files changed, 93 insertions(+), 89 deletions(-) diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java index ef086ad8..7201f785 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java @@ -113,6 +113,7 @@ public interface TIFF { int TAG_STRIP_OFFSETS = 273; int TAG_ROWS_PER_STRIP = 278; int TAG_STRIP_BYTE_COUNTS = 279; + // "Old-style" JPEG (still used as EXIF thumbnail) int TAG_JPEG_INTERCHANGE_FORMAT = 513; int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = 514; @@ -162,11 +163,12 @@ public interface TIFF { int TAG_TILE_OFFSETS = 324; int TAG_TILE_BYTE_COUNTS = 325; + // JPEG int TAG_JPEG_TABLES = 347; - // "Old-style" JPEG (Obsolete) - int TAG_JPEG_PROC = 512; - int TAG_JPEG_QTABLES = 519; - int TAG_JPEG_DCTABLES = 520; - int TAG_JPEG_ACTABLES = 521; + // "Old-style" JPEG (Obsolete) DO NOT WRITE! + int TAG_OLD_JPEG_PROC = 512; + int TAG_OLD_JPEG_QTABLES = 519; + int TAG_OLD_JPEG_DCTABLES = 520; + int TAG_OLD_JPEG_ACTABLES = 521; } diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java index 1da96254..918854c8 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java @@ -37,11 +37,13 @@ import com.twelvemonkeys.imageio.metadata.Entry; import com.twelvemonkeys.imageio.metadata.exif.EXIFReader; import com.twelvemonkeys.imageio.metadata.exif.Rational; import com.twelvemonkeys.imageio.metadata.exif.TIFF; +import com.twelvemonkeys.imageio.metadata.jpeg.JPEG; import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream; import com.twelvemonkeys.imageio.stream.SubImageInputStream; import com.twelvemonkeys.imageio.util.IIOUtil; import com.twelvemonkeys.imageio.util.IndexedImageTypeSpecifier; import com.twelvemonkeys.imageio.util.ProgressListenerBase; +import com.twelvemonkeys.io.FastByteArrayOutputStream; import com.twelvemonkeys.io.LittleEndianDataInputStream; import com.twelvemonkeys.io.enc.DecoderStream; import com.twelvemonkeys.io.enc.PackBitsDecoder; @@ -289,7 +291,7 @@ public class TIFFImageReader extends ImageReaderBase { return ImageTypeSpecifier.createInterleaved(cs, new int[] {0, 1, 2, 3}, dataType, true, false); case TIFFExtension.PLANARCONFIG_PLANAR: - return ImageTypeSpecifier.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, dataType, false, false); + return ImageTypeSpecifier.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, dataType, true, false); } } // TODO: More samples might be ok, if multiple alpha or unknown samples @@ -730,7 +732,7 @@ public class TIFFImageReader extends ImageReaderBase { // http://www.remotesensing.org/libtiff/TIFFTechNote2.html // TODO: Issue warning? - int mode = getValueAsIntWithDefault(TIFF.TAG_JPEG_PROC, 1); + int mode = getValueAsIntWithDefault(TIFF.TAG_OLD_JPEG_PROC, 1); if (mode == TIFFExtension.JPEG_PROC_LOSSLESS) { throw new IIOException("Unsupported TIFF JPEGProcessingMode: Lossless (14)"); } @@ -774,14 +776,18 @@ public class TIFFImageReader extends ImageReaderBase { int jpegOffset = getValueAsIntWithDefault(TIFF.TAG_JPEG_INTERCHANGE_FORMAT, -1); int jpegLenght = getValueAsIntWithDefault(TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, -1); - ImageInputStream subStream; + ImageInputStream stream; if (jpegOffset != -1) { // 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) { + processWarningOccurred("Old-style JPEG compressed TIFF with JFIF stream encountered. Reading as single tile, ignoring tables."); + } + imageInput.seek(jpegOffset); - subStream = new SubImageInputStream(imageInput, jpegLenght != -1 ? jpegLenght : Short.MAX_VALUE); - jpegReader.setInput(subStream); + stream = new SubImageInputStream(imageInput, jpegLenght != -1 ? jpegLenght : Short.MAX_VALUE); + jpegReader.setInput(stream); // Read data processImageStarted(imageIndex); @@ -794,7 +800,7 @@ public class TIFFImageReader extends ImageReaderBase { jpegReader.read(0, jpegParam); } finally { - subStream.close(); + stream.close(); } processImageProgress(100f * row / (float) height); @@ -806,18 +812,21 @@ public class TIFFImageReader extends ImageReaderBase { else { // The hard way: Read tables and re-create a full JFIF stream + processWarningOccurred("Old-style JPEG compressed TIFF without JFIF stream encountered. Attempting to re-create JFIF stream."); + // 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 - long[] qTablesOffsets = getValueAsLongArray(TIFF.TAG_JPEG_QTABLES, "JPEGQTables", true); + long[] qTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_QTABLES, "JPEGQTables", true); byte[][] qTables = new byte[3][(int) (qTablesOffsets[1] - qTablesOffsets[0])]; // TODO: Using the offsets seems fragile.. Use fixed length?? for (int j = 0; j < 3; j++) { imageInput.seek(qTablesOffsets[j]); imageInput.readFully(qTables[j]); } + // System.err.println("qTables: " + qTables[0].length); - long[] dcTablesOffsets = getValueAsLongArray(TIFF.TAG_JPEG_DCTABLES, "JPEGDCTables", true); + long[] dcTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_DCTABLES, "JPEGDCTables", true); byte[][] dcTables = new byte[3][(int) (dcTablesOffsets[1] - dcTablesOffsets[0])]; for (int j = 0; j < 3; j++) { imageInput.seek(dcTablesOffsets[j]); @@ -825,7 +834,7 @@ public class TIFFImageReader extends ImageReaderBase { } // System.err.println("dcTables: " + dcTables[0].length); - long[] acTablesOffsets = getValueAsLongArray(TIFF.TAG_JPEG_ACTABLES, "JPEGACTables", true); + long[] acTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_ACTABLES, "JPEGACTables", true); byte[][] acTables = new byte[3][(int) (acTablesOffsets[1] - acTablesOffsets[0])]; for (int j = 0; j < 3; j++) { imageInput.seek(acTablesOffsets[j]); @@ -845,86 +854,79 @@ public class TIFFImageReader extends ImageReaderBase { int i = y * tilesAcross + x; imageInput.seek(stripTileOffsets[i]); - subStream = ImageIO.createImageInputStream(new SequenceInputStream(Collections.enumeration( + 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( Arrays.asList( - // TODO; Get rid of hardcoded data + extract method/class... - // TODO: - // - Create a BAIS with size large enough to keep JFIF structure incl tables and SOS, - // - Wrap in DataInput, - // - Insert width/height, component ids etc at correct place - // - Insert tables at correct place - - new ByteArrayInputStream(new byte[] {(byte) 0xff, (byte) 0xd8, // SOI - // SOF0 (short), length (short) - (byte) 0xff, (byte) 0xc0, 0x00, 0x11, // SOF0, 17 bytes - // bits (byte), width (short), height (short) - 0x08, 0x00, (byte) 0xe0, 0x00, (byte) 0xf0, - // num comp (byte), (id (byte) h/vsub (byte), qtsel (byte) * num comp) - 0x03, 0x00, 0x22, 0x00, 0x01, 0x11, 0x01, 0x02, 0x11, 0x02, -// 0x03, 0x00, 0x22, 0x00, 0x01, 0x11, 0x01, 0x02, 0x11, 0x01, - // DQT - (byte) 0xff, (byte) 0xdb, 0x00, 0x43, 0x00, - }), - // ... table data - new ByteArrayInputStream(qTables[0]), - new ByteArrayInputStream(new byte[] { - (byte) 0xff, (byte) 0xdb, 0x00, 0x43, 0x01, - }), - // ... table data - new ByteArrayInputStream(qTables[1]), - new ByteArrayInputStream(new byte[] { - (byte) 0xff, (byte) 0xdb, 0x00, 0x43, 0x02, - }), - // ... table data - new ByteArrayInputStream(qTables[2]), - - // DHT (DC) - new ByteArrayInputStream(new byte[] { - (byte) 0xff, (byte) 0xc4, 0x00, 0x1f, 0x00, - }), - // ... table data - new ByteArrayInputStream(dcTables[0]), - new ByteArrayInputStream(new byte[] { - (byte) 0xff, (byte) 0xc4, 0x00, 0x1f, 0x01, - }), - // ... table data - new ByteArrayInputStream(dcTables[1]), - new ByteArrayInputStream(new byte[] { - (byte) 0xff, (byte) 0xc4, 0x00, 0x1f, 0x02, - }), - // ... table data - new ByteArrayInputStream(dcTables[2]), - - // DHT (AC) - new ByteArrayInputStream(new byte[] { - (byte) 0xff, (byte) 0xc4, 0x00, (byte) 0xb5, 0x10, - }), - // ... table data - new ByteArrayInputStream(acTables[0]), - new ByteArrayInputStream(new byte[] { - (byte) 0xff, (byte) 0xc4, 0x00, (byte) 0xb5, 0x11, - }), - // ... table data - new ByteArrayInputStream(acTables[1]), - new ByteArrayInputStream(new byte[] { - (byte) 0xff, (byte) 0xc4, 0x00, (byte) 0xb5, 0x12, - }), - // ... table data - new ByteArrayInputStream(acTables[2]), - - new ByteArrayInputStream(new byte[] { - (byte) 0xff, (byte) 0xda, // SOS - // TODO: Figure out what the last 3 bytes are... - // Length: 12 (short), num comp (byte), (id (byte), dc/ac sel (byte) * num comp), ?? byte, ?? byte ?? byte - 0x00, 0x0C, 0x03, 0x00, 0x00, 0x01, 0x11, 0x02, 0x11, 0x00, 0x00, 0x00 -// 0x00, 0x0C, 0x03, 0x00, 0x00, 0x01, 0x11, 0x02, 0x12, 0x00, 0x63, 0x00 - }), + jfifBytes.createInputStream(), IIOUtil.createStreamAdapter(imageInput, stripTileByteCounts != null ? (int) stripTileByteCounts[i] : Short.MAX_VALUE), new ByteArrayInputStream(new byte[] {(byte) 0xff, (byte) 0xd9}) // EOI ) ))); - jpegReader.setInput(subStream); + jpegReader.setInput(stream); try { jpegParam.setSourceRegion(new Rectangle(0, 0, colsInTile, rowsInTile)); @@ -935,7 +937,7 @@ public class TIFFImageReader extends ImageReaderBase { jpegReader.read(0, jpegParam); } finally { - subStream.close(); + stream.close(); } if (abortRequested()) { From 02063c809eee4400d12c85be5072146c39aa3828 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Wed, 6 Feb 2013 17:02:51 +0100 Subject: [PATCH 13/58] TMI-TIFF: Added JPEG-compressed data to tests. --- .../plugins/tiff/TIFFImageReaderTest.java | 1 + .../src/test/resources/tiff/penguin_jpeg.tif | Bin 0 -> 111398 bytes 2 files changed, 1 insertion(+) create mode 100755 imageio/imageio-tiff/src/test/resources/tiff/penguin_jpeg.tif diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java index 1280af1a..7c8dbabd 100644 --- a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java +++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java @@ -58,6 +58,7 @@ public class TIFFImageReaderTest extends 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�Jcj76kj-p) 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 c^3!J}bKKK}D;2DZy;+Ka0rVa7M&sE3*(l~^eE Date: Thu, 7 Feb 2013 12:35:04 +0100 Subject: [PATCH 14/58] TMI-TIFF: Added type spec for CMYK+A separated images --- .../imageio/plugins/tiff/TIFFImageReader.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java index 918854c8..195859f9 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java @@ -343,12 +343,21 @@ public class TIFFImageReader extends ImageReaderBase { return ImageTypeSpecifier.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, dataType, false, false); } } + case 5: + if (bitsPerSample == 8 || bitsPerSample == 16) { + switch (planarConfiguration) { + case TIFFBaseline.PLANARCONFIG_CHUNKY: + return ImageTypeSpecifier.createInterleaved(cs, new int[] {0, 1, 2, 3, 4}, dataType, true, false); + 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); + } + } // TODO: More samples might be ok, if multiple alpha or unknown samples, consult ExtraSamples default: throw new IIOException( - String.format("Unsupported TIFF SamplesPerPixels/BitsPerSample combination for Separated TIFF (expected 4/8 or 4/16): %d/%s", samplesPerPixel, bitsPerSample) + String.format("Unsupported TIFF SamplesPerPixels/BitsPerSample combination for Separated TIFF (expected 4/8, 4/16, 5/8 or 5/16): %d/%s", samplesPerPixel, bitsPerSample) ); } case TIFFBaseline.PHOTOMETRIC_MASK: From 3b15653a10aebc7622d9cfd30606f8a2e76e0da4 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Thu, 14 Feb 2013 11:25:33 +0100 Subject: [PATCH 15/58] TMI-TIFF: Added support for premultiplied alpha (ExtraSamples == 1) + code clean-up. --- .../imageio/plugins/tiff/TIFFImageReader.java | 156 +++++++++--------- 1 file changed, 81 insertions(+), 75 deletions(-) diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java index 195859f9..77b74650 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java @@ -280,23 +280,25 @@ public class TIFFImageReader extends ImageReaderBase { } } case 4: - // TODO: Consult ExtraSamples! 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) { case TIFFBaseline.PLANARCONFIG_CHUNKY: if (bitsPerSample == 8 && cs.isCS_sRGB()) { 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: - 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 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: // Palette @@ -345,11 +347,14 @@ public class TIFFImageReader extends ImageReaderBase { } case 5: 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) { 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: - 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]); } -// System.err.println("qTables: " + qTables[0].length); - long[] dcTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_DCTABLES, "JPEGDCTables", true); byte[][] dcTables = new byte[3][(int) (dcTablesOffsets[1] - dcTablesOffsets[0])]; for (int j = 0; j < 3; j++) { imageInput.seek(dcTablesOffsets[j]); imageInput.readFully(dcTables[j]); } -// System.err.println("dcTables: " + dcTables[0].length); long[] acTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_ACTABLES, "JPEGACTables", true); byte[][] acTables = new byte[3][(int) (acTablesOffsets[1] - acTablesOffsets[0])]; @@ -849,7 +851,6 @@ public class TIFFImageReader extends ImageReaderBase { imageInput.seek(acTablesOffsets[j]); imageInput.readFully(acTables[j]); } -// System.err.println("acTables: " + acTables[0].length); // Read data processImageStarted(imageIndex); @@ -863,73 +864,9 @@ public class TIFFImageReader extends ImageReaderBase { int i = y * tilesAcross + x; 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( Arrays.asList( - jfifBytes.createInputStream(), + createJFIFStream(raster, stripTileWidth, stripTileHeight, qTables, dcTables, acTables), IIOUtil.createStreamAdapter(imageInput, stripTileByteCounts != null ? (int) stripTileByteCounts[i] : Short.MAX_VALUE), new ByteArrayInputStream(new byte[] {(byte) 0xff, (byte) 0xd9}) // EOI ) @@ -987,6 +924,75 @@ public class TIFFImageReader extends ImageReaderBase { 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, final WritableRaster raster, final int numBands, final int col, final int startRow, final int colsInStrip, final int rowsInStrip, final DataInput input) From b3672be1d4b0607d0079a7c4fb47377ca3c7b8cd Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Thu, 14 Feb 2013 12:31:00 +0100 Subject: [PATCH 16/58] TMI-TIFF: Replaced JPEG test case with more light-weight file. --- .../plugins/tiff/TIFFImageReaderTest.java | 2 +- .../src/test/resources/tiff/penguin_jpeg.tif | Bin 111398 -> 0 bytes .../src/test/resources/tiff/quad-jpeg.tif | Bin 0 -> 24428 bytes 3 files changed, 1 insertion(+), 1 deletion(-) delete mode 100755 imageio/imageio-tiff/src/test/resources/tiff/penguin_jpeg.tif create mode 100755 imageio/imageio-tiff/src/test/resources/tiff/quad-jpeg.tif diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java index 7c8dbabd..88ff7e55 100644 --- a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java +++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java @@ -58,7 +58,7 @@ public class TIFFImageReaderTest extends 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�Jcj76kj-p) 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 Date: Thu, 14 Feb 2013 12:37:05 +0100 Subject: [PATCH 17/58] TMI-TIFF: Code clean-up. --- .../imageio/plugins/tiff/TIFFImageReader.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java index 77b74650..cbd1c601 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java @@ -832,22 +832,22 @@ public class TIFFImageReader extends ImageReaderBase { // use only the first occurrence, and update selectors in SOF0 and SOS long[] qTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_QTABLES, "JPEGQTables", true); - byte[][] qTables = new byte[3][(int) (qTablesOffsets[1] - qTablesOffsets[0])]; // TODO: Using the offsets seems fragile.. Use fixed length?? - for (int j = 0; j < 3; j++) { + byte[][] qTables = new byte[qTablesOffsets.length][(int) (qTablesOffsets[1] - qTablesOffsets[0])]; // TODO: Using the offsets seems fragile.. Use fixed length?? + for (int j = 0; j < qTables.length; j++) { imageInput.seek(qTablesOffsets[j]); imageInput.readFully(qTables[j]); } long[] dcTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_DCTABLES, "JPEGDCTables", true); - byte[][] dcTables = new byte[3][(int) (dcTablesOffsets[1] - dcTablesOffsets[0])]; - for (int j = 0; j < 3; j++) { + byte[][] dcTables = new byte[dcTablesOffsets.length][(int) (dcTablesOffsets[1] - dcTablesOffsets[0])]; + for (int j = 0; j < dcTables.length; j++) { imageInput.seek(dcTablesOffsets[j]); imageInput.readFully(dcTables[j]); } long[] acTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_ACTABLES, "JPEGACTables", true); - byte[][] acTables = new byte[3][(int) (acTablesOffsets[1] - acTablesOffsets[0])]; - for (int j = 0; j < 3; j++) { + byte[][] acTables = new byte[acTablesOffsets.length][(int) (acTablesOffsets[1] - acTablesOffsets[0])]; + for (int j = 0; j < acTables.length; j++) { imageInput.seek(acTablesOffsets[j]); imageInput.readFully(acTables[j]); } From 42831ea65b6f8f52ae6d64d4ca12c5647a3e4eb1 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Thu, 14 Feb 2013 12:48:07 +0100 Subject: [PATCH 18/58] TMI-TIFF: Now passes YCbCrPositioning to upsampler. Replaced magic value with constant. --- .../com/twelvemonkeys/imageio/plugins/tiff/TIFFExtension.java | 3 +++ .../twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java | 4 ++-- .../imageio/plugins/tiff/YCbCrUpsamplerStream.java | 4 +++- .../imageio/plugins/tiff/YCbCrUpsamplerStreamTest.java | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFExtension.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFExtension.java index 2ce098d9..be17c4c4 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFExtension.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFExtension.java @@ -66,6 +66,9 @@ interface TIFFExtension { int SAMPLEFORMAT_FP = 3; int SAMPLEFORMAT_UNDEFINED = 4; + int YCBCR_POSITIONING_CENTERED = 1; + int YCBCR_POSITIONING_COSITED = 2; + // "Old-style" JPEG (obsolete) int JPEG_PROC_BASELINE = 1; int JPEG_PROC_LOSSLESS = 14; diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java index cbd1c601..2fccfe72 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java @@ -524,7 +524,7 @@ public class TIFFImageReader extends ImageReaderBase { throw new IIOException("TIFF PhotometricInterpreatation YCbCr requires BitsPerSample == [8,8,8]"); } - yCbCrPos = getValueAsIntWithDefault(TIFF.TAG_YCBCR_POSITIONING, 1); + yCbCrPos = getValueAsIntWithDefault(TIFF.TAG_YCBCR_POSITIONING, TIFFExtension.YCBCR_POSITIONING_CENTERED); Entry subSampling = currentIFD.getEntryById(TIFF.TAG_YCBCR_SUB_SAMPLING); @@ -586,7 +586,7 @@ public class TIFFImageReader extends ImageReaderBase { adapter = createDecoderInputStream(compression, adapter); if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR) { - adapter = new YCbCrUpsamplerStream(adapter, yCbCrSubsampling, colsInTile, yCbCrCoefficients); + adapter = new YCbCrUpsamplerStream(adapter, yCbCrSubsampling, yCbCrPos, colsInTile, yCbCrCoefficients); } // According to the spec, short/long/etc should follow order of containing stream diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java index 967e1ea7..703ef406 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java @@ -48,6 +48,7 @@ final class YCbCrUpsamplerStream extends FilterInputStream { private final int horizChromaSub; private final int vertChromaSub; + private final int yCbCrPos; private final double[] coefficients; private final int units; @@ -60,11 +61,12 @@ final class YCbCrUpsamplerStream extends FilterInputStream { int bufferLength; int bufferPos; - public YCbCrUpsamplerStream(InputStream stream, int[] chromaSub, int cols, double[] coefficients) { + public YCbCrUpsamplerStream(InputStream stream, int[] chromaSub, int yCbCrPos, int cols, double[] coefficients) { super(stream); this.horizChromaSub = chromaSub[0]; this.vertChromaSub = chromaSub[1]; + this.yCbCrPos = yCbCrPos; this.coefficients = Arrays.equals(TIFFImageReader.CCIR_601_1_COEFFICIENTS, coefficients) ? null : coefficients; // In TIFF, subsampled streams are stored in "units" of horiz * vert pixels. diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStreamTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStreamTest.java index 0713f5c7..836816aa 100644 --- a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStreamTest.java +++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStreamTest.java @@ -46,6 +46,6 @@ public class YCbCrUpsamplerStreamTest extends InputStreamAbstractTestCase { // TODO: Implement + add @Ignore for all tests that makes no sense for this class. @Override protected InputStream makeInputStream(byte[] pBytes) { - return new YCbCrUpsamplerStream(new ByteArrayInputStream(pBytes), new int[] {2, 2}, pBytes.length / 4, null); + return new YCbCrUpsamplerStream(new ByteArrayInputStream(pBytes), new int[] {2, 2}, TIFFExtension.YCBCR_POSITIONING_CENTERED, pBytes.length / 4, null); } } From 9c8ad3cb74cb81b2b69585c20943770a8121950d Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Thu, 14 Feb 2013 12:53:22 +0100 Subject: [PATCH 19/58] TMI-TIFF: Added warning for unknown YCbCrPositioning values. --- .../twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java index 2fccfe72..01398695 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java @@ -525,6 +525,9 @@ public class TIFFImageReader extends ImageReaderBase { } yCbCrPos = getValueAsIntWithDefault(TIFF.TAG_YCBCR_POSITIONING, TIFFExtension.YCBCR_POSITIONING_CENTERED); + if (yCbCrPos != TIFFExtension.YCBCR_POSITIONING_CENTERED && yCbCrPos != TIFFExtension.YCBCR_POSITIONING_COSITED) { + processWarningOccurred("Uknown TIFF YCbCrPositioning value, expected 1 or 2: " + yCbCrPos); + } Entry subSampling = currentIFD.getEntryById(TIFF.TAG_YCBCR_SUB_SAMPLING); From 10f501e919d145c9a5e4b52471d1fe5b939f3ed4 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Thu, 14 Feb 2013 14:14:43 +0100 Subject: [PATCH 20/58] TMI-XXXX: Fixed a typo in the JPEG docs. --- .../java/com/twelvemonkeys/imageio/metadata/jpeg/JPEG.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEG.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEG.java index aa9a3489..4d611ad6 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEG.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEG.java @@ -40,7 +40,8 @@ public interface JPEG { int SOI = 0xFFD8; /** End of Image segment marker (EOI). */ int EOI = 0xFFD9; - /** Start of Stream segment marker (SOS). */ + + /** Start of Scan segment marker (SOS). */ int SOS = 0xFFDA; /** Define Quantization Tables segment marker (DQT). */ From 94db6b4a6ffb8b1474bc77163ed7ad935ae5fe71 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Thu, 14 Feb 2013 14:40:03 +0100 Subject: [PATCH 21/58] TMI-TIFF: Simplified progress update. --- .../com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java index 01398695..08dd0e5e 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java @@ -820,7 +820,7 @@ public class TIFFImageReader extends ImageReaderBase { stream.close(); } - processImageProgress(100f * row / (float) height); + processImageProgress(100f); if (abortRequested()) { processReadAborted(); From f8369fb5b69111b2c4416a0bb5e2b8553bac09b5 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Fri, 15 Feb 2013 12:34:36 +0100 Subject: [PATCH 22/58] TMI-TIFF: Rolled back some breaking changes. --- .../imageio/metadata/jpeg/JPEGSegmentUtil.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java index 98a8f548..818716f5 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java @@ -197,7 +197,7 @@ public final class JPEGSegmentUtil { @Override public boolean contains(Object o) { - return o instanceof String; + return true; } } @@ -209,13 +209,13 @@ public final class JPEGSegmentUtil { @Override public List get(Object key) { - return containsKey(key) && JPEGSegment.isAppSegmentMarker((Integer) key) ? ALL_IDS : null; + return key instanceof Integer && JPEGSegment.isAppSegmentMarker((Integer) key) ? ALL_IDS : null; } @Override public boolean containsKey(Object key) { - return key instanceof Integer; + return true; } } From ed6223fcab2f4d77b0cf3afa57bed77f193800b4 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Fri, 15 Feb 2013 12:52:56 +0100 Subject: [PATCH 23/58] TMI-CORE: Fixed a reappearing bug in the JDK7 code, should now work properly with "broken" ICC color profiles . --- .../java/com/twelvemonkeys/imageio/color/ColorSpaces.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ColorSpaces.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ColorSpaces.java index 13fabe81..702a7cc3 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ColorSpaces.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ColorSpaces.java @@ -73,8 +73,10 @@ public final class ColorSpaces { private final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.color.debug")); - // JDK 7 seems to handle non-perceptual rendering intents gracefully, so we don't need to fiddle with the profiles - private final static boolean JDK_HANDLES_RENDERING_INTENTS = SystemUtil.isClassAvailable("java.lang.invoke.CallSite"); + // OpenJDK 7 seems to handle non-perceptual rendering intents gracefully, so we don't need to fiddle with the profiles. + // However, the later Oracle distribute JDK seems to include the color management code that has the known bugs... + private final static boolean JDK_HANDLES_RENDERING_INTENTS = + SystemUtil.isClassAvailable("java.lang.invoke.CallSite") && !SystemUtil.isClassAvailable("sun.java2d.cmm.kcms.CMM"); // NOTE: java.awt.color.ColorSpace.CS_* uses 1000-1004, we'll use 5000+ to not interfere with future additions From d8867736b72a59e938ad5906043fbbd0dbf41d0b Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Fri, 15 Feb 2013 12:55:37 +0100 Subject: [PATCH 24/58] TMI-TIFF: Fixed several bugs in the LittleEndianDataInputStream needed for proper TIFF output (should affect other things as well...) --- .../io/LittleEndianDataInputStream.java | 16 +- .../io/LittleEndianDataInputStreamTest.java | 208 +++++++ .../image/MappedBufferImage.java | 521 ++++++++++++++++-- .../com/twelvemonkeys/util/PersistentMap.java | 299 +++++++++- 4 files changed, 978 insertions(+), 66 deletions(-) create mode 100644 common/common-io/src/test/java/com/twelvemonkeys/io/LittleEndianDataInputStreamTest.java diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianDataInputStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianDataInputStream.java index adceaead..c20d8000 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianDataInputStream.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianDataInputStream.java @@ -158,7 +158,7 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da throw new EOFException(); } - return (short) (((byte2 << 24) >>> 16) + (byte1 << 24) >>> 24); + return (short) (((byte2 << 24) >>> 16) | (byte1 << 24) >>> 24); } /** @@ -198,7 +198,7 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da throw new EOFException(); } - return (char) (((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24)); + return (char) (((byte2 << 24) >>> 16) | ((byte1 << 24) >>> 24)); } @@ -221,8 +221,8 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da throw new EOFException(); } - return (byte4 << 24) + ((byte3 << 24) >>> 8) - + ((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24); + return (byte4 << 24) | ((byte3 << 24) >>> 8) + | ((byte2 << 24) >>> 16) | ((byte1 << 24) >>> 24); } /** @@ -248,10 +248,10 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da throw new EOFException(); } - return (byte8 << 56) + ((byte7 << 56) >>> 8) - + ((byte6 << 56) >>> 16) + ((byte5 << 56) >>> 24) - + ((byte4 << 56) >>> 32) + ((byte3 << 56) >>> 40) - + ((byte2 << 56) >>> 48) + ((byte1 << 56) >>> 56); + return (byte8 << 56) | ((byte7 << 56) >>> 8) + | ((byte6 << 56) >>> 16) | ((byte5 << 56) >>> 24) + | ((byte4 << 56) >>> 32) | ((byte3 << 56) >>> 40) + | ((byte2 << 56) >>> 48) | ((byte1 << 56) >>> 56); } /** diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/LittleEndianDataInputStreamTest.java b/common/common-io/src/test/java/com/twelvemonkeys/io/LittleEndianDataInputStreamTest.java new file mode 100644 index 00000000..1f7c551b --- /dev/null +++ b/common/common-io/src/test/java/com/twelvemonkeys/io/LittleEndianDataInputStreamTest.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2013, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.io; + +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +import static org.junit.Assert.*; + +/** + * LittleEndianDataInputStreamTest + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: LittleEndianDataInputStreamTest.java,v 1.0 15.02.13 11:04 haraldk Exp$ + */ +public class LittleEndianDataInputStreamTest { + @Test + public void testReadBoolean() throws IOException { + LittleEndianDataInputStream data = new LittleEndianDataInputStream(new ByteArrayInputStream(new byte[] {0, 1, 0x7f, (byte) 0xff})); + assertFalse(data.readBoolean()); + assertTrue(data.readBoolean()); + assertTrue(data.readBoolean()); + assertTrue(data.readBoolean()); + } + + @Test + public void testReadByte() throws IOException { + LittleEndianDataInputStream data = new LittleEndianDataInputStream(new ByteArrayInputStream( + new byte[] { + (byte) 0x00, (byte) 0x00, + (byte) 0x01, (byte) 0x00, + (byte) 0xff, (byte) 0xff, + (byte) 0x00, (byte) 0x80, + (byte) 0xff, (byte) 0x7f, + (byte) 0x00, (byte) 0x01, + } + + )); + + assertEquals(0, data.readByte()); + assertEquals(0, data.readByte()); + assertEquals(1, data.readByte()); + assertEquals(0, data.readByte()); + assertEquals(-1, data.readByte()); + assertEquals(-1, data.readByte()); + assertEquals(0, data.readByte()); + assertEquals(Byte.MIN_VALUE, data.readByte()); + assertEquals(-1, data.readByte()); + assertEquals(Byte.MAX_VALUE, data.readByte()); + assertEquals(0, data.readByte()); + assertEquals(1, data.readByte()); + } + + @Test + public void testReadUnsignedByte() throws IOException { + LittleEndianDataInputStream data = new LittleEndianDataInputStream(new ByteArrayInputStream( + new byte[] { + (byte) 0x00, (byte) 0x00, + (byte) 0x01, (byte) 0x00, + (byte) 0xff, (byte) 0xff, + (byte) 0x00, (byte) 0x80, + (byte) 0xff, (byte) 0x7f, + (byte) 0x00, (byte) 0x01, + } + + )); + + assertEquals(0, data.readUnsignedByte()); + assertEquals(0, data.readUnsignedByte()); + assertEquals(1, data.readUnsignedByte()); + assertEquals(0, data.readUnsignedByte()); + assertEquals(255, data.readUnsignedByte()); + assertEquals(255, data.readUnsignedByte()); + assertEquals(0, data.readUnsignedByte()); + assertEquals(128, data.readUnsignedByte()); + assertEquals(255, data.readUnsignedByte()); + assertEquals(Byte.MAX_VALUE, data.readUnsignedByte()); + assertEquals(0, data.readUnsignedByte()); + assertEquals(1, data.readUnsignedByte()); + } + + @Test + public void testReadShort() throws IOException { + LittleEndianDataInputStream data = new LittleEndianDataInputStream(new ByteArrayInputStream( + new byte[] { + (byte) 0x00, (byte) 0x00, + (byte) 0x01, (byte) 0x00, + (byte) 0xff, (byte) 0xff, + (byte) 0x00, (byte) 0x80, + (byte) 0xff, (byte) 0x7f, + (byte) 0x00, (byte) 0x01, + } + + )); + + assertEquals(0, data.readShort()); + assertEquals(1, data.readShort()); + assertEquals(-1, data.readShort()); + assertEquals(Short.MIN_VALUE, data.readShort()); + assertEquals(Short.MAX_VALUE, data.readShort()); + assertEquals(256, data.readShort()); + } + + @Test + public void testReadUnsignedShort() throws IOException { + LittleEndianDataInputStream data = new LittleEndianDataInputStream(new ByteArrayInputStream( + new byte[] { + (byte) 0x00, (byte) 0x00, + (byte) 0x01, (byte) 0x00, + (byte) 0xff, (byte) 0xff, + (byte) 0x00, (byte) 0x80, + (byte) 0xff, (byte) 0x7f, + (byte) 0x00, (byte) 0x01, + } + + )); + + assertEquals(0, data.readUnsignedShort()); + assertEquals(1, data.readUnsignedShort()); + assertEquals(Short.MAX_VALUE * 2 + 1, data.readUnsignedShort()); + assertEquals(Short.MAX_VALUE + 1, data.readUnsignedShort()); + assertEquals(Short.MAX_VALUE, data.readUnsignedShort()); + assertEquals(256, data.readUnsignedShort()); + } + + @Test + public void testReadInt() throws IOException { + LittleEndianDataInputStream data = new LittleEndianDataInputStream(new ByteArrayInputStream( + new byte[] { + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, + (byte) 0xff, (byte) 0x00, (byte) 0xff, (byte) 0x00, + (byte) 0x00, (byte) 0xff, (byte) 0x00, (byte) 0xff, + (byte) 0xbe, (byte) 0xba, (byte) 0xfe, (byte) 0xca, + (byte) 0xca, (byte) 0xfe, (byte) 0xd0, (byte) 0x0d, + } + + )); + + assertEquals(0, data.readInt()); + assertEquals(1, data.readInt()); + assertEquals(-1, data.readInt()); + assertEquals(Integer.MIN_VALUE, data.readInt()); + assertEquals(Integer.MAX_VALUE, data.readInt()); + assertEquals(16777216, data.readInt()); + assertEquals(0xff00ff, data.readInt()); + assertEquals(0xff00ff00, data.readInt()); + assertEquals(0xCafeBabe, data.readInt()); + assertEquals(0x0dd0feca, data.readInt()); + } + + @Test + public void testReadLong() throws IOException { + LittleEndianDataInputStream data = new LittleEndianDataInputStream(new ByteArrayInputStream( + new byte[] { + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, + (byte) 0x0d, (byte) 0xd0, (byte) 0xfe, (byte) 0xca, (byte) 0xbe, (byte) 0xba, (byte) 0xfe, (byte) 0xca, + } + + )); + + assertEquals(0, data.readLong()); + assertEquals(1, data.readLong()); + assertEquals(-1, data.readLong()); + assertEquals(Long.MIN_VALUE, data.readLong()); + assertEquals(Long.MAX_VALUE, data.readLong()); + assertEquals(72057594037927936L, data.readLong()); + assertEquals(0xCafeBabeL << 32 | 0xCafeD00dL, data.readLong()); + } +} diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/MappedBufferImage.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/MappedBufferImage.java index 7a31a119..8d69ffce 100644 --- a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/MappedBufferImage.java +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/MappedBufferImage.java @@ -30,6 +30,7 @@ package com.twelvemonkeys.image; import com.twelvemonkeys.imageio.util.ProgressListenerBase; import com.twelvemonkeys.lang.StringUtil; +import com.twelvemonkeys.util.LRUHashMap; import javax.imageio.ImageIO; import javax.imageio.ImageReadParam; @@ -38,17 +39,17 @@ import javax.imageio.ImageTypeSpecifier; import javax.imageio.stream.ImageInputStream; import javax.swing.*; import java.awt.*; -import java.awt.geom.AffineTransform; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; import java.awt.image.BufferedImage; import java.awt.image.DataBuffer; import java.io.File; import java.io.IOException; -import java.util.Iterator; -import java.util.Random; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.util.*; +import java.util.List; +import java.util.concurrent.*; /** * MappedBufferImage @@ -59,7 +60,7 @@ import java.util.concurrent.TimeUnit; */ public class MappedBufferImage { private static int threads = Runtime.getRuntime().availableProcessors(); - private static ExecutorService executorService = Executors.newFixedThreadPool(threads); + private static ExecutorService executorService = Executors.newFixedThreadPool(threads * 4); public static void main(String[] args) throws IOException { int argIndex = 0; @@ -91,8 +92,9 @@ public class MappedBufferImage { // TODO: Negotiate best layout according to the GraphicsConfiguration. - w = reader.getWidth(0); - h = reader.getHeight(0); + int sub = 1; + w = reader.getWidth(0) / sub; + h = reader.getHeight(0) / sub; // GraphicsConfiguration configuration = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); // ColorModel cm2 = configuration.getColorModel(cm.getTransparency()); @@ -111,8 +113,11 @@ public class MappedBufferImage { System.out.println("image = " + image); + // TODO: Display image while reading + ImageReadParam param = reader.getDefaultReadParam(); param.setDestination(image); + param.setSourceSubsampling(sub, sub, 0, 0); reader.addIIOReadProgressListener(new ConsoleProgressListener()); reader.read(0, param); @@ -166,7 +171,7 @@ public class MappedBufferImage { return size; } }; - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); JScrollPane scroll = new JScrollPane(new ImageComponent(image)); scroll.setBorder(BorderFactory.createEmptyBorder()); frame.add(scroll); @@ -184,13 +189,24 @@ public class MappedBufferImage { // NOTE: The createCompatibleDestImage takes the byte order/layout into account, unlike the cm.createCompatibleWritableRaster final BufferedImage output = new ResampleOp(width, height).createCompatibleDestImage(image, null); - final int inStep = (int) Math.ceil(image.getHeight() / (double) threads); - final int outStep = (int) Math.ceil(height / (double) threads); + final int steps = threads * height / 100; + final int inStep = (int) Math.ceil(image.getHeight() / (double) steps); + final int outStep = (int) Math.ceil(height / (double) steps); - final CountDownLatch latch = new CountDownLatch(threads); + final CountDownLatch latch = new CountDownLatch(steps); + + // System.out.println("Starting image scale on single thread, waiting for execution to complete..."); +// BufferedImage output = new ResampleOp(width, height, ResampleOp.FILTER_LANCZOS).filter(image, null); + System.out.printf("Started image scale on %d threads, waiting for execution to complete...\n", threads); + + System.out.print("["); + final int dotsPerStep = 78 / steps; + for (int j = 0; j < 78 - (steps * dotsPerStep); j++) { + System.out.print("."); + } // Resample image in slices - for (int i = 0; i < threads; i++) { + for (int i = 0; i < steps; i++) { final int inY = i * inStep; final int outY = i * outStep; final int inHeight = Math.min(inStep, image.getHeight() - inY); @@ -200,10 +216,12 @@ public class MappedBufferImage { try { BufferedImage in = image.getSubimage(0, inY, image.getWidth(), inHeight); BufferedImage out = output.getSubimage(0, outY, width, outHeight); - new ResampleOp(width, outHeight, ResampleOp.FILTER_LANCZOS).filter(in, out); -// new ResampleOp(width, outHeight, ResampleOp.FILTER_LANCZOS).resample(in, out, ResampleOp.createFilter(ResampleOp.FILTER_LANCZOS)); -// BufferedImage out = new ResampleOp(width, outHeight, ResampleOp.FILTER_LANCZOS).filter(in, null); -// ImageUtil.drawOnto(output.getSubimage(0, outY, width, outHeight), out); + new ResampleOp(width, outHeight, ResampleOp.FILTER_TRIANGLE).filter(in, out); +// new ResampleOp(width, outHeight, ResampleOp.FILTER_LANCZOS).filter(in, out); + + for (int j = 0; j < dotsPerStep; j++) { + System.out.print("."); + } } catch (RuntimeException e) { e.printStackTrace(); @@ -216,19 +234,17 @@ public class MappedBufferImage { }); } -// System.out.println("Starting image scale on single thread, waiting for execution to complete..."); -// BufferedImage output = new ResampleOp(width, height, ResampleOp.FILTER_LANCZOS).filter(image, null); - System.out.printf("Started image scale on %d threads, waiting for execution to complete...%n", threads); - Boolean done = null; try { done = latch.await(5L, TimeUnit.MINUTES); } catch (InterruptedException ignore) { } + System.out.println("]"); - System.out.printf("%s scaling image in %d ms%n", (done == null ? "Interrupted" : !done ? "Timed out" : "Done"), System.currentTimeMillis() - start); + System.out.printf("%s scaling image in %d ms\n", (done == null ? "Interrupted" : !done ? "Timed out" : "Done"), System.currentTimeMillis() - start); System.out.println("image = " + output); + return output; } @@ -358,10 +374,12 @@ public class MappedBufferImage { private static class ImageComponent extends JComponent implements Scrollable { private final BufferedImage image; private Paint texture; - double zoom = 1; + private double zoom = 1; public ImageComponent(final BufferedImage image) { - setOpaque(true); // Very important when subclassing JComponent... + setOpaque(true); // Very important when sub classing JComponent... + setDoubleBuffered(true); + this.image = image; } @@ -370,6 +388,68 @@ public class MappedBufferImage { super.addNotify(); texture = createTexture(); + + Rectangle bounds = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds(); + zoom = Math.min(1.0, Math.min(bounds.getWidth() / (double) image.getWidth(), bounds.getHeight() / (double) image.getHeight())); + + // TODO: Take scroll pane into account when zooming (center around center point) + AbstractAction zoomIn = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + System.err.println("ZOOM IN"); + setZoom(zoom * 2); + } + }; + + addAction(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, getToolkit().getMenuShortcutKeyMask()), zoomIn); + addAction(KeyStroke.getKeyStroke(KeyEvent.VK_ADD, getToolkit().getMenuShortcutKeyMask()), zoomIn); + addAction(KeyStroke.getKeyStroke(Character.valueOf('+'), 0), zoomIn); + addAction(KeyStroke.getKeyStroke(Character.valueOf('+'), getToolkit().getMenuShortcutKeyMask()), zoomIn); + AbstractAction zoomOut = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + System.err.println("ZOOM OUT"); + setZoom(zoom / 2); + } + }; + addAction(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, getToolkit().getMenuShortcutKeyMask()), zoomOut); + addAction(KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, getToolkit().getMenuShortcutKeyMask()), zoomOut); + addAction(KeyStroke.getKeyStroke(Character.valueOf('-'), 0), zoomOut); + addAction(KeyStroke.getKeyStroke(Character.valueOf('-'), getToolkit().getMenuShortcutKeyMask()), zoomOut); + AbstractAction zoomFit = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + System.err.println("ZOOM FIT"); +// Rectangle bounds = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds(); + Rectangle bounds = getVisibleRect(); + setZoom(Math.min(1.0, Math.min(bounds.getWidth() / (double) image.getWidth(), bounds.getHeight() / (double) image.getHeight()))); + } + }; + addAction(KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, getToolkit().getMenuShortcutKeyMask()), zoomFit); + addAction(KeyStroke.getKeyStroke(KeyEvent.VK_9, getToolkit().getMenuShortcutKeyMask()), zoomFit); + addAction(KeyStroke.getKeyStroke(KeyEvent.VK_0, getToolkit().getMenuShortcutKeyMask()), new AbstractAction() { + public void actionPerformed(ActionEvent e) { + System.err.println("ZOOM ACTUAL"); + setZoom(1); + } + }); + } + + private void setZoom(final double newZoom) { + if (newZoom != zoom) { + zoom = newZoom; + // TODO: Add PCL support for zoom and discard tiles cache based on property change + tiles = createTileCache(); + revalidate(); + repaint(); + } + } + + private Map createTileCache() { + return Collections.synchronizedMap(new SizedLRUMap(16 * 1024 * 1024)); + } + + private void addAction(final KeyStroke keyStroke, final AbstractAction action) { + UUID key = UUID.randomUUID(); + getInputMap(WHEN_IN_FOCUSED_WINDOW).put(keyStroke, key); + getActionMap().put(key, action); } private Paint createTexture() { @@ -392,10 +472,17 @@ public class MappedBufferImage { @Override protected void paintComponent(Graphics g) { + // TODO: Java 7 kills the performance from our custom painting... :-( + // TODO: Figure out why mouse wheel/track pad scroll repaints entire component, // unlike using the scroll bars of the JScrollPane. // Consider creating a custom mouse wheel listener as a workaround. + // TODO: Cache visible rect content in buffered/volatile image (s) + visible rect (+ zoom) to speed up repaints + // - Blit the cahced image (possibly translated) (onto itself?) + // - Paint only the necessary parts outside the cached image + // - Async rendering into cached image + // We want to paint only the visible part of the image Rectangle visible = getVisibleRect(); Rectangle clip = g.getClipBounds(); @@ -405,9 +492,28 @@ public class MappedBufferImage { g2.setPaint(texture); g2.fillRect(rect.x, rect.y, rect.width, rect.height); + /* + // Center image (might not be the best way to cooperate with the scroll pane) + Rectangle imageSize = new Rectangle((int) Math.round(image.getWidth() * zoom), (int) Math.round(image.getHeight() * zoom)); + if (imageSize.width < getWidth()) { + g2.translate((getWidth() - imageSize.width) / 2, 0); + } + if (imageSize.height < getHeight()) { + g2.translate(0, (getHeight() - imageSize.height) / 2); + } + */ + + // Zoom if (zoom != 1) { - AffineTransform transform = AffineTransform.getScaleInstance(zoom, zoom); - g2.setTransform(transform); + // NOTE: This helps mostly when scaling up, or scaling down less than 50% + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + + rect = new Rectangle( + (int) Math.round(rect.x / zoom), (int) Math.round(rect.y / zoom), + (int) Math.round(rect.width / zoom), (int) Math.round(rect.height / zoom) + ); + + rect = rect.intersection(new Rectangle(image.getWidth(), image.getHeight())); } long start = System.currentTimeMillis(); @@ -415,39 +521,308 @@ public class MappedBufferImage { System.err.println("repaint: " + (System.currentTimeMillis() - start) + " ms"); } - private void repaintImage(Rectangle rect, Graphics2D g2) { + static class Tile { + private final int size; + + private final int x; + private final int y; + + private final Reference data; + private final BufferedImage hardRef; + + Tile(int x, int y, BufferedImage data) { + this.x = x; + this.y = y; + this.data = new SoftReference(data); + + hardRef = data; + + size = 16 + data.getWidth() * data.getHeight() * data.getRaster().getNumDataElements() * sizeOf(data.getRaster().getTransferType()); + } + + private static int sizeOf(final int transferType) { + switch (transferType) { + case DataBuffer.TYPE_INT: + return 4; + case DataBuffer.TYPE_SHORT: + return 2; + case DataBuffer.TYPE_BYTE: + return 1; + default: + throw new IllegalArgumentException("Unsupported transfer type: " + transferType); + } + } + + public void drawTo(Graphics2D g) { + BufferedImage img = data.get(); + + if (img != null) { + g.drawImage(img, x, y, null); + } + +// g.setPaint(Color.GREEN); +// g.drawString(String.format("[%d, %d]", x, y), x + 20, y + 20); + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public int getWidth() { + BufferedImage img = data.get(); + return img != null ? img.getWidth() : -1; + } + + public int getHeight() { + BufferedImage img = data.get(); + return img != null ? img.getHeight() : -1; + } + + public Rectangle getRect() { + BufferedImage img = data.get(); + return img != null ? new Rectangle(x, y, img.getWidth(), img.getHeight()) : null; + } + + public Point getLocation() { + return new Point(x, y); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (other == null || getClass() != other.getClass()) { + return false; + } + + Tile tile = (Tile) other; + + return x == tile.x && y == tile.y; + } + + @Override + public int hashCode() { + return 997 * x + y; + } + + @Override + public String toString() { + return String.format("Tile[%d, %d, %d, %d]", x, y, getWidth(), getHeight()); + } + + public int size() { + return size; + } + } + + // TODO: Consider a fixed size (mem) LRUCache instead + Map tiles = createTileCache(); + + private void repaintImage(final Rectangle rect, final Graphics2D g2) { +// System.err.println("rect: " + rect); +// System.err.println("tiles: " + tiles.size()); + // TODO: Fix rounding errors + // FIx repaint bugs + try { // Paint tiles of the image, to preserve memory - int sliceSize = 200; + final int tileSize = 200; - int slicesW = rect.width / sliceSize; - int slicesH = rect.height / sliceSize; + int tilesW = 1 + rect.width / tileSize; + int tilesH = 1 + rect.height / tileSize; - for (int sliceY = 0; sliceY <= slicesH; sliceY++) { - for (int sliceX = 0; sliceX <= slicesW; sliceX++) { - int x = rect.x + sliceX * sliceSize; - int y = rect.y + sliceY * sliceSize; + for (int yTile = 0; yTile <= tilesH; yTile++) { + for (int xTile = 0; xTile <= tilesW; xTile++) { + // Image (source) coordinates + int x = rect.x + xTile * tileSize; + int y = rect.y + yTile * tileSize; - int w = sliceX == slicesW ? Math.min(sliceSize, rect.x + rect.width - x) : sliceSize; - int h = sliceY == slicesH ? Math.min(sliceSize, rect.y + rect.height - y) : sliceSize; + int w = xTile == tilesW ? Math.min(tileSize, rect.x + rect.width - x) : tileSize; + int h = yTile == tilesH ? Math.min(tileSize, rect.y + rect.height - y) : tileSize; if (w == 0 || h == 0) { continue; } // System.err.printf("%04d, %04d, %04d, %04d%n", x, y, w, h); - BufferedImage img = image.getSubimage(x, y, w, h); - g2.drawImage(img, x, y, null); + + // - Get tile from cache + // - If non-null, paint + // - If null, request data for later use, with callback, and return + // TODO: Could we use ImageProducer/ImageConsumer/ImageObserver interface?? + + // Destination (display) coordinates + int dstX = (int) Math.round(x * zoom); + int dstY = (int) Math.round(y * zoom); + int dstW = (int) Math.round(w * zoom); + int dstH = (int) Math.round(h * zoom); + + if (dstW == 0 || dstH == 0) { + continue; + } + + // Don't create overlapping/duplicate tiles... + // - Always start tile grid at 0,0 + // - Always occupy entire tile, unless edge + + // Source (original) coordinates + int tileSrcX = x - x % tileSize; + int tileSrcY = y - y % tileSize; +// final int tileSrcW = Math.min(tileSize, image.getWidth() - tileSrcX); +// final int tileSrcH = Math.min(tileSize, image.getHeight() - tileSrcY); + + // Destination (display) coordinates + int tileDstX = (int) Math.round(tileSrcX * zoom); + int tileDstY = (int) Math.round(tileSrcY * zoom); +// final int tileDstW = (int) Math.round(tileSrcW * zoom); +// final int tileDstH = (int) Math.round(tileSrcH * zoom); + + List points = new ArrayList(4); + points.add(new Point(tileDstX, tileDstY)); + if (tileDstX != dstX) { + points.add(new Point(tileDstX + tileSize, tileDstY)); + } + if (tileDstY != dstY) { + points.add(new Point(tileDstX, tileDstY + tileSize)); + } + if (tileDstX != dstX && tileDstY != dstY) { + points.add(new Point(tileDstX + tileSize, tileDstY + tileSize)); + } + + for (final Point point : points) { + Tile tile = tiles.get(point); + + if (tile != null) { + Reference img = tile.data; + if (img != null) { + tile.drawTo(g2); + continue; + } + else { + tiles.remove(point); + } + } + +// System.err.printf("Tile miss: [%d, %d]\n", dstX, dstY); + + // Dispatch to off-thread worker + final Map localTiles = tiles; + executorService.submit(new Runnable() { + public void run() { + // TODO: Fix rounding issues... Problem is that sometimes the srcW/srcH is 1 pixel off filling the tile... + int tileSrcX = (int) Math.round(point.x / zoom); + int tileSrcY = (int) Math.round(point.y / zoom); + int tileSrcW = Math.min(tileSize, image.getWidth() - tileSrcX); + int tileSrcH = Math.min(tileSize, image.getHeight() - tileSrcY); + int tileDstW = (int) Math.round(tileSrcW * zoom); + int tileDstH = (int) Math.round(tileSrcH * zoom); + + try { + // TODO: Consider comparing zoom/local zoom + if (localTiles != tiles) { + return; // Return early after re-zoom + } + + if (localTiles.containsKey(point)) { +// System.err.println("Skipping tile, already producing..."); + return; + } + + // Test against current view rect, to avoid computing tiles that will be thrown away immediately + // TODO: EDT safe? + if (!getVisibleRect().intersects(new Rectangle(point.x, point.y, tileDstW, tileDstH))) { + return; + } + +// System.err.printf("Creating tile: [%d, %d]\n", tileDstX, tileDstY); + + BufferedImage temp = getGraphicsConfiguration().createCompatibleImage(tileDstW, tileDstH); + final Tile tile = new Tile(point.x, point.y, temp); + localTiles.put(point, tile); + + Graphics2D graphics = temp.createGraphics(); + try { + Object hint = g2.getRenderingHint(RenderingHints.KEY_INTERPOLATION); + + if (hint != null) { + graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint); + } + + graphics.scale(zoom, zoom); + graphics.drawImage(image.getSubimage(tileSrcX, tileSrcY, tileSrcW, tileSrcH), 0, 0, null); + } + finally { + graphics.dispose(); + } + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + repaint(10, tile.x, tile.y, tile.getWidth(), tile.getHeight()); + } + }); + } + catch (Throwable t) { + localTiles.remove(point); + System.err.println("Boooo: " + t.getMessage()); + } + } + }); + } + } } - -// BufferedImage img = image.getSubimage(rect.x, rect.y, rect.width, rect.height); -// g2.drawImage(img, rect.x, rect.y, null); } catch (NullPointerException e) { // e.printStackTrace(); - // Happens whenever apple.awt.OSXCachingSufraceManager runs out of memory + // Happens whenever apple.awt.OSXCachingSurfaceManager runs out of memory // TODO: Figure out why repaint(x,y,w,h) doesn't work any more..? + System.err.println("Full repaint due to NullPointerException (probably out of memory)."); + repaint(); // NOTE: Might cause a brief flash while the component is redrawn + } + } + + private void repaintImage0(final Rectangle rect, final Graphics2D g2) { + g2.scale(zoom, zoom); + + try { + // Paint tiles of the image, to preserve memory + final int tileSize = 200; + + int tilesW = rect.width / tileSize; + int tilesH = rect.height / tileSize; + + for (int yTile = 0; yTile <= tilesH; yTile++) { + for (int xTile = 0; xTile <= tilesW; xTile++) { + // Image (source) coordinates + final int x = rect.x + xTile * tileSize; + final int y = rect.y + yTile * tileSize; + + final int w = xTile == tilesW ? Math.min(tileSize, rect.x + rect.width - x) : tileSize; + final int h = yTile == tilesH ? Math.min(tileSize, rect.y + rect.height - y) : tileSize; + + if (w == 0 || h == 0) { + continue; + } + +// System.err.printf("%04d, %04d, %04d, %04d%n", x, y, w, h); + + BufferedImage img = image.getSubimage(x, y, w, h); + g2.drawImage(img, x, y, null); + + } + } + } + catch (NullPointerException e) { +// e.printStackTrace(); + // Happens whenever apple.awt.OSXCachingSurfaceManager runs out of memory + // TODO: Figure out why repaint(x,y,w,h) doesn't work any more..? + System.err.println("Full repaint due to NullPointerException (probably out of memory)."); repaint(); // NOTE: Might cause a brief flash while the component is redrawn } } @@ -476,12 +851,68 @@ public class MappedBufferImage { } public boolean getScrollableTracksViewportWidth() { - return false; + return getWidth() > getPreferredSize().width; } public boolean getScrollableTracksViewportHeight() { + return getHeight() > getPreferredSize().height; + } + } + + final static class SizedLRUMap extends LRUHashMap { + int currentSize; + int maxSize; + + public SizedLRUMap(int pMaxSize) { + super(); // Note: super.maxSize doesn't count... + maxSize = pMaxSize; + } + + + protected int sizeOf(final Object pValue) { + ImageComponent.Tile cached = (ImageComponent.Tile) pValue; + + if (cached == null) { + return 0; + } + + return cached.size(); + } + + @Override + public V put(K pKey, V pValue) { + currentSize += sizeOf(pValue); + + V old = super.put(pKey, pValue); + if (old != null) { + currentSize -= sizeOf(old); + } + return old; + } + + @Override + public V remove(Object pKey) { + V old = super.remove(pKey); + if (old != null) { + currentSize -= sizeOf(old); + } + return old; + } + + @Override + protected boolean removeEldestEntry(Map.Entry pEldest) { + if (maxSize <= currentSize) { // NOTE: maxSize here is mem size + removeLRU(); + } return false; } + + @Override + public void removeLRU() { + while (maxSize <= currentSize) { // NOTE: maxSize here is mem size + super.removeLRU(); + } + } } private static class PaintDotsTask implements Runnable { diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/PersistentMap.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/PersistentMap.java index c35ece68..e87673b7 100755 --- a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/PersistentMap.java +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/PersistentMap.java @@ -28,6 +28,13 @@ package com.twelvemonkeys.util; +import com.twelvemonkeys.io.FileUtil; + +import java.io.*; +import java.util.*; + +import static com.twelvemonkeys.lang.Validate.notNull; + /** * PersistentMap * @@ -35,27 +42,293 @@ package com.twelvemonkeys.util; * @author last modified by $Author: haraldk$ * @version $Id: PersistentMap.java,v 1.0 May 13, 2009 2:31:29 PM haraldk Exp$ */ -public class PersistentMap { - // TODO: Implement Map - // TODO: Delta synchronization (db?) +public class PersistentMap extends AbstractMap{ + public static final FileFilter DIRECTORIES = new FileFilter() { + public boolean accept(File file) { + return file.isDirectory(); + } + + @Override + public String toString() { + return "[All folders]"; + } + }; + private static final String INDEX = ".index"; + + private final File root; + private final Map index = new LinkedHashMap(); + + private boolean mutable = true; + + + // Idea 2.0: + // - Create directory per hashCode + // - Create file per object in that directory + // - Name file after serialized form of key? Base64? + // - Special case for String/Integer/Long etc? + // - Or create index file in directory with serialized objects + name (uuid) of file + + // TODO: Consider single index file? Or a few? In root directory instead of each directory + // Consider a RAF/FileChannel approach instead of streams - how do we discard portions of a RAF? + // - Need to keep track of used/unused parts of file, scan for gaps etc...? + // - Need to periodically truncate and re-build the index (always as startup, then at every N puts/removes?) + + /*public */PersistentMap(String id) { + this(new File(FileUtil.getTempDirFile(), id)); + } + + public PersistentMap(File root) { + this.root = notNull(root); + + init(); + } + + private void init() { + if (!root.exists() && !root.mkdirs()) { + throw new IllegalStateException(String.format("'%s' does not exist/could not be created", root.getAbsolutePath())); + } + else if (!root.isDirectory()) { + throw new IllegalStateException(String.format("'%s' exists but is not a directory", root.getAbsolutePath())); + } + + if (!root.canRead()) { + throw new IllegalStateException(String.format("'%s' is not readable", root.getAbsolutePath())); + } + + if (!root.canWrite()) { + mutable = false; + } + + FileUtil.visitFiles(root, DIRECTORIES, new Visitor() { + public void visit(File dir) { + // - Read .index file + // - Add entries to index + ObjectInputStream input = null; + try { + input = new ObjectInputStream(new FileInputStream(new File(dir, INDEX))); + while (true) { + @SuppressWarnings({"unchecked"}) + K key = (K) input.readObject(); + String fileName = (String) input.readObject(); + index.put(key, UUID.fromString(fileName)); + } + } + catch (EOFException eof) { + // break here + } + catch (IOException e) { + throw new RuntimeException(e); + } + catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + finally { + FileUtil.close(input); + } + } + }); + } + + @Override + public Set> entrySet() { + return new AbstractSet>() { + @Override + public Iterator> iterator() { + return new Iterator>() { + Iterator> indexIter = index.entrySet().iterator(); + + public boolean hasNext() { + return indexIter.hasNext(); + } + + public Entry next() { + return new Entry() { + final Entry entry = indexIter.next(); + + public K getKey() { + return entry.getKey(); + } + + public V getValue() { + K key = entry.getKey(); + int hash = key != null ? key.hashCode() : 0; + return readVal(hash, entry.getValue()); + } + + public V setValue(V value) { + K key = entry.getKey(); + int hash = key != null ? key.hashCode() : 0; + return writeVal(key, hash, entry.getValue(), value, getValue()); + } + }; + } + + public void remove() { + indexIter.remove(); + } + }; + } + + @Override + public int size() { + return index.size(); + } + }; + } + + @Override + public int size() { + return index.size(); + } + + @Override + public V put(K key, V value) { + V oldVal = null; + + UUID uuid = index.get(key); + int hash = key != null ? key.hashCode() : 0; + + if (uuid != null) { + oldVal = readVal(hash, uuid); + } + + return writeVal(key, hash, uuid, value, oldVal); + } + + private V writeVal(K key, int hash, UUID uuid, V value, V oldVal) { + if (!mutable) { + throw new UnsupportedOperationException(); + } + + File bucket = new File(root, hashToFileName(hash)); + if (!bucket.exists() && !bucket.mkdirs()) { + throw new IllegalStateException(String.format("Could not create bucket '%s'", bucket)); + } + + if (uuid == null) { + // No uuid means new entry + uuid = UUID.randomUUID(); + + File idx = new File(bucket, INDEX); + + ObjectOutputStream output = null; + try { + output = new ObjectOutputStream(new FileOutputStream(idx, true)); + output.writeObject(key); + output.writeObject(uuid.toString()); + + index.put(key, uuid); + } + catch (IOException e) { + throw new RuntimeException(e); + } + finally { + FileUtil.close(output); + } + } + + File entry = new File(bucket, uuid.toString()); + if (value != null) { + ObjectOutputStream output = null; + try { + output = new ObjectOutputStream(new FileOutputStream(entry)); + output.writeObject(value); + + } + catch (IOException e) { + throw new RuntimeException(e); + } + finally { + FileUtil.close(output); + } + } + else if (entry.exists()) { + if (!entry.delete()) { + throw new IllegalStateException(String.format("'%s' could not be deleted", entry)); + } + } + + return oldVal; + } + + private String hashToFileName(int hash) { + return Integer.toString(hash, 16); + } + + @Override + public V get(Object key) { + UUID uuid = index.get(key); + + if (uuid != null) { + int hash = key != null ? key.hashCode() : 0; + return readVal(hash, uuid); + } + + return null; + } + + private V readVal(final int hash, final UUID uuid) { + File bucket = new File(root, hashToFileName(hash)); + File entry = new File(bucket, uuid.toString()); + + if (entry.exists()) { + ObjectInputStream input = null; + try { + input = new ObjectInputStream(new FileInputStream(entry)); + //noinspection unchecked + return (V) input.readObject(); + } + catch (IOException e) { + throw new RuntimeException(e); + } + catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + finally { + FileUtil.close(input); + } + } + + return null; + } + + @Override + public V remove(Object key) { + // TODO!!! + return super.remove(key); + } + + // TODO: Should override size, put, get, remove, containsKey and containsValue + + + } + + /* +Memory mapped file? +Delta sync? + Persistent format Header File ID 4-8 bytes - Size + Size (entries) - Entry pointer array block - Size - Next entry pointer block address - Entry 1 address + PersistentEntry pointer array block (PersistentEntry 0) + Size (bytes) + Next entry pointer block address (0 if last) + PersistentEntry 1 address/offset + key ... - Entry n address + PersistentEntry n address/offset + key + + PersistentEntry 1 + Size (bytes)? + Serialized value or pointer array block + ... + PersistentEntry n + Size (bytes)? + Serialized value or pointer array block - Entry 1 - ... - Entry n - */ \ No newline at end of file From a0d4973d7f1b2e849f758b812d88af3cf616c730 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Fri, 15 Feb 2013 13:08:14 +0100 Subject: [PATCH 25/58] TMC-XXXX: Updated author + version number. --- .../java/com/twelvemonkeys/io/LittleEndianDataInputStream.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianDataInputStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianDataInputStream.java index c20d8000..7eb31923 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianDataInputStream.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianDataInputStream.java @@ -65,7 +65,8 @@ import java.io.*; * @see java.io.DataOutput * * @author Elliotte Rusty Harold - * @version 1.0.3, 28 December 2002 + * @author Harald Kuhr + * @version 2 */ public class LittleEndianDataInputStream extends FilterInputStream implements DataInput { // TODO: Optimize by reading into a fixed size (8 bytes) buffer instead of individual read operations? From cc604e650b81acf3045d8af1a1bc91d3bd639607 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Mon, 18 Feb 2013 14:51:45 +0100 Subject: [PATCH 26/58] TMI-TIFF: Added more constants + debugging code for field names + suppressed unwanted warnings. --- .../imageio/metadata/exif/EXIF.java | 1 + .../imageio/metadata/exif/EXIFEntry.java | 22 +++++++++++++++++++ .../imageio/metadata/exif/TIFF.java | 9 +++++--- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIF.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIF.java index 9ebda2ab..7ad54e97 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIF.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIF.java @@ -35,6 +35,7 @@ package com.twelvemonkeys.imageio.metadata.exif; * @author last modified by $Author: haraldk$ * @version $Id: EXIF.java,v 1.0 Nov 11, 2009 5:36:04 PM haraldk Exp$ */ +@SuppressWarnings("UnusedDeclaration") public interface EXIF { // See http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html int TAG_EXPOSURE_TIME = 33434; diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java index d2c77af6..cf10da19 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java @@ -209,6 +209,28 @@ final class EXIFEntry extends AbstractEntry { return "PixelYDimension"; // TODO: More field names + /* + default: + Class[] classes = new Class[] {TIFF.class, EXIF.class}; + + for (Class cl : classes) { + Field[] fields = cl.getFields(); + + for (Field field : fields) { + try { + if (field.getType() == Integer.TYPE && field.getName().startsWith("TAG_")) { + if (field.get(null).equals(getIdentifier())) { + return StringUtil.lispToCamel(field.getName().substring(4).replace("_", "-").toLowerCase(), true); + } + } + } + catch (IllegalAccessException e) { + // Should never happen, but in case, abort + break; + } + } + } + */ } return null; diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java index 7201f785..574bfb4d 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java @@ -35,6 +35,7 @@ package com.twelvemonkeys.imageio.metadata.exif; * @author last modified by $Author: haraldk$ * @version $Id: TIFF.java,v 1.0 Nov 15, 2009 3:02:24 PM haraldk Exp$ */ +@SuppressWarnings("UnusedDeclaration") public interface TIFF { int TIFF_MAGIC = 42; @@ -98,6 +99,7 @@ public interface TIFF { int TAG_BITS_PER_SAMPLE = 258; int TAG_COMPRESSION = 259; int TAG_PHOTOMETRIC_INTERPRETATION = 262; + int TAG_FILL_ORDER = 266; int TAG_ORIENTATION = 274; int TAG_SAMPLES_PER_PIXEL = 277; int TAG_PLANAR_CONFIGURATION = 284; @@ -135,6 +137,7 @@ public interface TIFF { int TAG_IMAGE_DESCRIPTION = 270; int TAG_MAKE = 271; int TAG_MODEL = 272; + int TAG_PAGE_NUMBER = 297; int TAG_SOFTWARE = 305; int TAG_ARTIST = 315; int TAG_HOST_COMPUTER = 316; @@ -168,7 +171,7 @@ public interface TIFF { // "Old-style" JPEG (Obsolete) DO NOT WRITE! int TAG_OLD_JPEG_PROC = 512; - int TAG_OLD_JPEG_QTABLES = 519; - int TAG_OLD_JPEG_DCTABLES = 520; - int TAG_OLD_JPEG_ACTABLES = 521; + int TAG_OLD_JPEG_Q_TABLES = 519; + int TAG_OLD_JPEG_DC_TABLES = 520; + int TAG_OLD_JPEG_AC_TABLES = 521; } From bb7e1a4258dbc942db554e3e32e0553f5e4ebb3f Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Tue, 19 Feb 2013 12:23:49 +0100 Subject: [PATCH 27/58] TMC-XXXX: Added constructor exposing fast rendering flag. --- .../java/com/twelvemonkeys/image/BufferedImageIcon.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/BufferedImageIcon.java b/common/common-image/src/main/java/com/twelvemonkeys/image/BufferedImageIcon.java index d4339b14..649666ba 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/BufferedImageIcon.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/BufferedImageIcon.java @@ -53,11 +53,15 @@ public class BufferedImageIcon implements Icon { } public BufferedImageIcon(BufferedImage pImage, int pWidth, int pHeight) { + this(pImage, pWidth, pHeight, pImage.getWidth() == pWidth && pImage.getHeight() == pHeight); + } + + public BufferedImageIcon(BufferedImage pImage, int pWidth, int pHeight, boolean useFastRendering) { image = Validate.notNull(pImage, "image"); width = Validate.isTrue(pWidth > 0, pWidth, "width must be positive: %d"); height = Validate.isTrue(pHeight > 0, pHeight, "height must be positive: %d"); - fast = image.getWidth() == width && image.getHeight() == height; + fast = useFastRendering; } public int getIconHeight() { From c9809d0fa169ae3baf4d2afe9d8c2283584519b0 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Tue, 19 Feb 2013 13:02:45 +0100 Subject: [PATCH 28/58] TMI-CORE: Added zoom in/out/actual to image display, moved background to submenu. --- .../imageio/ImageReaderBase.java | 85 ++++++++++++++++--- 1 file changed, 71 insertions(+), 14 deletions(-) diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java index 4ca4c686..645bf527 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java @@ -418,6 +418,10 @@ public abstract class ImageReaderBase extends ImageReader { } private static class ImageLabel extends JLabel { + static final String ZOOM_IN = "zoom-in"; + static final String ZOOM_OUT = "zoom-out"; + static final String ZOOM_ACTUAL = "zoom-actual"; + Paint backgroundPaint; final Paint checkeredBG; @@ -435,9 +439,8 @@ public abstract class ImageReaderBase extends ImageReader { backgroundPaint = defaultBG != null ? defaultBG : checkeredBG; - JPopupMenu popup = createBackgroundPopup(); - - setComponentPopupMenu(popup); + setupActions(pImage); + setComponentPopupMenu(createPopupMenu()); addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { @@ -448,24 +451,50 @@ public abstract class ImageReaderBase extends ImageReader { }); } - private JPopupMenu createBackgroundPopup() { + private void setupActions(final BufferedImage pImage) { + // Mac weirdness... VK_MINUS/VK_PLUS seems to map to english key map always... + bindAction(new ZoomAction("Zoom in", pImage, 2), ZOOM_IN, KeyStroke.getKeyStroke('+'), KeyStroke.getKeyStroke(KeyEvent.VK_ADD, 0)); + bindAction(new ZoomAction("Zoom out", pImage, .5), ZOOM_OUT, KeyStroke.getKeyStroke('-'), KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, 0)); + bindAction(new ZoomAction("Zoom actual", pImage), ZOOM_ACTUAL, KeyStroke.getKeyStroke('0'), KeyStroke.getKeyStroke(KeyEvent.VK_0, 0)); + } + + private void bindAction(final AbstractAction action, final String key, final KeyStroke... keyStrokes) { + for (KeyStroke keyStroke : keyStrokes) { + getInputMap(WHEN_IN_FOCUSED_WINDOW).put(keyStroke, key); + } + + getActionMap().put(key, action); + } + + private JPopupMenu createPopupMenu() { JPopupMenu popup = new JPopupMenu(); + + popup.add(getActionMap().get(ZOOM_ACTUAL)); + popup.add(getActionMap().get(ZOOM_IN)); + popup.add(getActionMap().get(ZOOM_OUT)); + popup.addSeparator(); + ButtonGroup group = new ButtonGroup(); - addCheckBoxItem(new ChangeBackgroundAction("Checkered", checkeredBG), popup, group); - popup.addSeparator(); - addCheckBoxItem(new ChangeBackgroundAction("White", Color.WHITE), popup, group); - addCheckBoxItem(new ChangeBackgroundAction("Light", Color.LIGHT_GRAY), popup, group); - addCheckBoxItem(new ChangeBackgroundAction("Gray", Color.GRAY), popup, group); - addCheckBoxItem(new ChangeBackgroundAction("Dark", Color.DARK_GRAY), popup, group); - addCheckBoxItem(new ChangeBackgroundAction("Black", Color.BLACK), popup, group); - popup.addSeparator(); - addCheckBoxItem(new ChooseBackgroundAction("Choose...", defaultBG != null ? defaultBG : Color.BLUE), popup, group); + JMenu background = new JMenu("Background"); + popup.add(background); + + ChangeBackgroundAction checkered = new ChangeBackgroundAction("Checkered", checkeredBG); + checkered.putValue(Action.SELECTED_KEY, true); + addCheckBoxItem(checkered, background, group); + background.addSeparator(); + addCheckBoxItem(new ChangeBackgroundAction("White", Color.WHITE), background, group); + addCheckBoxItem(new ChangeBackgroundAction("Light", Color.LIGHT_GRAY), background, group); + addCheckBoxItem(new ChangeBackgroundAction("Gray", Color.GRAY), background, group); + addCheckBoxItem(new ChangeBackgroundAction("Dark", Color.DARK_GRAY), background, group); + addCheckBoxItem(new ChangeBackgroundAction("Black", Color.BLACK), background, group); + background.addSeparator(); + addCheckBoxItem(new ChooseBackgroundAction("Choose...", defaultBG != null ? defaultBG : Color.BLUE), background, group); return popup; } - private void addCheckBoxItem(final Action pAction, final JPopupMenu pPopup, final ButtonGroup pGroup) { + private void addCheckBoxItem(final Action pAction, final JMenu pPopup, final ButtonGroup pGroup) { JCheckBoxMenuItem item = new JCheckBoxMenuItem(pAction); pGroup.add(item); pPopup.add(item); @@ -553,6 +582,34 @@ public abstract class ImageReaderBase extends ImageReader { } } } + + private class ZoomAction extends AbstractAction { + private final BufferedImage image; + private final double zoomFactor; + + public ZoomAction(final String name, final BufferedImage image, final double zoomFactor) { + super(name); + this.image = image; + this.zoomFactor = zoomFactor; + } + + public ZoomAction(final String name, final BufferedImage image) { + this(name, image, 0); + } + + public void actionPerformed(ActionEvent e) { + if (zoomFactor <= 0) { + setIcon(new BufferedImageIcon(image)); + } + else { + Icon current = getIcon(); + int w = (int) Math.max(Math.min(current.getIconWidth() * zoomFactor, image.getWidth() * 16), image.getWidth() / 16); + int h = (int) Math.max(Math.min(current.getIconHeight() * zoomFactor, image.getHeight() * 16), image.getHeight() / 16); + + setIcon(new BufferedImageIcon(image, Math.max(w, 2), Math.max(h, 2), true)); + } + } + } } private static class ExitIfNoWindowPresentHandler extends WindowAdapter { From 2764460db5a8fc7fd961f0787ee3d070368e8ae3 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Tue, 19 Feb 2013 22:02:15 +0100 Subject: [PATCH 29/58] 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. --- .../imageio/plugins/tiff/TIFFImageReader.java | 134 ++++++++++-------- .../plugins/tiff/YCbCrUpsamplerStream.java | 64 +++++---- 2 files changed, 106 insertions(+), 92 deletions(-) diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java index 08dd0e5e..b6f57a39 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java @@ -110,7 +110,7 @@ public class TIFFImageReader extends ImageReaderBase { // TODO: Implement readAsRenderedImage to allow tiled renderImage? // For some layouts, we could do reads super-fast with a memory mapped buffer. // TODO: Implement readAsRaster directly - // TODO: IIOMetadata + // TODO: IIOMetadata (stay close to Sun's TIFF metadata) // TODOs Full BaseLine support: // TODO: Support ExtraSamples (an array, if multiple extra samples!) @@ -121,17 +121,14 @@ public class TIFFImageReader extends ImageReaderBase { // TODO: Support PlanarConfiguration 2 // TODO: Support ICCProfile (fully) // 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 34661 (JBIG)? Depends on JBIG ImageReader // DONE: // 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")); - - // 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}; + final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.tiff.debug")); private CompoundDirectory IFDs; 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 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("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: - // 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: Handle YCbCrSubsampling (up-scaler stream, or read data as-is + up-sample (sub-)raster after read? Apply smoothing?) case TIFFBaseline.PHOTOMETRIC_RGB: // RGB 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) { // getRawImageType does the lookup/conversion for these 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) { - 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); @@ -541,10 +537,13 @@ public class TIFFImageReader extends ImageReaderBase { if (yCbCrSubsampling.length != 2 || yCbCrSubsampling[0] != 1 && yCbCrSubsampling[0] != 2 && yCbCrSubsampling[0] != 4 || - yCbCrSubsampling[1] != 1 && yCbCrSubsampling[1] != 2 && yCbCrSubsampling[1] != 4 || - yCbCrSubsampling[0] < yCbCrSubsampling[1]) { + yCbCrSubsampling[1] != 1 && yCbCrSubsampling[1] != 2 && yCbCrSubsampling[1] != 4) { 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 { yCbCrSubsampling = new int[] {2, 2}; @@ -557,7 +556,7 @@ public class TIFFImageReader extends ImageReaderBase { } else { // 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? // 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 - // compatible approach..? + // TODO: Consider splicing the TAG_JPEG_TABLES into the streams for each tile, for a + // (slightly slower for multiple images, but) more compatible approach..? jpegReader.setInput(new ByteArrayImageInputStream(tablesValue)); @@ -745,61 +744,45 @@ public class TIFFImageReader extends ImageReaderBase { case TIFFExtension.COMPRESSION_OLD_JPEG: // JPEG ('old-style' JPEG, later overridden in Technote2) - // http://www.remotesensing.org/libtiff/TIFFTechNote2.html - // TODO: Issue warning? - int mode = getValueAsIntWithDefault(TIFF.TAG_OLD_JPEG_PROC, 1); - if (mode == TIFFExtension.JPEG_PROC_LOSSLESS) { - throw new IIOException("Unsupported TIFF JPEGProcessingMode: Lossless (14)"); - } - else if (mode != TIFFExtension.JPEG_PROC_BASELINE) { - throw new IIOException("Unknown TIFF JPEGProcessingMode value: " + mode); + // 512/JPEGProc: 1=Baseline, 14=Lossless (with Huffman coding), no default, although 1 is assumed if absent + int mode = getValueAsIntWithDefault(TIFF.TAG_OLD_JPEG_PROC, TIFFExtension.JPEG_PROC_BASELINE); + switch (mode) { + case TIFFExtension.JPEG_PROC_BASELINE: + break; // Supported + 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?? - // 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 jpegReader = new JPEGImageReader(getOriginatingProvider()); jpegParam = (JPEGImageReadParam) jpegReader.getDefaultReadParam(); + // 513/JPEGInterchangeFormat (may be absent...) int jpegOffset = getValueAsIntWithDefault(TIFF.TAG_JPEG_INTERCHANGE_FORMAT, -1); + // 514/JPEGInterchangeFormatLength (may be absent...) int jpegLenght = getValueAsIntWithDefault(TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, -1); + // TODO: 515/JPEGRestartInterval (may be absent) + + // Currently ignored + // 517/JPEGLosslessPredictors + // 518/JPEGPointTransforms ImageInputStream stream; if (jpegOffset != -1) { // 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) { - processWarningOccurred("Old-style JPEG compressed TIFF with JFIF stream encountered. Reading as single tile, ignoring tables."); + 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. 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); @@ -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."); + // 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), // use only the first occurrence, and update selectors in SOF0 and SOS - long[] qTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_QTABLES, "JPEGQTables", true); - byte[][] qTables = new byte[qTablesOffsets.length][(int) (qTablesOffsets[1] - qTablesOffsets[0])]; // TODO: Using the offsets seems fragile.. Use fixed length?? + 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 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++) { imageInput.seek(qTablesOffsets[j]); imageInput.readFully(qTables[j]); } - long[] dcTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_DCTABLES, "JPEGDCTables", true); - byte[][] dcTables = new byte[dcTablesOffsets.length][(int) (dcTablesOffsets[1] - dcTablesOffsets[0])]; + long[] dcTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_DC_TABLES, "JPEGDCTables", true); + 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++) { imageInput.seek(dcTablesOffsets[j]); imageInput.readFully(dcTables[j]); } - long[] acTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_ACTABLES, "JPEGACTables", true); - byte[][] acTables = new byte[acTablesOffsets.length][(int) (acTablesOffsets[1] - acTablesOffsets[0])]; + long[] acTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_AC_TABLES, "JPEGACTables", true); + 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++) { imageInput.seek(acTablesOffsets[j]); imageInput.readFully(acTables[j]); diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java index 703ef406..4f0e8bc7 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java @@ -44,15 +44,18 @@ import java.util.Arrays; * @version $Id: YCbCrUpsamplerStream.java,v 1.0 31.01.13 09:25 haraldk Exp$ */ 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 vertChromaSub; private final int yCbCrPos; + private final int columns; private final double[] coefficients; private final int units; private final int unitSize; + private final int padding; private final byte[] decodedRows; int decodedLength; int decodedPos; @@ -61,13 +64,14 @@ final class YCbCrUpsamplerStream extends FilterInputStream { int bufferLength; 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); this.horizChromaSub = chromaSub[0]; this.vertChromaSub = chromaSub[1]; 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. // 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... - units = cols / horizChromaSub; 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]; } - @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 { if (bufferPos >= bufferLength) { int pos = 0; @@ -125,8 +113,6 @@ final class YCbCrUpsamplerStream extends FilterInputStream { private void decodeRows() throws EOFException { decodedLength = decodedRows.length; - int rowOff = horizChromaSub * units; - for (int u = 0; u < units; u++) { if (bufferPos >= bufferLength) { 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 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 + 1] = cb; @@ -154,7 +147,7 @@ final class YCbCrUpsamplerStream extends FilterInputStream { } } - bufferPos+= 2; + bufferPos += 2; // CbCr bytes at end of unit } bufferPos = bufferLength; @@ -162,8 +155,20 @@ final class YCbCrUpsamplerStream extends FilterInputStream { } @Override - public final int read(byte[] b) throws IOException { - return read(b, 0, b.length); + public int read() throws IOException { + if (decodedLength < 0) { + return -1; + } + + if (decodedPos >= decodedLength) { + fetch(); + + if (decodedLength < 0) { + return -1; + } + } + + return decodedRows[decodedPos++]; } @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); System.arraycopy(decodedRows, decodedPos, b, off, read); decodedPos += read; @@ -259,7 +265,7 @@ final class YCbCrUpsamplerStream extends FilterInputStream { * Initializes tables for YCC->RGB color space conversion. */ private static void buildYCCtoRGBtable() { - if (DEBUG) { + if (TIFFImageReader.DEBUG) { System.err.println("Building YCC conversion table"); } From 59e5c3b3fde12f9dcad7b6afe1691c83bcf9602b Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Wed, 20 Feb 2013 10:44:42 +0100 Subject: [PATCH 30/58] TMI-TIFF: Fixed doc, removed todo. --- .../imageio/plugins/tiff/YCbCrUpsamplerStream.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java index 4f0e8bc7..e18bd152 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java @@ -37,14 +37,14 @@ import java.io.InputStream; import java.util.Arrays; /** - * YCbCrUpsamplerStream + * Input stream that provides on-the-fly conversion and upsampling of TIFF susampled YCbCr samples to (raw) RGB samples. * * @author Harald Kuhr * @author last modified by $Author: haraldk$ * @version $Id: YCbCrUpsamplerStream.java,v 1.0 31.01.13 09:25 haraldk Exp$ */ final class YCbCrUpsamplerStream extends FilterInputStream { - // NOTE: DO NOT MODIFY OR EXPOSE! + // NOTE: DO NOT MODIFY OR EXPOSE THIS ARRAY OUTSIDE PACKAGE! 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; @@ -147,7 +147,7 @@ final class YCbCrUpsamplerStream extends FilterInputStream { } } - bufferPos += 2; // CbCr bytes at end of unit + bufferPos += 2; // Skip CbCr bytes at end of unit } bufferPos = bufferLength; @@ -185,7 +185,6 @@ final class YCbCrUpsamplerStream extends FilterInputStream { } } - // TODO: Read no longer than until row boundary.... int read = Math.min(decodedLength - decodedPos, len); System.arraycopy(decodedRows, decodedPos, b, off, read); decodedPos += read; From df9f5734bd37462723089359453303b9e0d1ecdb Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Wed, 20 Feb 2013 10:45:20 +0100 Subject: [PATCH 31/58] TMI-CORE: Improved zoom quality when zooming out. --- .../main/java/com/twelvemonkeys/imageio/ImageReaderBase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java index 645bf527..35ac966a 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java @@ -606,7 +606,7 @@ public abstract class ImageReaderBase extends ImageReader { int w = (int) Math.max(Math.min(current.getIconWidth() * zoomFactor, image.getWidth() * 16), image.getWidth() / 16); int h = (int) Math.max(Math.min(current.getIconHeight() * zoomFactor, image.getHeight() * 16), image.getHeight() / 16); - setIcon(new BufferedImageIcon(image, Math.max(w, 2), Math.max(h, 2), true)); + setIcon(new BufferedImageIcon(image, Math.max(w, 2), Math.max(h, 2), w > image.getWidth() || h > image.getHeight())); } } } From 0319a6f84cdced5f5564df8eca01011867c67fcc Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Wed, 27 Feb 2013 12:07:34 +0100 Subject: [PATCH 32/58] TMI-CORE: Minor UI bugfix --- .../java/com/twelvemonkeys/imageio/ImageReaderBase.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java index 35ac966a..4a0d7243 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java @@ -480,7 +480,7 @@ public abstract class ImageReaderBase extends ImageReader { popup.add(background); ChangeBackgroundAction checkered = new ChangeBackgroundAction("Checkered", checkeredBG); - checkered.putValue(Action.SELECTED_KEY, true); + checkered.putValue(Action.SELECTED_KEY, backgroundPaint == checkeredBG); addCheckBoxItem(checkered, background, group); background.addSeparator(); addCheckBoxItem(new ChangeBackgroundAction("White", Color.WHITE), background, group); @@ -489,7 +489,9 @@ public abstract class ImageReaderBase extends ImageReader { addCheckBoxItem(new ChangeBackgroundAction("Dark", Color.DARK_GRAY), background, group); addCheckBoxItem(new ChangeBackgroundAction("Black", Color.BLACK), background, group); background.addSeparator(); - addCheckBoxItem(new ChooseBackgroundAction("Choose...", defaultBG != null ? defaultBG : Color.BLUE), background, group); + ChooseBackgroundAction chooseBackgroundAction = new ChooseBackgroundAction("Choose...", defaultBG != null ? defaultBG : Color.BLUE); + chooseBackgroundAction.putValue(Action.SELECTED_KEY, backgroundPaint == defaultBG); + addCheckBoxItem(chooseBackgroundAction, background, group); return popup; } From dd0f382d3c5736392f0f820fcc2f0affe534a622 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Thu, 28 Feb 2013 13:08:19 +0100 Subject: [PATCH 33/58] TMC-XXXX: Code cleaun-up and fixed spelling errors. --- .../twelvemonkeys/image/DiffusionDither.java | 80 +++++++++---------- .../com/twelvemonkeys/image/ImageUtil.java | 4 +- .../com/twelvemonkeys/image/IndexImage.java | 32 ++++---- 3 files changed, 59 insertions(+), 57 deletions(-) diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/DiffusionDither.java b/common/common-image/src/main/java/com/twelvemonkeys/image/DiffusionDither.java index 363b9542..48664fcc 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/DiffusionDither.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/DiffusionDither.java @@ -292,20 +292,20 @@ public class DiffusionDither implements BufferedImageOp, RasterOp { // When reference for column, add 1 to reference as this buffer is // offset from actual column position by one to allow FS to not check // left/right edge conditions - int[][] mCurrErr = new int[width + 2][3]; - int[][] mNextErr = new int[width + 2][3]; + int[][] currErr = new int[width + 2][3]; + int[][] nextErr = new int[width + 2][3]; // Random errors in [-1 .. 1] - for first row for (int i = 0; i < width + 2; i++) { // Note: This is broken for the strange cases where nextInt returns Integer.MIN_VALUE /* - mCurrErr[i][0] = (Math.abs(RANDOM.nextInt()) % (FS_SCALE * 2)) - FS_SCALE; - mCurrErr[i][1] = (Math.abs(RANDOM.nextInt()) % (FS_SCALE * 2)) - FS_SCALE; - mCurrErr[i][2] = (Math.abs(RANDOM.nextInt()) % (FS_SCALE * 2)) - FS_SCALE; + currErr[i][0] = (Math.abs(RANDOM.nextInt()) % (FS_SCALE * 2)) - FS_SCALE; + currErr[i][1] = (Math.abs(RANDOM.nextInt()) % (FS_SCALE * 2)) - FS_SCALE; + currErr[i][2] = (Math.abs(RANDOM.nextInt()) % (FS_SCALE * 2)) - FS_SCALE; */ - mCurrErr[i][0] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE; - mCurrErr[i][1] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE; - mCurrErr[i][2] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE; + currErr[i][0] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE; + currErr[i][1] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE; + currErr[i][2] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE; } // Temp buffers @@ -318,10 +318,10 @@ public class DiffusionDither implements BufferedImageOp, RasterOp { // Loop through image data for (int y = 0; y < height; y++) { // Clear out next error rows for colour errors - for (int i = mNextErr.length; --i >= 0;) { - mNextErr[i][0] = 0; - mNextErr[i][1] = 0; - mNextErr[i][2] = 0; + for (int i = nextErr.length; --i >= 0;) { + nextErr[i][0] = 0; + nextErr[i][1] = 0; + nextErr[i][2] = 0; } // Set up start column and limit @@ -348,7 +348,7 @@ public class DiffusionDither implements BufferedImageOp, RasterOp { for (int i = 0; i < 3; i++) { // Make a 28.4 FP number, add Error (with fraction), // rounding and truncate to int - inRGB[i] = ((inRGB[i] << 4) + mCurrErr[x + 1][i] + 0x08) >> 4; + inRGB[i] = ((inRGB[i] << 4) + currErr[x + 1][i] + 0x08) >> 4; // Clamp if (inRGB[i] > 255) { @@ -384,26 +384,26 @@ public class DiffusionDither implements BufferedImageOp, RasterOp { if (forward) { // Row 1 (y) // Update error in this pixel (x + 1) - mCurrErr[x + 2][0] += diff[0] * 7; - mCurrErr[x + 2][1] += diff[1] * 7; - mCurrErr[x + 2][2] += diff[2] * 7; + currErr[x + 2][0] += diff[0] * 7; + currErr[x + 2][1] += diff[1] * 7; + currErr[x + 2][2] += diff[2] * 7; // Row 2 (y + 1) // Update error in this pixel (x - 1) - mNextErr[x][0] += diff[0] * 3; - mNextErr[x][1] += diff[1] * 3; - mNextErr[x][2] += diff[2] * 3; + nextErr[x][0] += diff[0] * 3; + nextErr[x][1] += diff[1] * 3; + nextErr[x][2] += diff[2] * 3; // Update error in this pixel (x) - mNextErr[x + 1][0] += diff[0] * 5; - mNextErr[x + 1][1] += diff[1] * 5; - mNextErr[x + 1][2] += diff[2] * 5; + nextErr[x + 1][0] += diff[0] * 5; + nextErr[x + 1][1] += diff[1] * 5; + nextErr[x + 1][2] += diff[2] * 5; // Update error in this pixel (x + 1) // TODO: Consider calculating this using // error term = error - sum(error terms 1, 2 and 3) // See Computer Graphics (Foley et al.), p. 573 - mNextErr[x + 2][0] += diff[0]; // * 1; - mNextErr[x + 2][1] += diff[1]; // * 1; - mNextErr[x + 2][2] += diff[2]; // * 1; + nextErr[x + 2][0] += diff[0]; // * 1; + nextErr[x + 2][1] += diff[1]; // * 1; + nextErr[x + 2][2] += diff[2]; // * 1; // Next x++; @@ -417,26 +417,26 @@ public class DiffusionDither implements BufferedImageOp, RasterOp { else { // Row 1 (y) // Update error in this pixel (x - 1) - mCurrErr[x][0] += diff[0] * 7; - mCurrErr[x][1] += diff[1] * 7; - mCurrErr[x][2] += diff[2] * 7; + currErr[x][0] += diff[0] * 7; + currErr[x][1] += diff[1] * 7; + currErr[x][2] += diff[2] * 7; // Row 2 (y + 1) // Update error in this pixel (x + 1) - mNextErr[x + 2][0] += diff[0] * 3; - mNextErr[x + 2][1] += diff[1] * 3; - mNextErr[x + 2][2] += diff[2] * 3; + nextErr[x + 2][0] += diff[0] * 3; + nextErr[x + 2][1] += diff[1] * 3; + nextErr[x + 2][2] += diff[2] * 3; // Update error in this pixel (x) - mNextErr[x + 1][0] += diff[0] * 5; - mNextErr[x + 1][1] += diff[1] * 5; - mNextErr[x + 1][2] += diff[2] * 5; + nextErr[x + 1][0] += diff[0] * 5; + nextErr[x + 1][1] += diff[1] * 5; + nextErr[x + 1][2] += diff[2] * 5; // Update error in this pixel (x - 1) // TODO: Consider calculating this using // error term = error - sum(error terms 1, 2 and 3) // See Computer Graphics (Foley et al.), p. 573 - mNextErr[x][0] += diff[0]; // * 1; - mNextErr[x][1] += diff[1]; // * 1; - mNextErr[x][2] += diff[2]; // * 1; + nextErr[x][0] += diff[0]; // * 1; + nextErr[x][1] += diff[1]; // * 1; + nextErr[x][2] += diff[2]; // * 1; // Previous x--; @@ -450,9 +450,9 @@ public class DiffusionDither implements BufferedImageOp, RasterOp { // Make next error info current for next iteration int[][] temperr; - temperr = mCurrErr; - mCurrErr = mNextErr; - mNextErr = temperr; + temperr = currErr; + currErr = nextErr; + nextErr = temperr; // Toggle direction if (alternateScans) { diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/ImageUtil.java b/common/common-image/src/main/java/com/twelvemonkeys/image/ImageUtil.java index f9159a9e..721051b9 100644 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/ImageUtil.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/ImageUtil.java @@ -39,7 +39,7 @@ import java.util.Hashtable; * * @author Harald Kuhr * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/ImageUtil.java#3 $ + * @version $Id: common/common-image/src/main/java/com/twelvemonkeys/image/ImageUtil.java#3 $ */ public final class ImageUtil { // TODO: Split palette generation out, into ColorModel classes (?) @@ -845,11 +845,13 @@ public final class ImageUtil { BufferedImage temp = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null); if (cm instanceof IndexColorModel && pHints == Image.SCALE_SMOOTH) { + // TODO: DiffusionDither does not support transparency at the moment, this will create bad results new DiffusionDither((IndexColorModel) cm).filter(scaled, temp); } else { drawOnto(temp, scaled); } + scaled = temp; //long end = System.currentTimeMillis(); //System.out.println("Time: " + (end - start) + " ms"); diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/IndexImage.java b/common/common-image/src/main/java/com/twelvemonkeys/image/IndexImage.java index ef97bc5c..f7419204 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/IndexImage.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/IndexImage.java @@ -96,7 +96,7 @@ import java.util.Iterator; import java.util.List; /** - * This class implements an adaptive pallete generator to reduce images + * This class implements an adaptive palette generator to reduce images * to a variable number of colors. * It can also render images into fixed color pallettes. *

@@ -589,7 +589,7 @@ class IndexImage { /** * Gets an {@code IndexColorModel} from the given image. If the image has an * {@code IndexColorModel}, this will be returned. Otherwise, an {@code IndexColorModel} - * is created, using an adaptive pallete. + * is created, using an adaptive palette. * * @param pImage the image to get {@code IndexColorModel} from * @param pNumberOfColors the number of colors for the {@code IndexColorModel} @@ -637,7 +637,7 @@ class IndexImage { // We now have at least a buffered image, create model from it if (icm == null) { icm = createIndexColorModel(ImageUtil.toBuffered(image), pNumberOfColors, pHints); - } + } else if (!(icm instanceof InverseColorMapIndexColorModel)) { // If possible, use faster code icm = new InverseColorMapIndexColorModel(icm); @@ -648,7 +648,7 @@ class IndexImage { /** * Creates an {@code IndexColorModel} from the given image, using an adaptive - * pallete. + * palette. * * @param pImage the image to get {@code IndexColorModel} from * @param pNumberOfColors the number of colors for the {@code IndexColorModel} @@ -821,7 +821,7 @@ class IndexImage { /** * Converts the input image (must be {@code TYPE_INT_RGB} or * {@code TYPE_INT_ARGB}) to an indexed image. Generating an adaptive - * pallete (8 bit) from the color data in the image, and uses default + * palette (8 bit) from the color data in the image, and uses default * dither. *

* The image returned is a new image, the input image is not modified. @@ -865,7 +865,7 @@ class IndexImage { * Converts the input image (must be {@code TYPE_INT_RGB} or * {@code TYPE_INT_ARGB}) to an indexed image. If the palette image * uses an {@code IndexColorModel}, this will be used. Otherwise, generating an - * adaptive pallete (8 bit) from the given palette image. + * adaptive palette (8 bit) from the given palette image. * Dithering, transparency and color selection is controlled with the * {@code pHints}parameter. *

@@ -875,7 +875,7 @@ class IndexImage { * @param pPalette the Image to read color information from * @param pMatte the background color, used where the original image was * transparent - * @param pHints mHints that control output quality and speed. + * @param pHints hints that control output quality and speed. * @return the indexed BufferedImage. The image will be of type * {@code BufferedImage.TYPE_BYTE_INDEXED} or * {@code BufferedImage.TYPE_BYTE_BINARY}, and use an @@ -900,7 +900,7 @@ class IndexImage { /** * Converts the input image (must be {@code TYPE_INT_RGB} or * {@code TYPE_INT_ARGB}) to an indexed image. Generating an adaptive - * pallete with the given number of colors. + * palette with the given number of colors. * Dithering, transparency and color selection is controlled with the * {@code pHints}parameter. *

@@ -910,7 +910,7 @@ class IndexImage { * @param pNumberOfColors the number of colors for the image * @param pMatte the background color, used where the original image was * transparent - * @param pHints mHints that control output quality and speed. + * @param pHints hints that control output quality and speed. * @return the indexed BufferedImage. The image will be of type * {@code BufferedImage.TYPE_BYTE_INDEXED} or * {@code BufferedImage.TYPE_BYTE_BINARY}, and use an @@ -947,7 +947,7 @@ class IndexImage { /** * Converts the input image (must be {@code TYPE_INT_RGB} or * {@code TYPE_INT_ARGB}) to an indexed image. Using the supplied - * {@code IndexColorModel}'s pallete. + * {@code IndexColorModel}'s palette. * Dithering, transparency and color selection is controlled with the * {@code pHints} parameter. *

@@ -1064,7 +1064,7 @@ class IndexImage { /** * Converts the input image (must be {@code TYPE_INT_RGB} or * {@code TYPE_INT_ARGB}) to an indexed image. Generating an adaptive - * pallete with the given number of colors. + * palette with the given number of colors. * Dithering, transparency and color selection is controlled with the * {@code pHints}parameter. *

@@ -1072,7 +1072,7 @@ class IndexImage { * * @param pImage the BufferedImage to index * @param pNumberOfColors the number of colors for the image - * @param pHints mHints that control output quality and speed. + * @param pHints hints that control output quality and speed. * @return the indexed BufferedImage. The image will be of type * {@code BufferedImage.TYPE_BYTE_INDEXED} or * {@code BufferedImage.TYPE_BYTE_BINARY}, and use an @@ -1094,7 +1094,7 @@ class IndexImage { /** * Converts the input image (must be {@code TYPE_INT_RGB} or * {@code TYPE_INT_ARGB}) to an indexed image. Using the supplied - * {@code IndexColorModel}'s pallete. + * {@code IndexColorModel}'s palette. * Dithering, transparency and color selection is controlled with the * {@code pHints}parameter. *

@@ -1125,7 +1125,7 @@ class IndexImage { * Converts the input image (must be {@code TYPE_INT_RGB} or * {@code TYPE_INT_ARGB}) to an indexed image. If the palette image * uses an {@code IndexColorModel}, this will be used. Otherwise, generating an - * adaptive pallete (8 bit) from the given palette image. + * adaptive palette (8 bit) from the given palette image. * Dithering, transparency and color selection is controlled with the * {@code pHints}parameter. *

@@ -1133,7 +1133,7 @@ class IndexImage { * * @param pImage the BufferedImage to index * @param pPalette the Image to read color information from - * @param pHints mHints that control output quality and speed. + * @param pHints hints that control output quality and speed. * @return the indexed BufferedImage. The image will be of type * {@code BufferedImage.TYPE_BYTE_INDEXED} or * {@code BufferedImage.TYPE_BYTE_BINARY}, and use an @@ -1393,7 +1393,7 @@ class IndexImage { System.exit(5); } - // Create mHints + // Create hints int hints = DITHER_DEFAULT; if ("DIFFUSION".equalsIgnoreCase(dither)) { From 1ffe694538a6cf5294026afb6ade6df7a4e080a4 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Mon, 4 Mar 2013 14:48:50 +0100 Subject: [PATCH 34/58] TMS-XXXX: New map adapters for servlet context and request attributes + minor API tweaks. --- .../servlet/AbstractServletMapAdapter.java | 104 ++++-------- .../servlet/ServletAttributesMapAdapter.java | 134 +++++++++++++++ .../servlet/ServletHeadersMapAdapter.java | 27 +-- .../servlet/ServletParametersMapAdapter.java | 23 +-- .../twelvemonkeys/servlet/ServletUtil.java | 39 +++-- ...ervletAttributesMapAdapterContextTest.java | 156 ++++++++++++++++++ ...ervletAttributesMapAdapterRequestTest.java | 156 ++++++++++++++++++ ....java => ServletConfigMapAdapterTest.java} | 8 +- ...java => ServletHeadersMapAdapterTest.java} | 2 +- ...a => ServletParametersMapAdapterTest.java} | 2 +- 10 files changed, 543 insertions(+), 108 deletions(-) create mode 100644 servlet/src/main/java/com/twelvemonkeys/servlet/ServletAttributesMapAdapter.java create mode 100755 servlet/src/test/java/com/twelvemonkeys/servlet/ServletAttributesMapAdapterContextTest.java create mode 100755 servlet/src/test/java/com/twelvemonkeys/servlet/ServletAttributesMapAdapterRequestTest.java rename servlet/src/test/java/com/twelvemonkeys/servlet/{ServletConfigMapAdapterTestCase.java => ServletConfigMapAdapterTest.java} (94%) rename servlet/src/test/java/com/twelvemonkeys/servlet/{ServletHeadersMapAdapterTestCase.java => ServletHeadersMapAdapterTest.java} (94%) rename servlet/src/test/java/com/twelvemonkeys/servlet/{ServletParametersMapAdapterTestCase.java => ServletParametersMapAdapterTest.java} (94%) diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/AbstractServletMapAdapter.java b/servlet/src/main/java/com/twelvemonkeys/servlet/AbstractServletMapAdapter.java index 3d187231..7ee01471 100755 --- a/servlet/src/main/java/com/twelvemonkeys/servlet/AbstractServletMapAdapter.java +++ b/servlet/src/main/java/com/twelvemonkeys/servlet/AbstractServletMapAdapter.java @@ -1,7 +1,5 @@ package com.twelvemonkeys.servlet; -import com.twelvemonkeys.util.CollectionUtil; - import java.util.*; /** @@ -11,88 +9,53 @@ import java.util.*; * @author last modified by $Author: haku $ * @version $Id: AbstractServletMapAdapter.java#1 $ */ -abstract class AbstractServletMapAdapter extends AbstractMap> { - // TODO: This map is now a little too lazy.. Should cache entries too (instead?) ! - - private final static List NULL_LIST = new ArrayList(); - - private transient Map> cache = new HashMap>(); - private transient int size = -1; - private transient AbstractSet>> entries; +abstract class AbstractServletMapAdapter extends AbstractMap { + // TODO: This map is now a little too lazy.. Should cache entries! + private transient Set> entries; protected abstract Iterator keysImpl(); - protected abstract Iterator valuesImpl(String pName); + protected abstract T valueImpl(String pName); @Override - public List get(final Object pKey) { + public T get(final Object pKey) { if (pKey instanceof String) { - return getValues((String) pKey); + return valueImpl((String) pKey); } return null; } - private List getValues(final String pName) { - List values = cache.get(pName); - - if (values == null) { - //noinspection unchecked - Iterator headers = valuesImpl(pName); - - if (headers == null) { - cache.put(pName, NULL_LIST); - } - else { - values = toList(headers); - cache.put(pName, values); - } - } - - return values == NULL_LIST ? null : values; - } - - private static List toList(final Iterator pValues) { - List list = new ArrayList(); - CollectionUtil.addAll(list, pValues); - return Collections.unmodifiableList(list); - } - @Override public int size() { - if (size == -1) { - computeSize(); + // Avoid creating expensive entry set for computing size + int size = 0; + + for (Iterator names = keysImpl(); names.hasNext(); names.next()) { + size++; } return size; } - private void computeSize() { - size = 0; - - for (Iterator names = keysImpl(); names.hasNext(); names.next()) { - size++; - } - } - - public Set>> entrySet() { + public Set> entrySet() { if (entries == null) { - entries = new AbstractSet>>() { - public Iterator>> iterator() { - return new Iterator>>() { - Iterator headerNames = keysImpl(); + entries = new AbstractSet>() { + public Iterator> iterator() { + return new Iterator>() { + Iterator keys = keysImpl(); public boolean hasNext() { - return headerNames.hasNext(); + return keys.hasNext(); } - public Entry> next() { + public Entry next() { // TODO: Replace with cached lookup - return new HeaderEntry(headerNames.next()); + return new HeaderEntry(keys.next()); } public void remove() { - throw new UnsupportedOperationException(); + keys.remove(); } }; } @@ -106,34 +69,35 @@ abstract class AbstractServletMapAdapter extends AbstractMap> { - String headerName; + private class HeaderEntry implements Entry { + final String key; - public HeaderEntry(String pHeaderName) { - headerName = pHeaderName; + public HeaderEntry(final String pKey) { + key = pKey; } public String getKey() { - return headerName; + return key; } - public List getValue() { - return get(headerName); + public T getValue() { + return get(key); } - public List setValue(List pValue) { - throw new UnsupportedOperationException(); + public T setValue(final T pValue) { + // Write-through if supported + return put(key, pValue); } @Override public int hashCode() { - List value; - return (headerName == null ? 0 : headerName.hashCode()) ^ - ((value = getValue()) == null ? 0 : value.hashCode()); + T value = getValue(); + return (key == null ? 0 : key.hashCode()) ^ + (value == null ? 0 : value.hashCode()); } @Override - public boolean equals(Object pOther) { + public boolean equals(final Object pOther) { if (pOther == this) { return true; } diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/ServletAttributesMapAdapter.java b/servlet/src/main/java/com/twelvemonkeys/servlet/ServletAttributesMapAdapter.java new file mode 100644 index 00000000..227f12c7 --- /dev/null +++ b/servlet/src/main/java/com/twelvemonkeys/servlet/ServletAttributesMapAdapter.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2013, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.servlet; + +import javax.servlet.ServletContext; +import javax.servlet.ServletRequest; +import java.util.Enumeration; +import java.util.Iterator; + +import static com.twelvemonkeys.lang.Validate.notNull; + +/** + * ServletAttributesMap + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: ServletAttributesMap.java,v 1.0 01.03.13 10:34 haraldk Exp$ + */ +class ServletAttributesMapAdapter extends AbstractServletMapAdapter { + private final ServletContext context; + private final ServletRequest request; + + ServletAttributesMapAdapter(final ServletContext context) { + this(notNull(context), null); + } + + ServletAttributesMapAdapter(final ServletRequest request) { + this(null, notNull(request)); + } + + private ServletAttributesMapAdapter(final ServletContext context, final ServletRequest request) { + this.context = context; + this.request = request; + } + + @SuppressWarnings("unchecked") + private Enumeration getAttributeNames() { + return context != null ? context.getAttributeNames() : request.getAttributeNames(); + } + + private Object getAttribute(final String name) { + return context != null ? context.getAttribute(name) : request.getAttribute(name); + } + + private Object setAttribute(String name, Object value) { + Object oldValue = getAttribute(name); + + if (context != null) { + context.setAttribute(name, value); + } + else { + request.setAttribute(name, value); + } + + return oldValue; + } + + private Object removeAttribute(String name) { + Object oldValue = getAttribute(name); + + if (context != null) { + context.removeAttribute(name); + } + else { + request.removeAttribute(name); + } + + return oldValue; + } + + @Override + protected Iterator keysImpl() { + final Enumeration keys = getAttributeNames(); + return new Iterator() { + private String key; + + public boolean hasNext() { + return keys.hasMoreElements(); + } + + public String next() { + key = keys.nextElement(); + return key; + } + + public void remove() { + // Support removal of attribute through key iterator + removeAttribute(key); + } + }; + + } + + @Override + protected Object valueImpl(String pName) { + return getAttribute(pName); + } + + @Override + public Object put(String key, Object value) { + return setAttribute(key, value); + } + + @Override + public Object remove(Object key) { + return key instanceof String ? removeAttribute((String) key) : null; + } +} diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/ServletHeadersMapAdapter.java b/servlet/src/main/java/com/twelvemonkeys/servlet/ServletHeadersMapAdapter.java index 76054e11..b547929f 100755 --- a/servlet/src/main/java/com/twelvemonkeys/servlet/ServletHeadersMapAdapter.java +++ b/servlet/src/main/java/com/twelvemonkeys/servlet/ServletHeadersMapAdapter.java @@ -1,11 +1,11 @@ package com.twelvemonkeys.servlet; -import com.twelvemonkeys.lang.Validate; import com.twelvemonkeys.util.CollectionUtil; import javax.servlet.http.HttpServletRequest; -import java.util.Enumeration; -import java.util.Iterator; +import java.util.*; + +import static com.twelvemonkeys.lang.Validate.notNull; /** * ServletHeadersMapAdapter @@ -14,24 +14,29 @@ import java.util.Iterator; * @author last modified by $Author: haku $ * @version $Id: ServletHeadersMapAdapter.java#1 $ */ -class ServletHeadersMapAdapter extends AbstractServletMapAdapter { +class ServletHeadersMapAdapter extends AbstractServletMapAdapter> { protected final HttpServletRequest request; - public ServletHeadersMapAdapter(HttpServletRequest pRequest) { - request = Validate.notNull(pRequest, "request"); + public ServletHeadersMapAdapter(final HttpServletRequest pRequest) { + request = notNull(pRequest, "request"); } - protected Iterator valuesImpl(String pName) { - //noinspection unchecked + protected List valueImpl(final String pName) { + @SuppressWarnings("unchecked") Enumeration headers = request.getHeaders(pName); - return headers == null ? null : CollectionUtil.iterator(headers); + return headers == null ? null : toList(CollectionUtil.iterator(headers)); + } + + private static List toList(final Iterator pValues) { + List list = new ArrayList(); + CollectionUtil.addAll(list, pValues); + return Collections.unmodifiableList(list); } protected Iterator keysImpl() { - //noinspection unchecked + @SuppressWarnings("unchecked") Enumeration headerNames = request.getHeaderNames(); return headerNames == null ? null : CollectionUtil.iterator(headerNames); } - } diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/ServletParametersMapAdapter.java b/servlet/src/main/java/com/twelvemonkeys/servlet/ServletParametersMapAdapter.java index 01dca170..9324d2fb 100755 --- a/servlet/src/main/java/com/twelvemonkeys/servlet/ServletParametersMapAdapter.java +++ b/servlet/src/main/java/com/twelvemonkeys/servlet/ServletParametersMapAdapter.java @@ -1,11 +1,14 @@ package com.twelvemonkeys.servlet; -import com.twelvemonkeys.lang.Validate; import com.twelvemonkeys.util.CollectionUtil; -import javax.servlet.http.HttpServletRequest; +import javax.servlet.ServletRequest; +import java.util.Arrays; import java.util.Enumeration; import java.util.Iterator; +import java.util.List; + +import static com.twelvemonkeys.lang.Validate.notNull; /** * ServletParametersMapAdapter @@ -14,23 +17,23 @@ import java.util.Iterator; * @author last modified by $Author: haku $ * @version $Id: ServletParametersMapAdapter.java#1 $ */ -class ServletParametersMapAdapter extends AbstractServletMapAdapter { +class ServletParametersMapAdapter extends AbstractServletMapAdapter> { + // TODO: Be able to piggyback on HttpServletRequest.getParameterMap when available? - protected final HttpServletRequest request; + protected final ServletRequest request; - public ServletParametersMapAdapter(HttpServletRequest pRequest) { - request = Validate.notNull(pRequest, "request"); + public ServletParametersMapAdapter(final ServletRequest pRequest) { + request = notNull(pRequest, "request"); } - protected Iterator valuesImpl(String pName) { + protected List valueImpl(String pName) { String[] values = request.getParameterValues(pName); - return values == null ? null : CollectionUtil.iterator(values); + return values == null ? null : Arrays.asList(values); } protected Iterator keysImpl() { - //noinspection unchecked + @SuppressWarnings("unchecked") Enumeration names = request.getParameterNames(); return names == null ? null : CollectionUtil.iterator(names); } - } \ No newline at end of file diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/ServletUtil.java b/servlet/src/main/java/com/twelvemonkeys/servlet/ServletUtil.java index 16772271..41072fa8 100755 --- a/servlet/src/main/java/com/twelvemonkeys/servlet/ServletUtil.java +++ b/servlet/src/main/java/com/twelvemonkeys/servlet/ServletUtil.java @@ -50,7 +50,7 @@ import java.util.Map; /** * Various servlet related helper methods. * - * @author Harald Kuhr + * @author Harald Kuhr * @author Eirik Torske * @author last modified by $Author: haku $ * @version $Id: ServletUtil.java#3 $ @@ -544,7 +544,7 @@ public final class ServletUtil { /** * Returns a {@code URL} containing the real path for a given virtual * path, on URL form. - * Note that this mehtod will return {@code null} for all the same reasons + * Note that this method will return {@code null} for all the same reasons * as {@code ServletContext.getRealPath(java.lang.String)} does. * * @param pContext the servlet context @@ -566,7 +566,7 @@ public final class ServletUtil { } /** - * Gets the temp directory for the given {@code ServletContext} (webapp). + * Gets the temp directory for the given {@code ServletContext} (web app). * * @param pContext the servlet context * @return the temp directory @@ -634,13 +634,30 @@ public final class ServletUtil { return new ServletConfigMapAdapter(pContext); } - // TODO? -// public static Map attributesAsMap(final ServletContext pContext) { -// } -// -// public static Map attributesAsMap(final ServletRequest pRequest) { -// } -// + /** + * Creates an modifiable {@code Map} view of the given + * {@code ServletContext}s attributes. + * + * @param pContext the servlet context + * @return a {@code Map} view of the attributes + * @throws IllegalArgumentException if {@code pContext} is {@code null} + */ + public static Map attributesAsMap(final ServletContext pContext) { + return new ServletAttributesMapAdapter(pContext); + } + + /** + * Creates an modifiable {@code Map} view of the given + * {@code ServletRequest}s attributes. + * + * @param pRequest the servlet request + * @return a {@code Map} view of the attributes + * @throws IllegalArgumentException if {@code pContext} is {@code null} + */ + public static Map attributesAsMap(final ServletRequest pRequest) { + return new ServletAttributesMapAdapter(pRequest); + } + /** * Creates an unmodifiable {@code Map} view of the given * {@code HttpServletRequest}s request parameters. @@ -649,7 +666,7 @@ public final class ServletUtil { * @return a {@code Map} view of the request parameters * @throws IllegalArgumentException if {@code pRequest} is {@code null} */ - public static Map> parametersAsMap(final HttpServletRequest pRequest) { + public static Map> parametersAsMap(final ServletRequest pRequest) { return new ServletParametersMapAdapter(pRequest); } diff --git a/servlet/src/test/java/com/twelvemonkeys/servlet/ServletAttributesMapAdapterContextTest.java b/servlet/src/test/java/com/twelvemonkeys/servlet/ServletAttributesMapAdapterContextTest.java new file mode 100755 index 00000000..759714e4 --- /dev/null +++ b/servlet/src/test/java/com/twelvemonkeys/servlet/ServletAttributesMapAdapterContextTest.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2013, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.servlet; + +import com.twelvemonkeys.util.MapAbstractTestCase; +import org.mockito.Mockito; + +import javax.servlet.ServletContext; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +import static org.mockito.Mockito.mock; + +/** + * ServletConfigMapAdapterTestCase + *

+ * + * @author Harald Kuhr + * @version $Id: ServletAttributesMapAdapterTestCase.java#1 $ + */ +public class ServletAttributesMapAdapterContextTest extends MapAbstractTestCase { + private static final String ATTRIB_VALUE_ETAG = "\"1234567890abcdef\""; + private static final Date ATTRIB_VALUE_DATE = new Date(); + private static final List ATTRIB_VALUE_FOO = Arrays.asList(1, 2); + + @Override + public boolean isTestSerialization() { + return false; + } + + @Override + public boolean isAllowNullKey() { + return false; // Makes no sense... + } + + @Override + public boolean isAllowNullValue() { + return false; // Should be allowed, but the tests don't handle the put(foo, null) == remove(foo) semantics + } + + public Map makeEmptyMap() { + MockServletContextImpl context = mock(MockServletContextImpl.class, Mockito.CALLS_REAL_METHODS); + context.attributes = createAttributes(false); + + return new ServletAttributesMapAdapter(context); + } + + @Override + public Map makeFullMap() { + MockServletContextImpl context = mock(MockServletContextImpl.class, Mockito.CALLS_REAL_METHODS); + context.attributes = createAttributes(true); + + return new ServletAttributesMapAdapter(context); + } + + private Map createAttributes(boolean initialValues) { + Map map = new ConcurrentHashMap(); + + if (initialValues) { + String[] sampleKeys = (String[]) getSampleKeys(); + for (int i = 0; i < sampleKeys.length; i++) { + map.put(sampleKeys[i], getSampleValues()[i]); + } + } + + return map; + } + + @Override + public Object[] getSampleKeys() { + return new String[] {"Date", "ETag", "X-Foo"}; + } + + @Override + public Object[] getSampleValues() { + return new Object[] {ATTRIB_VALUE_DATE, ATTRIB_VALUE_ETAG, ATTRIB_VALUE_FOO}; + } + + @Override + public Object[] getNewSampleValues() { + // Needs to be same length but different values + return new Object[] {new Date(-1l), "foo/bar", Arrays.asList(2, 3, 4)}; + } + + @SuppressWarnings("unchecked") + @Override + public void testMapPutNullValue() { + // Special null semantics + resetFull(); + + int size = map.size(); + String key = getClass().getName() + ".someNewKey"; + map.put(key, null); + assertEquals(size, map.size()); + assertFalse(map.containsKey(key)); + + map.put(getSampleKeys()[0], null); + assertEquals(size - 1, map.size()); + assertFalse(map.containsKey(getSampleKeys()[0])); + + map.remove(getSampleKeys()[1]); + assertEquals(size - 2, map.size()); + assertFalse(map.containsKey(getSampleKeys()[1])); + } + + private static abstract class MockServletContextImpl implements ServletContext { + Map attributes; + + public Object getAttribute(String name) { + return attributes.get(name); + } + + public Enumeration getAttributeNames() { + return Collections.enumeration(attributes.keySet()); + } + + public void setAttribute(String name, Object o) { + if (o == null) { + attributes.remove(name); + } + else { + attributes.put(name, o); + } + } + + public void removeAttribute(String name) { + attributes.remove(name); + } + } +} diff --git a/servlet/src/test/java/com/twelvemonkeys/servlet/ServletAttributesMapAdapterRequestTest.java b/servlet/src/test/java/com/twelvemonkeys/servlet/ServletAttributesMapAdapterRequestTest.java new file mode 100755 index 00000000..ee461352 --- /dev/null +++ b/servlet/src/test/java/com/twelvemonkeys/servlet/ServletAttributesMapAdapterRequestTest.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2013, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.servlet; + +import com.twelvemonkeys.util.MapAbstractTestCase; +import org.mockito.Mockito; + +import javax.servlet.ServletRequest; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +import static org.mockito.Mockito.mock; + +/** + * ServletConfigMapAdapterTestCase + *

+ * + * @author Harald Kuhr + * @version $Id: ServletAttributesMapAdapterTestCase.java#1 $ + */ +public class ServletAttributesMapAdapterRequestTest extends MapAbstractTestCase { + private static final String ATTRIB_VALUE_ETAG = "\"1234567890abcdef\""; + private static final Date ATTRIB_VALUE_DATE = new Date(); + private static final List ATTRIB_VALUE_FOO = Arrays.asList(1, 2); + + @Override + public boolean isTestSerialization() { + return false; + } + + @Override + public boolean isAllowNullKey() { + return false; // Makes no sense... + } + + @Override + public boolean isAllowNullValue() { + return false; // Should be allowed, but the tests don't handle the put(foo, null) == remove(foo) semantics + } + + public Map makeEmptyMap() { + MockServletRequestImpl request = mock(MockServletRequestImpl.class, Mockito.CALLS_REAL_METHODS); + request.attributes = createAttributes(false); + + return new ServletAttributesMapAdapter(request); + } + + @Override + public Map makeFullMap() { + MockServletRequestImpl request = mock(MockServletRequestImpl.class, Mockito.CALLS_REAL_METHODS); + request.attributes = createAttributes(true); + + return new ServletAttributesMapAdapter(request); + } + + private Map createAttributes(boolean initialValues) { + Map map = new ConcurrentHashMap(); + + if (initialValues) { + String[] sampleKeys = (String[]) getSampleKeys(); + for (int i = 0; i < sampleKeys.length; i++) { + map.put(sampleKeys[i], getSampleValues()[i]); + } + } + + return map; + } + + @Override + public Object[] getSampleKeys() { + return new String[] {"Date", "ETag", "X-Foo"}; + } + + @Override + public Object[] getSampleValues() { + return new Object[] {ATTRIB_VALUE_DATE, ATTRIB_VALUE_ETAG, ATTRIB_VALUE_FOO}; + } + + @Override + public Object[] getNewSampleValues() { + // Needs to be same length but different values + return new Object[] {new Date(-1l), "foo/bar", Arrays.asList(2, 3, 4)}; + } + + @SuppressWarnings("unchecked") + @Override + public void testMapPutNullValue() { + // Special null semantics + resetFull(); + + int size = map.size(); + String key = getClass().getName() + ".someNewKey"; + map.put(key, null); + assertEquals(size, map.size()); + assertFalse(map.containsKey(key)); + + map.put(getSampleKeys()[0], null); + assertEquals(size - 1, map.size()); + assertFalse(map.containsKey(getSampleKeys()[0])); + + map.remove(getSampleKeys()[1]); + assertEquals(size - 2, map.size()); + assertFalse(map.containsKey(getSampleKeys()[1])); + } + + private static abstract class MockServletRequestImpl implements ServletRequest { + Map attributes; + + public Object getAttribute(String name) { + return attributes.get(name); + } + + public Enumeration getAttributeNames() { + return Collections.enumeration(attributes.keySet()); + } + + public void setAttribute(String name, Object o) { + if (o == null) { + attributes.remove(name); + } + else { + attributes.put(name, o); + } + } + + public void removeAttribute(String name) { + attributes.remove(name); + } + } +} diff --git a/servlet/src/test/java/com/twelvemonkeys/servlet/ServletConfigMapAdapterTestCase.java b/servlet/src/test/java/com/twelvemonkeys/servlet/ServletConfigMapAdapterTest.java similarity index 94% rename from servlet/src/test/java/com/twelvemonkeys/servlet/ServletConfigMapAdapterTestCase.java rename to servlet/src/test/java/com/twelvemonkeys/servlet/ServletConfigMapAdapterTest.java index 1994fa4b..9b5c405a 100755 --- a/servlet/src/test/java/com/twelvemonkeys/servlet/ServletConfigMapAdapterTestCase.java +++ b/servlet/src/test/java/com/twelvemonkeys/servlet/ServletConfigMapAdapterTest.java @@ -16,7 +16,7 @@ import java.net.MalformedURLException; * @author Harald Kuhr * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletConfigMapAdapterTestCase.java#3 $ */ -public abstract class ServletConfigMapAdapterTestCase extends MapAbstractTestCase { +public abstract class ServletConfigMapAdapterTest extends MapAbstractTestCase { public boolean isPutAddSupported() { return false; @@ -148,7 +148,7 @@ public abstract class ServletConfigMapAdapterTestCase extends MapAbstractTestCas } } - public static final class ServletConfigMapTestCase extends ServletConfigMapAdapterTestCase { + public static final class ServletConfigMapTestCase extends ServletConfigMapAdapterTest { public Map makeEmptyMap() { ServletConfig config = new TestConfig(); @@ -162,7 +162,7 @@ public abstract class ServletConfigMapAdapterTestCase extends MapAbstractTestCas } } - public static final class FilterConfigMapTestCase extends ServletConfigMapAdapterTestCase { + public static final class FilterConfigMapTestCase extends ServletConfigMapAdapterTest { public Map makeEmptyMap() { FilterConfig config = new TestConfig(); @@ -176,7 +176,7 @@ public abstract class ServletConfigMapAdapterTestCase extends MapAbstractTestCas } } - public static final class ServletContextMapTestCase extends ServletConfigMapAdapterTestCase { + public static final class ServletContextMapTestCase extends ServletConfigMapAdapterTest { public Map makeEmptyMap() { ServletContext config = new TestConfig(); diff --git a/servlet/src/test/java/com/twelvemonkeys/servlet/ServletHeadersMapAdapterTestCase.java b/servlet/src/test/java/com/twelvemonkeys/servlet/ServletHeadersMapAdapterTest.java similarity index 94% rename from servlet/src/test/java/com/twelvemonkeys/servlet/ServletHeadersMapAdapterTestCase.java rename to servlet/src/test/java/com/twelvemonkeys/servlet/ServletHeadersMapAdapterTest.java index e462b92f..f70d214c 100755 --- a/servlet/src/test/java/com/twelvemonkeys/servlet/ServletHeadersMapAdapterTestCase.java +++ b/servlet/src/test/java/com/twelvemonkeys/servlet/ServletHeadersMapAdapterTest.java @@ -17,7 +17,7 @@ import static org.mockito.Mockito.when; * @author Harald Kuhr * @version $Id: ServletHeadersMapAdapterTestCase.java#1 $ */ -public class ServletHeadersMapAdapterTestCase extends MapAbstractTestCase { +public class ServletHeadersMapAdapterTest extends MapAbstractTestCase { private static final List HEADER_VALUE_ETAG = Arrays.asList("\"1234567890abcdef\""); private static final List HEADER_VALUE_DATE = Arrays.asList(new Date().toString()); private static final List HEADER_VALUE_FOO = Arrays.asList("one", "two"); diff --git a/servlet/src/test/java/com/twelvemonkeys/servlet/ServletParametersMapAdapterTestCase.java b/servlet/src/test/java/com/twelvemonkeys/servlet/ServletParametersMapAdapterTest.java similarity index 94% rename from servlet/src/test/java/com/twelvemonkeys/servlet/ServletParametersMapAdapterTestCase.java rename to servlet/src/test/java/com/twelvemonkeys/servlet/ServletParametersMapAdapterTest.java index 9af1ad34..b2590873 100755 --- a/servlet/src/test/java/com/twelvemonkeys/servlet/ServletParametersMapAdapterTestCase.java +++ b/servlet/src/test/java/com/twelvemonkeys/servlet/ServletParametersMapAdapterTest.java @@ -17,7 +17,7 @@ import static org.mockito.Mockito.when; * @author Harald Kuhr * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletParametersMapAdapterTestCase.java#1 $ */ -public class ServletParametersMapAdapterTestCase extends MapAbstractTestCase { +public class ServletParametersMapAdapterTest extends MapAbstractTestCase { private static final List PARAM_VALUE_ETAG = Arrays.asList("\"1234567890abcdef\""); private static final List PARAM_VALUE_DATE = Arrays.asList(new Date().toString()); private static final List PARAM_VALUE_FOO = Arrays.asList("one", "two"); From b97d95cca7336c3089f9609f7ebfbfcd25d30359 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Mon, 4 Mar 2013 15:16:59 +0100 Subject: [PATCH 35/58] More Maven-friendly test suite. --- .../servlet/ServletConfigMapAdapterTest.java | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/servlet/src/test/java/com/twelvemonkeys/servlet/ServletConfigMapAdapterTest.java b/servlet/src/test/java/com/twelvemonkeys/servlet/ServletConfigMapAdapterTest.java index 9b5c405a..7a164636 100755 --- a/servlet/src/test/java/com/twelvemonkeys/servlet/ServletConfigMapAdapterTest.java +++ b/servlet/src/test/java/com/twelvemonkeys/servlet/ServletConfigMapAdapterTest.java @@ -1,13 +1,15 @@ package com.twelvemonkeys.servlet; import com.twelvemonkeys.util.MapAbstractTestCase; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; import javax.servlet.*; -import java.util.*; -import java.io.Serializable; import java.io.InputStream; -import java.net.URL; +import java.io.Serializable; import java.net.MalformedURLException; +import java.net.URL; +import java.util.*; /** * ServletConfigMapAdapterTestCase @@ -16,7 +18,12 @@ import java.net.MalformedURLException; * @author Harald Kuhr * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletConfigMapAdapterTestCase.java#3 $ */ -public abstract class ServletConfigMapAdapterTest extends MapAbstractTestCase { +@RunWith(Suite.class) +@Suite.SuiteClasses({AbstractServletConfigMapAdapterTest.ServletConfigMapTest.class, AbstractServletConfigMapAdapterTest.FilterConfigMapTest.class, AbstractServletConfigMapAdapterTest.ServletContextMapTest.class}) +public final class ServletConfigMapAdapterTest { +} + +abstract class AbstractServletConfigMapAdapterTest extends MapAbstractTestCase { public boolean isPutAddSupported() { return false; @@ -148,7 +155,7 @@ public abstract class ServletConfigMapAdapterTest extends MapAbstractTestCase { } } - public static final class ServletConfigMapTestCase extends ServletConfigMapAdapterTest { + public static final class ServletConfigMapTest extends AbstractServletConfigMapAdapterTest { public Map makeEmptyMap() { ServletConfig config = new TestConfig(); @@ -162,7 +169,7 @@ public abstract class ServletConfigMapAdapterTest extends MapAbstractTestCase { } } - public static final class FilterConfigMapTestCase extends ServletConfigMapAdapterTest { + public static final class FilterConfigMapTest extends AbstractServletConfigMapAdapterTest { public Map makeEmptyMap() { FilterConfig config = new TestConfig(); @@ -176,7 +183,7 @@ public abstract class ServletConfigMapAdapterTest extends MapAbstractTestCase { } } - public static final class ServletContextMapTestCase extends ServletConfigMapAdapterTest { + public static final class ServletContextMapTest extends AbstractServletConfigMapAdapterTest { public Map makeEmptyMap() { ServletContext config = new TestConfig(); From 09444ab0837fded5bdb09e98b2bc5e81363a2383 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Tue, 26 Mar 2013 09:42:49 +0100 Subject: [PATCH 36/58] TMI-TIFF: Horizontal differencing predictor implementation as stream, for easier reading. --- .../tiff/HorizontalDeDifferencingStream.java | 340 +++++++++++ .../HorizontalDeDifferencingStreamTest.java | 569 ++++++++++++++++++ 2 files changed, 909 insertions(+) create mode 100644 imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/HorizontalDeDifferencingStream.java create mode 100644 imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/HorizontalDeDifferencingStreamTest.java diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/HorizontalDeDifferencingStream.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/HorizontalDeDifferencingStream.java new file mode 100644 index 00000000..4adaf9ed --- /dev/null +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/HorizontalDeDifferencingStream.java @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2013, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.tiff; + +import com.twelvemonkeys.lang.Validate; + +import java.io.EOFException; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteOrder; + +/** + * A decoder for data converted using "horizontal differencing predictor". + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: HorizontalDeDifferencingStream.java,v 1.0 11.03.13 14:20 haraldk Exp$ + */ +final class HorizontalDeDifferencingStream extends FilterInputStream { + // See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64. + + private final int columns; + // NOTE: PlanarConfiguration == 2 may be treated as samplesPerPixel == 1 + private final int samplesPerPixel; + private final int bitsPerSample; + private final ByteOrder byteOrder; + + int decodedLength; + int decodedPos; + + private final byte[] buffer; + + public HorizontalDeDifferencingStream(final InputStream stream, final int columns, final int samplesPerPixel, final int bitsPerSample, final ByteOrder byteOrder) { + super(Validate.notNull(stream, "stream")); + + this.columns = Validate.isTrue(columns > 0, columns, "width must be greater than 0"); + this.samplesPerPixel = Validate.isTrue(bitsPerSample >= 8 || samplesPerPixel == 1, samplesPerPixel, "Unsupported samples per pixel for < 8 bit samples: %s"); + this.bitsPerSample = Validate.isTrue(isValidBPS(bitsPerSample), bitsPerSample, "Unsupported bits per sample value: %s"); + this.byteOrder = byteOrder; + + buffer = new byte[(columns * samplesPerPixel * bitsPerSample + 7) / 8]; + } + + private boolean isValidBPS(final int bitsPerSample) { + switch (bitsPerSample) { + case 1: + case 2: + case 4: + case 8: + case 16: + case 32: + case 64: + return true; + default: + return false; + } + } + + private void fetch() throws IOException { + int pos = 0; + int read; + + // This *SHOULD* read an entire row of pixels (or nothing at all) into the buffer, otherwise we will throw EOFException below + while (pos < buffer.length && (read = in.read(buffer, pos, buffer.length - pos)) > 0) { + pos += read; + } + + if (pos > 0) { + if (buffer.length > pos) { + throw new EOFException("Unexpected end of stream"); + } + + decodeRow(); + + decodedLength = buffer.length; + decodedPos = 0; + } + else { + decodedLength = -1; + } + } + + private void decodeRow() throws EOFException { + // Un-apply horizontal predictor + int sample = 0; + byte temp; + + switch (bitsPerSample) { + case 1: + for (int b = 0; b < (columns + 7) / 8; b++) { + sample += (buffer[b] >> 7) & 0x1; + temp = (byte) ((sample << 7) & 0x80); + sample += (buffer[b] >> 6) & 0x1; + temp |= (byte) ((sample << 6) & 0x40); + sample += (buffer[b] >> 5) & 0x1; + temp |= (byte) ((sample << 5) & 0x20); + sample += (buffer[b] >> 4) & 0x1; + temp |= (byte) ((sample << 4) & 0x10); + sample += (buffer[b] >> 3) & 0x1; + temp |= (byte) ((sample << 3) & 0x08); + sample += (buffer[b] >> 2) & 0x1; + temp |= (byte) ((sample << 2) & 0x04); + sample += (buffer[b] >> 1) & 0x1; + temp |= (byte) ((sample << 1) & 0x02); + sample += buffer[b] & 0x1; + buffer[b] = (byte) (temp | sample & 0x1); + } + break; + case 2: + for (int b = 0; b < (columns + 3) / 4; b++) { + sample += (buffer[b] >> 6) & 0x3; + temp = (byte) ((sample << 6) & 0xc0); + sample += (buffer[b] >> 4) & 0x3; + temp |= (byte) ((sample << 4) & 0x30); + sample += (buffer[b] >> 2) & 0x3; + temp |= (byte) ((sample << 2) & 0x0c); + sample += buffer[b] & 0x3; + buffer[b] = (byte) (temp | sample & 0x3); + } + break; + + case 4: + for (int b = 0; b < (columns + 1) / 2; b++) { + sample += (buffer[b] >> 4) & 0xf; + temp = (byte) ((sample << 4) & 0xf0); + sample += buffer[b] & 0x0f; + buffer[b] = (byte) (temp | sample & 0xf); + } + break; + + case 8: + for (int x = 1; x < columns; x++) { + for (int b = 0; b < samplesPerPixel; b++) { + int off = x * samplesPerPixel + b; + buffer[off] = (byte) (buffer[off - samplesPerPixel] + buffer[off]); + } + } + break; + + case 16: + for (int x = 1; x < columns; x++) { + for (int b = 0; b < samplesPerPixel; b++) { + int off = x * samplesPerPixel + b; + putShort(off, asShort(off - samplesPerPixel) + asShort(off)); + } + } + break; + + case 32: + for (int x = 1; x < columns; x++) { + for (int b = 0; b < samplesPerPixel; b++) { + int off = x * samplesPerPixel + b; + putInt(off, asInt(off - samplesPerPixel) + asInt(off)); + } + } + break; + + case 64: + for (int x = 1; x < columns; x++) { + for (int b = 0; b < samplesPerPixel; b++) { + int off = x * samplesPerPixel + b; + putLong(off, asLong(off - samplesPerPixel) + asLong(off)); + } + } + break; + + default: + throw new AssertionError(String.format("Unsupported bits per sample value: %d", bitsPerSample)); + } + } + + private void putLong(final int index, final long value) { + if (byteOrder == ByteOrder.BIG_ENDIAN) { + buffer[index * 8 ] = (byte) ((value >> 56) & 0xff); + buffer[index * 8 + 1] = (byte) ((value >> 48) & 0xff); + buffer[index * 8 + 2] = (byte) ((value >> 40) & 0xff); + buffer[index * 8 + 3] = (byte) ((value >> 32) & 0xff); + buffer[index * 8 + 4] = (byte) ((value >> 24) & 0xff); + buffer[index * 8 + 5] = (byte) ((value >> 16) & 0xff); + buffer[index * 8 + 6] = (byte) ((value >> 8) & 0xff); + buffer[index * 8 + 7] = (byte) ((value) & 0xff); + } + else { + buffer[index * 8 + 7] = (byte) ((value >> 56) & 0xff); + buffer[index * 8 + 6] = (byte) ((value >> 48) & 0xff); + buffer[index * 8 + 5] = (byte) ((value >> 40) & 0xff); + buffer[index * 8 + 4] = (byte) ((value >> 32) & 0xff); + buffer[index * 8 + 3] = (byte) ((value >> 24) & 0xff); + buffer[index * 8 + 2] = (byte) ((value >> 16) & 0xff); + buffer[index * 8 + 1] = (byte) ((value >> 8) & 0xff); + buffer[index * 8 ] = (byte) ((value) & 0xff); + } + } + + private long asLong(final int index) { + if (byteOrder == ByteOrder.BIG_ENDIAN) { + return (buffer[index * 8 ] & 0xffl) << 56l | (buffer[index * 8 + 1] & 0xffl) << 48l | + (buffer[index * 8 + 2] & 0xffl) << 40l | (buffer[index * 8 + 3] & 0xffl) << 32l | + (buffer[index * 8 + 4] & 0xffl) << 24 | (buffer[index * 8 + 5] & 0xffl) << 16 | + (buffer[index * 8 + 6] & 0xffl) << 8 | buffer[index * 8 + 7] & 0xffl; + } + else { + return (buffer[index * 8 + 7] & 0xffl) << 56l | (buffer[index * 8 + 6] & 0xffl) << 48l | + (buffer[index * 8 + 5] & 0xffl) << 40l | (buffer[index * 8 + 4] & 0xffl) << 32l | + (buffer[index * 8 + 3] & 0xffl) << 24 | (buffer[index * 8 + 2] & 0xffl) << 16 | + (buffer[index * 8 + 1] & 0xffl) << 8 | buffer[index * 8] & 0xffl; + } + } + + private void putInt(final int index, final int value) { + if (byteOrder == ByteOrder.BIG_ENDIAN) { + buffer[index * 4 ] = (byte) ((value >> 24) & 0xff); + buffer[index * 4 + 1] = (byte) ((value >> 16) & 0xff); + buffer[index * 4 + 2] = (byte) ((value >> 8) & 0xff); + buffer[index * 4 + 3] = (byte) ((value) & 0xff); + } + else { + buffer[index * 4 + 3] = (byte) ((value >> 24) & 0xff); + buffer[index * 4 + 2] = (byte) ((value >> 16) & 0xff); + buffer[index * 4 + 1] = (byte) ((value >> 8) & 0xff); + buffer[index * 4 ] = (byte) ((value) & 0xff); + } + } + + private int asInt(final int index) { + if (byteOrder == ByteOrder.BIG_ENDIAN) { + return (buffer[index * 4] & 0xff) << 24 | (buffer[index * 4 + 1] & 0xff) << 16 | + (buffer[index * 4 + 2] & 0xff) << 8 | buffer[index * 4 + 3] & 0xff; + } + else { + return (buffer[index * 4 + 3] & 0xff) << 24 | (buffer[index * 4 + 2] & 0xff) << 16 | + (buffer[index * 4 + 1] & 0xff) << 8 | buffer[index * 4] & 0xff; + } + } + + private void putShort(final int index, final int value) { + if (byteOrder == ByteOrder.BIG_ENDIAN) { + buffer[index * 2 ] = (byte) ((value >> 8) & 0xff); + buffer[index * 2 + 1] = (byte) ((value) & 0xff); + } + else { + buffer[index * 2 + 1] = (byte) ((value >> 8) & 0xff); + buffer[index * 2 ] = (byte) ((value) & 0xff); + } + } + + private short asShort(final int index) { + if (byteOrder == ByteOrder.BIG_ENDIAN) { + return (short) ((buffer[index * 2] & 0xff) << 8 | buffer[index * 2 + 1] & 0xff); + } + else { + return (short) ((buffer[index * 2 + 1] & 0xff) << 8 | buffer[index * 2] & 0xff); + } + } + + @Override + public int read() throws IOException { + if (decodedLength < 0) { + return -1; + } + + if (decodedPos >= decodedLength) { + fetch(); + + if (decodedLength < 0) { + return -1; + } + } + + return buffer[decodedPos++] & 0xff; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (decodedLength < 0) { + return -1; + } + + if (decodedPos >= decodedLength) { + fetch(); + + if (decodedLength < 0) { + return -1; + } + } + + int read = Math.min(decodedLength - decodedPos, len); + System.arraycopy(buffer, decodedPos, b, off, read); + decodedPos += read; + + return read; + } + + @Override + public long skip(long n) throws IOException { + if (decodedLength < 0) { + return -1; + } + + if (decodedPos >= decodedLength) { + fetch(); + + if (decodedLength < 0) { + return -1; + } + } + + int skipped = (int) Math.min(decodedLength - decodedPos, n); + decodedPos += skipped; + + return skipped; + } +} diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/HorizontalDeDifferencingStreamTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/HorizontalDeDifferencingStreamTest.java new file mode 100644 index 00000000..2ee1692d --- /dev/null +++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/HorizontalDeDifferencingStreamTest.java @@ -0,0 +1,569 @@ +/* + * Copyright (c) 2013, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.tiff; + +import com.twelvemonkeys.io.FastByteArrayOutputStream; +import com.twelvemonkeys.io.LittleEndianDataInputStream; +import com.twelvemonkeys.io.LittleEndianDataOutputStream; +import org.junit.Test; + +import java.io.*; +import java.nio.ByteOrder; + +import static org.junit.Assert.*; + +/** + * HorizontalDeDifferencingStreamTest + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: HorizontalDeDifferencingStreamTest.java,v 1.0 13.03.13 12:46 haraldk Exp$ + */ +public class HorizontalDeDifferencingStreamTest { + @Test + public void testRead1SPP1BPS() throws IOException { + // 1 sample per pixel, 1 bits per sample (mono/indexed) + byte[] data = { + (byte) 0x80, 0x00, 0x00, + 0x71, 0x11, 0x44, + }; + + InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 24, 1, 1, ByteOrder.BIG_ENDIAN); + + // Row 1 + assertEquals(0xff, stream.read()); + assertEquals(0xff, stream.read()); + assertEquals(0xff, stream.read()); + + // Row 2 + assertEquals(0x5e, stream.read()); + assertEquals(0x1e, stream.read()); + assertEquals(0x78, stream.read()); + + // EOF + assertEquals(-1, stream.read()); + } + + @Test + public void testRead1SPP2BPS() throws IOException { + // 1 sample per pixel, 2 bits per sample (gray/indexed) + byte[] data = { + (byte) 0xc0, 0x00, 0x00, 0x00, + 0x71, 0x11, 0x44, (byte) 0xcc, + }; + + InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 16, 1, 2, ByteOrder.BIG_ENDIAN); + + // Row 1 + assertEquals(0xff, stream.read()); + assertEquals(0xff, stream.read()); + assertEquals(0xff, stream.read()); + assertEquals(0xff, stream.read()); + + // Row 2 + assertEquals(0x41, stream.read()); + assertEquals(0x6b, stream.read()); + assertEquals(0x05, stream.read()); + assertEquals(0x0f, stream.read()); + + // EOF + assertEquals(-1, stream.read()); + } + + @Test + public void testRead1SPP4BPS() throws IOException { + // 1 sample per pixel, 4 bits per sample (gray/indexed) + byte[] data = { + (byte) 0xf0, 0x00, 0x00, 0x00, + 0x70, 0x11, 0x44, (byte) 0xcc, + 0x00, 0x01, 0x10, (byte) 0xe0 + }; + + InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 8, 1, 4, ByteOrder.BIG_ENDIAN); + + // Row 1 + assertEquals(0xff, stream.read()); + assertEquals(0xff, stream.read()); + assertEquals(0xff, stream.read()); + assertEquals(0xff, stream.read()); + + // Row 2 + assertEquals(0x77, stream.read()); + assertEquals(0x89, stream.read()); + assertEquals(0xd1, stream.read()); + assertEquals(0xd9, stream.read()); + + // Row 3 + assertEquals(0x00, stream.read()); + assertEquals(0x01, stream.read()); + assertEquals(0x22, stream.read()); + assertEquals(0x00, stream.read()); + + // EOF + assertEquals(-1, stream.read()); + } + + @Test + public void testRead1SPP8BPS() throws IOException { + // 1 sample per pixel, 8 bits per sample (gray/indexed) + byte[] data = { + (byte) 0xff, 0, 0, 0, + 0x7f, 1, 4, -4, + 0x00, 127, 127, -127 + }; + + InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 4, 1, 8, ByteOrder.BIG_ENDIAN); + + // Row 1 + assertEquals(0xff, stream.read()); + assertEquals(0xff, stream.read()); + assertEquals(0xff, stream.read()); + assertEquals(0xff, stream.read()); + + // Row 2 + assertEquals(0x7f, stream.read()); + assertEquals(0x80, stream.read()); + assertEquals(0x84, stream.read()); + assertEquals(0x80, stream.read()); + + // Row 3 + assertEquals(0x00, stream.read()); + assertEquals(0x7f, stream.read()); + assertEquals(0xfe, stream.read()); + assertEquals(0x7f, stream.read()); + + // EOF + assertEquals(-1, stream.read()); + } + + @Test + public void testReadArray1SPP8BPS() throws IOException { + // 1 sample per pixel, 8 bits per sample (gray/indexed) + byte[] data = { + (byte) 0xff, 0, 0, 0, + 0x7f, 1, 4, -4, + 0x00, 127, 127, -127 + }; + + InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 4, 1, 8, ByteOrder.BIG_ENDIAN); + + byte[] result = new byte[data.length]; + new DataInputStream(stream).readFully(result); + + assertArrayEquals( + new byte[] { + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + 0x7f, (byte) 0x80, (byte) 0x84, (byte) 0x80, + 0x00, 0x7f, (byte) 0xfe, 0x7f, + }, + result + ); + + // EOF + assertEquals(-1, stream.read(new byte[16])); + assertEquals(-1, stream.read()); + } + + @Test + public void testRead1SPP32BPS() throws IOException { + // 1 sample per pixel, 32 bits per sample (gray) + FastByteArrayOutputStream out = new FastByteArrayOutputStream(16); + DataOutput dataOut = new DataOutputStream(out); + dataOut.writeInt(0x00000000); + dataOut.writeInt(305419896); + dataOut.writeInt(305419896); + dataOut.writeInt(-610839792); + + InputStream in = new HorizontalDeDifferencingStream(out.createInputStream(), 4, 1, 32, ByteOrder.BIG_ENDIAN); + DataInput dataIn = new DataInputStream(in); + + // Row 1 + assertEquals(0, dataIn.readInt()); + assertEquals(305419896, dataIn.readInt()); + assertEquals(610839792, dataIn.readInt()); + assertEquals(0, dataIn.readInt()); + + // EOF + assertEquals(-1, in.read()); + } + + @Test + public void testRead1SPP32BPSLittleEndian() throws IOException { + // 1 sample per pixel, 32 bits per sample (gray) + FastByteArrayOutputStream out = new FastByteArrayOutputStream(16); + DataOutput dataOut = new LittleEndianDataOutputStream(out); + dataOut.writeInt(0x00000000); + dataOut.writeInt(305419896); + dataOut.writeInt(305419896); + dataOut.writeInt(-610839792); + + InputStream in = new HorizontalDeDifferencingStream(out.createInputStream(), 4, 1, 32, ByteOrder.LITTLE_ENDIAN); + DataInput dataIn = new LittleEndianDataInputStream(in); + + // Row 1 + assertEquals(0, dataIn.readInt()); + assertEquals(305419896, dataIn.readInt()); + assertEquals(610839792, dataIn.readInt()); + assertEquals(0, dataIn.readInt()); + + // EOF + assertEquals(-1, in.read()); + } + + @Test + public void testRead1SPP64BPS() throws IOException { + // 1 sample per pixel, 64 bits per sample (gray) + FastByteArrayOutputStream out = new FastByteArrayOutputStream(32); + DataOutput dataOut = new DataOutputStream(out); + dataOut.writeLong(0x00000000); + dataOut.writeLong(81985529216486895L); + dataOut.writeLong(81985529216486895L); + dataOut.writeLong(-163971058432973790L); + + InputStream in = new HorizontalDeDifferencingStream(out.createInputStream(), 4, 1, 64, ByteOrder.BIG_ENDIAN); + DataInput dataIn = new DataInputStream(in); + + // Row 1 + assertEquals(0, dataIn.readLong()); + assertEquals(81985529216486895L, dataIn.readLong()); + assertEquals(163971058432973790L, dataIn.readLong()); + assertEquals(0, dataIn.readLong()); + + // EOF + assertEquals(-1, in.read()); + } + + @Test + public void testRead1SPP64BPSLittleEndian() throws IOException { + // 1 sample per pixel, 64 bits per sample (gray) + FastByteArrayOutputStream out = new FastByteArrayOutputStream(32); + DataOutput dataOut = new LittleEndianDataOutputStream(out); + dataOut.writeLong(0x00000000); + dataOut.writeLong(81985529216486895L); + dataOut.writeLong(81985529216486895L); + dataOut.writeLong(-163971058432973790L); + + InputStream in = new HorizontalDeDifferencingStream(out.createInputStream(), 4, 1, 64, ByteOrder.LITTLE_ENDIAN); + DataInput dataIn = new LittleEndianDataInputStream(in); + + // Row 1 + assertEquals(0, dataIn.readLong()); + assertEquals(81985529216486895L, dataIn.readLong()); + assertEquals(163971058432973790L, dataIn.readLong()); + assertEquals(0, dataIn.readLong()); + + // EOF + assertEquals(-1, in.read()); + } + + @Test + public void testRead3SPP8BPS() throws IOException { + // 3 samples per pixel, 8 bits per sample (RGB) + byte[] data = { + (byte) 0xff, (byte) 0x00, (byte) 0x7f, -1, -1, -1, -4, -4, -4, 4, 4, 4, + 0x7f, 0x7f, 0x7f, 1, 1, 1, 4, 4, 4, -4, -4, -4, + 0x00, 0x00, 0x00, 127, -127, 0, -127, 127, 0, 0, 0, 127, + }; + + InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 4, 3, 8, ByteOrder.BIG_ENDIAN); + + // Row 1 + assertEquals(0xff, stream.read()); + assertEquals(0x00, stream.read()); + assertEquals(0x7f, stream.read()); + + assertEquals(0xfe, stream.read()); + assertEquals(0xff, stream.read()); + assertEquals(0x7e, stream.read()); + + assertEquals(0xfa, stream.read()); + assertEquals(0xfb, stream.read()); + assertEquals(0x7a, stream.read()); + + assertEquals(0xfe, stream.read()); + assertEquals(0xff, stream.read()); + assertEquals(0x7e, stream.read()); + + // Row 2 + assertEquals(0x7f, stream.read()); + assertEquals(0x7f, stream.read()); + assertEquals(0x7f, stream.read()); + + assertEquals(0x80, stream.read()); + assertEquals(0x80, stream.read()); + assertEquals(0x80, stream.read()); + + assertEquals(0x84, stream.read()); + assertEquals(0x84, stream.read()); + assertEquals(0x84, stream.read()); + + assertEquals(0x80, stream.read()); + assertEquals(0x80, stream.read()); + assertEquals(0x80, stream.read()); + + // Row 3 + assertEquals(0x00, stream.read()); + assertEquals(0x00, stream.read()); + assertEquals(0x00, stream.read()); + + assertEquals(0x7f, stream.read()); + assertEquals(0x81, stream.read()); + assertEquals(0x00, stream.read()); + + assertEquals(0x00, stream.read()); + assertEquals(0x00, stream.read()); + assertEquals(0x00, stream.read()); + + assertEquals(0x00, stream.read()); + assertEquals(0x00, stream.read()); + assertEquals(0x7f, stream.read()); + + // EOF + assertEquals(-1, stream.read()); + } + + @Test + public void testRead3SPP16BPS() throws IOException { + FastByteArrayOutputStream out = new FastByteArrayOutputStream(24); + DataOutput dataOut = new DataOutputStream(out); + dataOut.writeShort(0x0000); + dataOut.writeShort(0x0000); + dataOut.writeShort(0x0000); + dataOut.writeShort(4660); + dataOut.writeShort(30292); + dataOut.writeShort(4660); + dataOut.writeShort(4660); + dataOut.writeShort(30292); + dataOut.writeShort(4660); + dataOut.writeShort(-9320); + dataOut.writeShort(-60584); + dataOut.writeShort(-9320); + + dataOut.writeShort(0x0000); + dataOut.writeShort(0x0000); + dataOut.writeShort(0x0000); + dataOut.writeShort(30292); + dataOut.writeShort(30292); + dataOut.writeShort(30292); + dataOut.writeShort(30292); + dataOut.writeShort(30292); + dataOut.writeShort(30292); + dataOut.writeShort(-60584); + dataOut.writeShort(-60584); + dataOut.writeShort(-60584); + + InputStream in = new HorizontalDeDifferencingStream(out.createInputStream(), 4, 3, 16, ByteOrder.BIG_ENDIAN); + DataInput dataIn = new DataInputStream(in); + + // Row 1 + assertEquals(0, dataIn.readUnsignedShort()); + assertEquals(0, dataIn.readUnsignedShort()); + assertEquals(0, dataIn.readUnsignedShort()); + assertEquals(4660, dataIn.readUnsignedShort()); + assertEquals(30292, dataIn.readUnsignedShort()); + assertEquals(4660, dataIn.readUnsignedShort()); + assertEquals(9320, dataIn.readUnsignedShort()); + assertEquals(60584, dataIn.readUnsignedShort()); + assertEquals(9320, dataIn.readUnsignedShort()); + assertEquals(0, dataIn.readUnsignedShort()); + assertEquals(0, dataIn.readUnsignedShort()); + assertEquals(0, dataIn.readUnsignedShort()); + + // Row 2 + assertEquals(0, dataIn.readUnsignedShort()); + assertEquals(0, dataIn.readUnsignedShort()); + assertEquals(0, dataIn.readUnsignedShort()); + assertEquals(30292, dataIn.readUnsignedShort()); + assertEquals(30292, dataIn.readUnsignedShort()); + assertEquals(30292, dataIn.readUnsignedShort()); + assertEquals(60584, dataIn.readUnsignedShort()); + assertEquals(60584, dataIn.readUnsignedShort()); + assertEquals(60584, dataIn.readUnsignedShort()); + assertEquals(0, dataIn.readUnsignedShort()); + assertEquals(0, dataIn.readUnsignedShort()); + assertEquals(0, dataIn.readUnsignedShort()); + + // EOF + assertEquals(-1, in.read()); + } + + @Test + public void testRead3SPP16BPSLittleEndian() throws IOException { + FastByteArrayOutputStream out = new FastByteArrayOutputStream(24); + DataOutput dataOut = new LittleEndianDataOutputStream(out); + dataOut.writeShort(0x0000); + dataOut.writeShort(0x0000); + dataOut.writeShort(0x0000); + dataOut.writeShort(4660); + dataOut.writeShort(30292); + dataOut.writeShort(4660); + dataOut.writeShort(4660); + dataOut.writeShort(30292); + dataOut.writeShort(4660); + dataOut.writeShort(-9320); + dataOut.writeShort(-60584); + dataOut.writeShort(-9320); + + dataOut.writeShort(0x0000); + dataOut.writeShort(0x0000); + dataOut.writeShort(0x0000); + dataOut.writeShort(30292); + dataOut.writeShort(30292); + dataOut.writeShort(30292); + dataOut.writeShort(30292); + dataOut.writeShort(30292); + dataOut.writeShort(30292); + dataOut.writeShort(-60584); + dataOut.writeShort(-60584); + dataOut.writeShort(-60584); + + InputStream in = new HorizontalDeDifferencingStream(out.createInputStream(), 4, 3, 16, ByteOrder.LITTLE_ENDIAN); + DataInput dataIn = new LittleEndianDataInputStream(in); + + // Row 1 + assertEquals(0, dataIn.readUnsignedShort()); + assertEquals(0, dataIn.readUnsignedShort()); + assertEquals(0, dataIn.readUnsignedShort()); + assertEquals(4660, dataIn.readUnsignedShort()); + assertEquals(30292, dataIn.readUnsignedShort()); + assertEquals(4660, dataIn.readUnsignedShort()); + assertEquals(9320, dataIn.readUnsignedShort()); + assertEquals(60584, dataIn.readUnsignedShort()); + assertEquals(9320, dataIn.readUnsignedShort()); + assertEquals(0, dataIn.readUnsignedShort()); + assertEquals(0, dataIn.readUnsignedShort()); + assertEquals(0, dataIn.readUnsignedShort()); + + // Row 2 + assertEquals(0, dataIn.readUnsignedShort()); + assertEquals(0, dataIn.readUnsignedShort()); + assertEquals(0, dataIn.readUnsignedShort()); + assertEquals(30292, dataIn.readUnsignedShort()); + assertEquals(30292, dataIn.readUnsignedShort()); + assertEquals(30292, dataIn.readUnsignedShort()); + assertEquals(60584, dataIn.readUnsignedShort()); + assertEquals(60584, dataIn.readUnsignedShort()); + assertEquals(60584, dataIn.readUnsignedShort()); + assertEquals(0, dataIn.readUnsignedShort()); + assertEquals(0, dataIn.readUnsignedShort()); + assertEquals(0, dataIn.readUnsignedShort()); + + // EOF + assertEquals(-1, in.read()); + } + + @Test + public void testRead4SPP8BPS() throws IOException { + // 4 samples per pixel, 8 bits per sample (RGBA) + byte[] data = { + (byte) 0xff, (byte) 0x00, (byte) 0x7f, 0x00, -1, -1, -1, -1, -4, -4, -4, -4, 4, 4, 4, 4, + 0x7f, 0x7f, 0x7f, 0x7f, 1, 1, 1, 1, 4, 4, 4, 4, -4, -4, -4, -4, + }; + + InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 4, 4, 8, ByteOrder.BIG_ENDIAN); + + // Row 1 + assertEquals(0xff, stream.read()); + assertEquals(0x00, stream.read()); + assertEquals(0x7f, stream.read()); + assertEquals(0x00, stream.read()); + + assertEquals(0xfe, stream.read()); + assertEquals(0xff, stream.read()); + assertEquals(0x7e, stream.read()); + assertEquals(0xff, stream.read()); + + assertEquals(0xfa, stream.read()); + assertEquals(0xfb, stream.read()); + assertEquals(0x7a, stream.read()); + assertEquals(0xfb, stream.read()); + + assertEquals(0xfe, stream.read()); + assertEquals(0xff, stream.read()); + assertEquals(0x7e, stream.read()); + assertEquals(0xff, stream.read()); + + // Row 2 + assertEquals(0x7f, stream.read()); + assertEquals(0x7f, stream.read()); + assertEquals(0x7f, stream.read()); + assertEquals(0x7f, stream.read()); + + assertEquals(0x80, stream.read()); + assertEquals(0x80, stream.read()); + assertEquals(0x80, stream.read()); + assertEquals(0x80, stream.read()); + + assertEquals(0x84, stream.read()); + assertEquals(0x84, stream.read()); + assertEquals(0x84, stream.read()); + assertEquals(0x84, stream.read()); + + assertEquals(0x80, stream.read()); + assertEquals(0x80, stream.read()); + assertEquals(0x80, stream.read()); + assertEquals(0x80, stream.read()); + + // EOF + assertEquals(-1, stream.read()); + } + + @Test + public void testReadArray4SPP8BPS() throws IOException { + // 4 samples per pixel, 8 bits per sample (RGBA) + byte[] data = { + (byte) 0xff, (byte) 0x00, (byte) 0x7f, 0x00, -1, -1, -1, -1, -4, -4, -4, -4, 4, 4, 4, 4, + 0x7f, 0x7f, 0x7f, 0x7f, 1, 1, 1, 1, 4, 4, 4, 4, -4, -4, -4, -4, + }; + + InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 4, 4, 8, ByteOrder.BIG_ENDIAN); + + byte[] result = new byte[data.length]; + new DataInputStream(stream).readFully(result); + + assertArrayEquals( + new byte[] { + (byte) 0xff, 0x00, 0x7f, 0x00, + (byte) 0xfe, (byte) 0xff, 0x7e, (byte) 0xff, + (byte) 0xfa, (byte) 0xfb, 0x7a, (byte) 0xfb, + (byte) 0xfe, (byte) 0xff, 0x7e, (byte) 0xff, + + 0x7f, 0x7f, 0x7f, 0x7f, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, + (byte) 0x84, (byte) 0x84, (byte) 0x84, (byte) 0x84, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, + }, + result + ); + + // EOF + assertEquals(-1, stream.read(new byte[16])); + assertEquals(-1, stream.read()); + } +} From 61e01e33169e6a3757318228522e414d3a28d4ef Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Tue, 26 Mar 2013 09:44:32 +0100 Subject: [PATCH 37/58] TMI-TIFF: Code clean-up. --- .../twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java | 8 ++++++-- .../imageio/plugins/tiff/YCbCrUpsamplerStream.java | 10 ++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java index 0d76ab1f..bf573fa9 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java @@ -35,12 +35,14 @@ import java.io.IOException; import java.io.InputStream; /** - * Implements Lempel-Ziv & Welch (LZW) decompression. + * Lempel–Ziv–Welch (LZW) decompression. LZW is a universal loss-less data compression algorithm + * created by Abraham Lempel, Jacob Ziv, and Terry Welch. * Inspired by libTiff's LZW decompression. * * @author Harald Kuhr * @author last modified by $Author: haraldk$ * @version $Id: LZWDecoder.java,v 1.0 08.05.12 21:11 haraldk Exp$ + * @see LZW (Wikipedia) */ abstract class LZWDecoder implements Decoder { /** Clear: Re-initialize tables. */ @@ -51,6 +53,8 @@ abstract class LZWDecoder implements Decoder { private static final int MIN_BITS = 9; private static final int MAX_BITS = 12; + private static final int TABLE_SIZE = 1 << MAX_BITS; + private final boolean compatibilityMode; private final String[] table; @@ -68,7 +72,7 @@ abstract class LZWDecoder implements Decoder { protected LZWDecoder(final boolean compatibilityMode) { this.compatibilityMode = compatibilityMode; - table = new String[compatibilityMode ? 4096 + 1024 : 4096]; // libTiff adds 1024 "for compatibility"... + table = new String[compatibilityMode ? TABLE_SIZE + 1024 : TABLE_SIZE]; // libTiff adds 1024 "for compatibility"... // First 258 entries of table is always fixed for (int i = 0; i < 256; i++) { diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java index e18bd152..dc193438 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java @@ -28,6 +28,8 @@ package com.twelvemonkeys.imageio.plugins.tiff; +import com.twelvemonkeys.lang.Validate; + import java.awt.image.DataBufferByte; import java.awt.image.Raster; import java.io.EOFException; @@ -64,8 +66,8 @@ final class YCbCrUpsamplerStream extends FilterInputStream { int bufferLength; int bufferPos; - public YCbCrUpsamplerStream(InputStream stream, int[] chromaSub, int yCbCrPos, int columns, double[] coefficients) { - super(stream); + public YCbCrUpsamplerStream(final InputStream stream, final int[] chromaSub, final int yCbCrPos, final int columns, final double[] coefficients) { + super(Validate.notNull(stream, "stream")); this.horizChromaSub = chromaSub[0]; this.vertChromaSub = chromaSub[1]; @@ -94,7 +96,7 @@ final class YCbCrUpsamplerStream extends FilterInputStream { int read; // This *SHOULD* read an entire row of units into the buffer, otherwise decodeRows will throw EOFException - while (pos < buffer.length && (read = super.read(buffer, pos, buffer.length - pos)) > 0) { + while (pos < buffer.length && (read = in.read(buffer, pos, buffer.length - pos)) > 0) { pos += read; } @@ -168,7 +170,7 @@ final class YCbCrUpsamplerStream extends FilterInputStream { } } - return decodedRows[decodedPos++]; + return decodedRows[decodedPos++] & 0xff; } @Override From b966254322aed055e12dec02a64a5ac97534779c Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Fri, 19 Apr 2013 16:17:01 +0200 Subject: [PATCH 38/58] TMI-JPEG: More lenient segment parsing, now allows 0xFF padding between segments + fixed an NPE in JPEGImageReader if the parsing fails. --- .../imageio/plugins/jpeg/JPEGImageReader.java | 14 +++++++++-- .../jpeg/JPEGSegmentImageInputStream.java | 6 +++++ .../plugins/jpeg/JPEGImageReaderTest.java | 3 ++- .../jpeg/JPEGSegmentImageInputStreamTest.java | 23 ++++++++++++++++++ .../resources/jpeg/jfif-padded-segments.jpg | Bin 0 -> 4326 bytes .../metadata/jpeg/JPEGSegmentUtil.java | 17 +++++++++---- .../metadata/jpeg/JPEGSegmentUtilTest.java | 13 ++++++++++ .../resources/jpeg/jfif-padded-segments.jpg | Bin 0 -> 4326 bytes 8 files changed, 68 insertions(+), 8 deletions(-) create mode 100755 imageio/imageio-jpeg/src/test/resources/jpeg/jfif-padded-segments.jpg create mode 100755 imageio/imageio-metadata/src/test/resources/jpeg/jfif-padded-segments.jpg diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java index 6a674242..8128f6f3 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java @@ -601,14 +601,24 @@ public class JPEGImageReader extends ImageReaderBase { segments = JPEGSegmentUtil.readSegments(imageInput, SEGMENT_IDENTIFIERS); } - catch (IOException ignore) { + catch (IIOException ignore) { + if (DEBUG) { + ignore.printStackTrace(); + } } catch (IllegalArgumentException foo) { - foo.printStackTrace(); + if (DEBUG) { + foo.printStackTrace(); + } } finally { imageInput.reset(); } + + // In case of an exception, avoid NPE when referencing segments later + if (segments == null) { + segments = Collections.emptyList(); + } } private List getAppSegments(final int marker, final String identifier) throws IOException { diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStream.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStream.java index 87dd6004..ff92bda1 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStream.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStream.java @@ -90,6 +90,12 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl { long realPosition = stream.getStreamPosition(); int marker = stream.readUnsignedShort(); + // Skip over 0xff padding between markers + while (marker == 0xffff) { + realPosition++; + marker = (marker & 0xff) << 8 | stream.readUnsignedByte(); + } + // TODO: Refactor to make various segments optional, we probably only want the "Adobe" APP14 segment, 'Exif' APP1 and very few others if (isAppSegmentMarker(marker) && marker != JPEG.APP0 && !(marker == JPEG.APP1 && isAppSegmentWithId("Exif", stream)) && marker != JPEG.APP14) { int length = stream.readUnsignedShort(); // Length including length field itself diff --git a/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java b/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java index 6fa34246..c2b7ba31 100644 --- a/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java +++ b/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java @@ -75,7 +75,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase appSegments = JPEGSegmentUtil.readSegments(stream, JPEGSegmentUtil.APP_SEGMENTS); + assertEquals(2, appSegments.size()); + + assertEquals(JPEG.APP0, appSegments.get(0).marker()); + assertEquals("JFIF", appSegments.get(0).identifier()); + + assertEquals(JPEG.APP1, appSegments.get(1).marker()); + assertEquals("Exif", appSegments.get(1).identifier()); + + stream.seek(0l); + + long length = 0; + while (stream.read() != -1) { + length++; + } + + assertEquals(1079L, length); // Sanity check: same as file size, except padding and the filtered ICC_PROFILE segment + } } diff --git a/imageio/imageio-jpeg/src/test/resources/jpeg/jfif-padded-segments.jpg b/imageio/imageio-jpeg/src/test/resources/jpeg/jfif-padded-segments.jpg new file mode 100755 index 0000000000000000000000000000000000000000..b7cfd8e6d7771b6c77e87374c7e415aa85405529 GIT binary patch literal 4326 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 F{{k(OZz})* literal 0 HcmV?d00001 diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java index 818716f5..5ebce5b7 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java @@ -153,11 +153,18 @@ public final class JPEGSegmentUtil { } } - static JPEGSegment readSegment(final ImageInputStream stream, Map> segmentIdentifiers) throws IOException { + static JPEGSegment readSegment(final ImageInputStream stream, final Map> segmentIdentifiers) throws IOException { int marker = stream.readUnsignedShort(); + + // Skip over 0xff padding between markers + while (marker == 0xffff) { + marker = (marker & 0xff) << 8 | stream.readUnsignedByte(); + } + if ((marker >> 8 & 0xff) != 0xff) { throw new IIOException(String.format("Bad marker: %04x", marker)); } + int length = stream.readUnsignedShort(); // Length including length field itself byte[] data; @@ -196,7 +203,7 @@ public final class JPEGSegmentUtil { } @Override - public boolean contains(Object o) { + public boolean contains(final Object o) { return true; } } @@ -208,13 +215,13 @@ public final class JPEGSegmentUtil { } @Override - public List get(Object key) { + public List get(final Object key) { return key instanceof Integer && JPEGSegment.isAppSegmentMarker((Integer) key) ? ALL_IDS : null; } @Override - public boolean containsKey(Object key) { + public boolean containsKey(final Object key) { return true; } } @@ -226,7 +233,7 @@ public final class JPEGSegmentUtil { } @Override - public List get(Object key) { + public List get(final Object key) { return containsKey(key) ? ALL_IDS : null; } diff --git a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtilTest.java b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtilTest.java index 443341dd..8d961e05 100644 --- a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtilTest.java +++ b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtilTest.java @@ -196,4 +196,17 @@ public class JPEGSegmentUtilTest { assertEquals(JPEG.APP14, segments.get(21).marker()); assertEquals("Adobe", segments.get(21).identifier()); } + + @Test + public void testReadPaddedSegments() throws IOException { + List segments = JPEGSegmentUtil.readSegments(getData("/jpeg/jfif-padded-segments.jpg"), JPEGSegmentUtil.APP_SEGMENTS); + assertEquals(3, segments.size()); + + assertEquals(JPEG.APP0, segments.get(0).marker()); + assertEquals("JFIF", segments.get(0).identifier()); + assertEquals(JPEG.APP2, segments.get(1).marker()); + assertEquals("ICC_PROFILE", segments.get(1).identifier()); + assertEquals(JPEG.APP1, segments.get(2).marker()); + assertEquals("Exif", segments.get(2).identifier()); + } } diff --git a/imageio/imageio-metadata/src/test/resources/jpeg/jfif-padded-segments.jpg b/imageio/imageio-metadata/src/test/resources/jpeg/jfif-padded-segments.jpg new file mode 100755 index 0000000000000000000000000000000000000000..b7cfd8e6d7771b6c77e87374c7e415aa85405529 GIT binary patch literal 4326 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 F{{k(OZz})* literal 0 HcmV?d00001 From 0ffd7cacc45e7fe32bce3554707f646495d82410 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Mon, 22 Apr 2013 11:08:29 +0200 Subject: [PATCH 39/58] TMI-ICNS: Added debug for sips command. --- .../com/twelvemonkeys/imageio/plugins/icns/SipsJP2Reader.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/SipsJP2Reader.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/SipsJP2Reader.java index c5834abb..ba1704e6 100644 --- a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/SipsJP2Reader.java +++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/SipsJP2Reader.java @@ -54,12 +54,16 @@ final class SipsJP2Reader { private static final File SIPS_COMMAND = new File("/usr/bin/sips"); private static final boolean SIPS_EXISTS_AND_EXECUTES = existsAndExecutes(SIPS_COMMAND); + private static final boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.icns.debug")); private static boolean existsAndExecutes(final File cmd) { try { return cmd.exists() && cmd.canExecute(); } catch (SecurityException ignore) { + if (DEBUG) { + ignore.printStackTrace(); + } } return false; From 28d8796e54cf0e59340a01b6b62a7fd3bfa0c143 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Mon, 22 Apr 2013 21:00:33 +0200 Subject: [PATCH 40/58] TMI-JPEG: Simplified logic. --- .../imageio/plugins/jpeg/JPEGSegmentImageInputStream.java | 2 +- .../twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStream.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStream.java index ff92bda1..a03f21fe 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStream.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStream.java @@ -93,7 +93,7 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl { // Skip over 0xff padding between markers while (marker == 0xffff) { realPosition++; - marker = (marker & 0xff) << 8 | stream.readUnsignedByte(); + marker = 0xff00 | stream.readUnsignedByte(); } // TODO: Refactor to make various segments optional, we probably only want the "Adobe" APP14 segment, 'Exif' APP1 and very few others diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java index 5ebce5b7..10db0713 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java @@ -158,7 +158,7 @@ public final class JPEGSegmentUtil { // Skip over 0xff padding between markers while (marker == 0xffff) { - marker = (marker & 0xff) << 8 | stream.readUnsignedByte(); + marker = 0xff00 | stream.readUnsignedByte(); } if ((marker >> 8 & 0xff) != 0xff) { From 1d5cc6d26680993dc5ac4d28c76d936ff4f6b8d2 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Mon, 22 Apr 2013 21:01:30 +0200 Subject: [PATCH 41/58] TMI-JPEG: Refactorings for better separation. --- .../{AdobeDCT.java => AdobeDCTSegment.java} | 8 +- .../imageio/plugins/jpeg/JPEGImageReader.java | 97 +++---------------- .../imageio/plugins/jpeg/SOFComponent.java | 59 +++++++++++ .../imageio/plugins/jpeg/SOFSegment.java | 64 ++++++++++++ 4 files changed, 142 insertions(+), 86 deletions(-) rename imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/{AdobeDCT.java => AdobeDCTSegment.java} (93%) create mode 100644 imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/SOFComponent.java create mode 100644 imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/SOFSegment.java diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/AdobeDCT.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/AdobeDCTSegment.java similarity index 93% rename from imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/AdobeDCT.java rename to imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/AdobeDCTSegment.java index c95a8355..1d0651b7 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/AdobeDCT.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/AdobeDCTSegment.java @@ -29,13 +29,13 @@ package com.twelvemonkeys.imageio.plugins.jpeg; /** - * AdobeDCT + * AdobeDCTSegment * * @author Harald Kuhr * @author last modified by $Author: haraldk$ - * @version $Id: AdobeDCT.java,v 1.0 23.04.12 16:55 haraldk Exp$ + * @version $Id: AdobeDCTSegment.java,v 1.0 23.04.12 16:55 haraldk Exp$ */ -class AdobeDCT { +class AdobeDCTSegment { public static final int Unknown = 0; public static final int YCC = 1; public static final int YCCK = 2; @@ -45,7 +45,7 @@ class AdobeDCT { final int flags1; final int transform; - public AdobeDCT(int version, int flags0, int flags1, int transform) { + AdobeDCTSegment(int version, int flags0, int flags1, int transform) { this.version = version; // 100 or 101 this.flags0 = flags0; this.flags1 = flags1; diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java index 8128f6f3..3fa67345 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java @@ -58,7 +58,7 @@ import java.util.List; /** * A JPEG {@code ImageReader} implementation based on the JRE {@code JPEGImageReader}, - * with support for CMYK/YCCK JPEGs, non-standard color spaces,broken ICC profiles + * with support for CMYK/YCCK JPEGs, non-standard color spaces, broken ICC profiles * and more. * * @author Harald Kuhr @@ -268,14 +268,14 @@ public class JPEGImageReader extends ImageReaderBase { boolean unsupported = !delegate.getImageTypes(imageIndex).hasNext(); ICC_Profile profile = getEmbeddedICCProfile(); - AdobeDCT adobeDCT = getAdobeDCT(); + AdobeDCTSegment adobeDCT = getAdobeDCT(); // TODO: Probably something bogus here, as ICC profile isn't applied if reading through the delegate any more... // We need to apply ICC profile unless the profile is sRGB/default gray (whatever that is) // - or only filter out the bad ICC profiles in the JPEGSegmentImageInputStream. if (delegate.canReadRaster() && ( unsupported || - adobeDCT != null && adobeDCT.getTransform() == AdobeDCT.YCCK || + adobeDCT != null && adobeDCT.getTransform() == AdobeDCTSegment.YCCK || profile != null && (ColorSpaces.isOffendingColorProfile(profile) || profile.getColorSpaceType() == ColorSpace.TYPE_CMYK))) { if (DEBUG) { System.out.println("Reading using raster and extra conversion"); @@ -296,8 +296,8 @@ public class JPEGImageReader extends ImageReaderBase { int origWidth = getWidth(imageIndex); int origHeight = getHeight(imageIndex); - AdobeDCT adobeDCT = getAdobeDCT(); - SOF startOfFrame = getSOF(); + AdobeDCTSegment adobeDCT = getAdobeDCT(); + SOFSegment startOfFrame = getSOF(); JPEGColorSpace csType = getSourceCSType(adobeDCT, startOfFrame); Iterator imageTypes = getImageTypes(imageIndex); @@ -436,7 +436,7 @@ public class JPEGImageReader extends ImageReaderBase { return image; } - static JPEGColorSpace getSourceCSType(AdobeDCT adobeDCT, final SOF startOfFrame) throws IIOException { + static JPEGColorSpace getSourceCSType(AdobeDCTSegment adobeDCT, final SOFSegment startOfFrame) throws IIOException { /* ADAPTED from http://download.oracle.com/javase/6/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html: @@ -478,11 +478,11 @@ public class JPEGImageReader extends ImageReaderBase { if (adobeDCT != null) { switch (adobeDCT.getTransform()) { - case AdobeDCT.YCC: + case AdobeDCTSegment.YCC: return JPEGColorSpace.YCbCr; - case AdobeDCT.YCCK: + case AdobeDCTSegment.YCCK: return JPEGColorSpace.YCCK; - case AdobeDCT.Unknown: + case AdobeDCTSegment.Unknown: if (startOfFrame.components.length == 1) { return JPEGColorSpace.Gray; } @@ -639,7 +639,7 @@ public class JPEGImageReader extends ImageReaderBase { return appSegments; } - private SOF getSOF() throws IOException { + private SOFSegment getSOF() throws IOException { for (JPEGSegment segment : segments) { if (JPEG.SOF0 >= segment.marker() && segment.marker() <= JPEG.SOF3 || JPEG.SOF5 >= segment.marker() && segment.marker() <= JPEG.SOF7 || @@ -664,7 +664,7 @@ public class JPEGImageReader extends ImageReaderBase { components[i] = new SOFComponent(id, ((sub & 0xF0) >> 4), (sub & 0xF), qtSel); } - return new SOF(segment.marker(), samplePrecision, lines, samplesPerLine, componentsInFrame, components); + return new SOFSegment(segment.marker(), samplePrecision, lines, samplesPerLine, componentsInFrame, components); } finally { data.close(); @@ -675,7 +675,7 @@ public class JPEGImageReader extends ImageReaderBase { return null; } - private AdobeDCT getAdobeDCT() throws IOException { + private AdobeDCTSegment getAdobeDCT() throws IOException { // TODO: Investigate http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6355567: 33/35 byte Adobe APP14 markers List adobe = getAppSegments(JPEG.APP14, "Adobe"); @@ -683,7 +683,7 @@ public class JPEGImageReader extends ImageReaderBase { // version (byte), flags (4bytes), color transform (byte: 0=unknown, 1=YCC, 2=YCCK) DataInputStream stream = new DataInputStream(adobe.get(0).data()); - return new AdobeDCT( + return new AdobeDCTSegment( stream.readUnsignedByte(), stream.readUnsignedShort(), stream.readUnsignedShort(), @@ -1145,73 +1145,6 @@ public class JPEGImageReader extends ImageReaderBase { } } - private static class SOF { - private final int marker; - private final int samplePrecision; - private final int lines; // height - private final int samplesPerLine; // width - private final int componentsInFrame; - final SOFComponent[] components; - - public SOF(int marker, int samplePrecision, int lines, int samplesPerLine, int componentsInFrame, SOFComponent[] components) { - this.marker = marker; - this.samplePrecision = samplePrecision; - this.lines = lines; - this.samplesPerLine = samplesPerLine; - this.componentsInFrame = componentsInFrame; - this.components = components; - } - - public int getMarker() { - return marker; - } - - public int getSamplePrecision() { - return samplePrecision; - } - - public int getLines() { - return lines; - } - - public int getSamplesPerLine() { - return samplesPerLine; - } - - public int getComponentsInFrame() { - return componentsInFrame; - } - - @Override - public String toString() { - return String.format( - "SOF%d[%04x, precision: %d, lines: %d, samples/line: %d, components: %s]", - marker & 0xf, marker, samplePrecision, lines, samplesPerLine, Arrays.toString(components) - ); - } - } - - private static class SOFComponent { - final int id; - final int hSub; - final int vSub; - final int qtSel; - - public SOFComponent(int id, int hSub, int vSub, int qtSel) { - this.id = id; - this.hSub = hSub; - this.vSub = vSub; - this.qtSel = qtSel; - } - - @Override - public String toString() { - // Use id either as component number or component name, based on value - Serializable idStr = (id >= 'a' && id <= 'z' || id >= 'A' && id <= 'Z') ? "'" + (char) id + "'" : id; - return String.format("id: %s, sub: %d/%d, sel: %d", idStr, hSub, vSub, qtSel); - } - } - protected static void showIt(final BufferedImage pImage, final String pTitle) { ImageReaderBase.showIt(pImage, pTitle); } @@ -1282,7 +1215,7 @@ public class JPEGImageReader extends ImageReaderBase { // param.setSourceSubsampling(sub, sub, 0, 0); // } - long start = System.currentTimeMillis(); +// long start = System.currentTimeMillis(); BufferedImage image = reader.read(0, param); // System.err.println("Read time: " + (System.currentTimeMillis() - start) + " ms"); // System.err.println("image: " + image); @@ -1295,7 +1228,7 @@ public class JPEGImageReader extends ImageReaderBase { int maxW = 400; int maxH = 400; if (image.getWidth() > maxW || image.getHeight() > maxH) { - start = System.currentTimeMillis(); +// start = System.currentTimeMillis(); float aspect = reader.getAspectRatio(0); if (aspect >= 1f) { image = ImageUtil.createResampled(image, maxW, Math.round(maxW / aspect), Image.SCALE_DEFAULT); diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/SOFComponent.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/SOFComponent.java new file mode 100644 index 00000000..3416aef8 --- /dev/null +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/SOFComponent.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2013, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.jpeg; + +import java.io.Serializable; + +/** +* SOFComponent +* +* @author Harald Kuhr +* @author last modified by $Author: haraldk$ +* @version $Id: SOFComponent.java,v 1.0 22.04.13 16:40 haraldk Exp$ +*/ +final class SOFComponent { + final int id; + final int hSub; + final int vSub; + final int qtSel; + + SOFComponent(int id, int hSub, int vSub, int qtSel) { + this.id = id; + this.hSub = hSub; + this.vSub = vSub; + this.qtSel = qtSel; + } + + @Override + public String toString() { + // Use id either as component number or component name, based on value + Serializable idStr = (id >= 'a' && id <= 'z' || id >= 'A' && id <= 'Z') ? "'" + (char) id + "'" : id; + return String.format("id: %s, sub: %d/%d, sel: %d", idStr, hSub, vSub, qtSel); + } +} diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/SOFSegment.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/SOFSegment.java new file mode 100644 index 00000000..20603bde --- /dev/null +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/SOFSegment.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2013, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.jpeg; + +import java.util.Arrays; + +/** +* SOFSegment +* +* @author Harald Kuhr +* @author last modified by $Author: haraldk$ +* @version $Id: SOFSegment.java,v 1.0 22.04.13 16:40 haraldk Exp$ +*/ +final class SOFSegment { + final int marker; + final int samplePrecision; + final int lines; // height + final int samplesPerLine; // width + final int componentsInFrame; + final SOFComponent[] components; + + SOFSegment(int marker, int samplePrecision, int lines, int samplesPerLine, int componentsInFrame, SOFComponent[] components) { + this.marker = marker; + this.samplePrecision = samplePrecision; + this.lines = lines; + this.samplesPerLine = samplesPerLine; + this.componentsInFrame = componentsInFrame; + this.components = components; + } + + @Override + public String toString() { + return String.format( + "SOF%d[%04x, precision: %d, lines: %d, samples/line: %d, components: %s]", + marker & 0xf, marker, samplePrecision, lines, samplesPerLine, Arrays.toString(components) + ); + } +} From 0e628f6e4c3c266111ce66f6965c811044b484b6 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Wed, 5 Jun 2013 09:58:32 +0200 Subject: [PATCH 42/58] TMI-CORE: Added empty ICC profile locations for Linux. + Better exception handling for missing profile locations. --- .../imageio/color/ColorSpaces.java | 30 +++++++++++++++++-- .../imageio/color/icc_profiles_lnx.properties | 29 ++++++++++++++++++ .../imageio/color/icc_profiles_win.properties | 2 +- .../imageio/color/ColorSpacesTest.java | 22 ++++++++++++-- 4 files changed, 78 insertions(+), 5 deletions(-) create mode 100644 imageio/imageio-core/src/main/resources/com/twelvemonkeys/imageio/color/icc_profiles_lnx.properties diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ColorSpaces.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ColorSpaces.java index 702a7cc3..767e6b8e 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ColorSpaces.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ColorSpaces.java @@ -173,6 +173,21 @@ public final class ColorSpaces { } } + /** + * Tests whether an ICC color profile is equal to the default sRGB profile. + * + * @param profile the ICC profile to test. May not be {@code null}. + * @return {@code true} if {@code profile} is equal to the default sRGB profile. + * @throws IllegalArgumentException if {@code profile} is {@code null} + * + * @see java.awt.color.ColorSpace#isCS_sRGB() + */ + public static boolean isCS_sRGB(final ICC_Profile profile) { + Validate.notNull(profile, "profile"); + + return profile.getColorSpaceType() == ColorSpace.TYPE_RGB && Arrays.equals(profile.getData(ICC_Profile.icSigHead), sRGB.header); + } + /** * Tests whether an ICC color profile is known to cause problems for {@link java.awt.image.ColorConvertOp}. *

@@ -229,7 +244,7 @@ public final class ColorSpaces { if (profile == null) { // Fall back to the bundled ClayRGB1998 public domain Adobe RGB 1998 compatible profile, - // identical for all practical purposes + // which is identical for all practical purposes profile = readProfileFromClasspathResource("/profiles/ClayRGB1998.icc"); if (profile == null) { @@ -339,15 +354,19 @@ public final class ColorSpaces { private static class sRGB { private static final byte[] header = ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData(ICC_Profile.icSigHead); } + private static class CIEXYZ { private static final byte[] header = ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ).getData(ICC_Profile.icSigHead); } + private static class PYCC { private static final byte[] header = ICC_Profile.getInstance(ColorSpace.CS_PYCC).getData(ICC_Profile.icSigHead); } + private static class GRAY { private static final byte[] header = ICC_Profile.getInstance(ColorSpace.CS_GRAY).getData(ICC_Profile.icSigHead); } + private static class LINEAR_RGB { private static final byte[] header = ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB).getData(ICC_Profile.icSigHead); } @@ -361,7 +380,14 @@ public final class ColorSpaces { systemDefaults = SystemUtil.loadProperties(ColorSpaces.class, "com/twelvemonkeys/imageio/color/icc_profiles_" + os.id()); } catch (IOException ignore) { - ignore.printStackTrace(); + System.err.printf( + "Warning: Could not load system default ICC profile locations from %s, will use bundled fallback profiles.\n", + ignore.getMessage() + ); + if (DEBUG) { + ignore.printStackTrace(); + } + systemDefaults = null; } diff --git a/imageio/imageio-core/src/main/resources/com/twelvemonkeys/imageio/color/icc_profiles_lnx.properties b/imageio/imageio-core/src/main/resources/com/twelvemonkeys/imageio/color/icc_profiles_lnx.properties new file mode 100644 index 00000000..706fe6d6 --- /dev/null +++ b/imageio/imageio-core/src/main/resources/com/twelvemonkeys/imageio/color/icc_profiles_lnx.properties @@ -0,0 +1,29 @@ +# +# Copyright (c) 2013, Harald Kuhr +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name "TwelveMonkeys" nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +#GENERIC_CMYK=unknown, use built in for now +#ADOBE_RGB_1998=unknown, use built in for now \ No newline at end of file diff --git a/imageio/imageio-core/src/main/resources/com/twelvemonkeys/imageio/color/icc_profiles_win.properties b/imageio/imageio-core/src/main/resources/com/twelvemonkeys/imageio/color/icc_profiles_win.properties index bab2bf93..21fef9f9 100644 --- a/imageio/imageio-core/src/main/resources/com/twelvemonkeys/imageio/color/icc_profiles_win.properties +++ b/imageio/imageio-core/src/main/resources/com/twelvemonkeys/imageio/color/icc_profiles_win.properties @@ -26,4 +26,4 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # GENERIC_CMYK=/C:/Windows/System32/spool/drivers/color/RSWOP.icm -#ADOBE_RGB_1998=use built in for now \ No newline at end of file +#ADOBE_RGB_1998=unknown, use built in for now \ No newline at end of file diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/ColorSpacesTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/ColorSpacesTest.java index 9dddc7aa..ac154765 100644 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/ColorSpacesTest.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/ColorSpacesTest.java @@ -139,7 +139,7 @@ public class ColorSpacesTest { assertSame(cs, ColorSpaces.createColorSpace(iccCs.getProfile())); } else { - System.err.println("Not an ICC_ColorSpace: " + cs); + System.err.println("WARNING: Not an ICC_ColorSpace: " + cs); } } @@ -163,7 +163,25 @@ public class ColorSpacesTest { assertSame(cs, ColorSpaces.createColorSpace(iccCs.getProfile())); } else { - System.err.println("Not an ICC_ColorSpace: " + cs); + System.err.println("Warning: Not an ICC_ColorSpace: " + cs); } } + + @Test + public void testIsCS_sRGBTrue() { + assertTrue(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_sRGB))); + } + + @Test + public void testIsCS_sRGBFalse() { + assertFalse(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB))); + assertFalse(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ))); + assertFalse(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_GRAY))); + assertFalse(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_PYCC))); + } + + @Test(expected = IllegalArgumentException.class) + public void testIsCS_sRGBNull() { + ColorSpaces.isCS_sRGB(null); + } } From e72988aa7bec72c899cf150b1cee0626f784520f Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Wed, 5 Jun 2013 10:45:31 +0200 Subject: [PATCH 43/58] TMC-IMAGE: Code clean-up. Removed obsolete code. No functional changes. --- .../image/BufferedImageFactory.java | 109 +++++++++++++---- .../com/twelvemonkeys/image/ImageUtil.java | 110 +++--------------- .../image/InverseColorMapIndexColorModel.java | 21 ++-- 3 files changed, 112 insertions(+), 128 deletions(-) diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/BufferedImageFactory.java b/common/common-image/src/main/java/com/twelvemonkeys/image/BufferedImageFactory.java index 69de39b3..d6b097ff 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/BufferedImageFactory.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/BufferedImageFactory.java @@ -259,11 +259,9 @@ public final class BufferedImageFactory { sourceProperties = null; } - private void processProgress(int mScanline) { + private void processProgress(int scanline) { if (listeners != null) { - int percent = 100 * mScanline / height; - - //System.out.println("Progress: " + percent + "%"); + int percent = 100 * scanline / height; if (percent > percentageDone) { percentageDone = percent; @@ -323,7 +321,7 @@ public final class BufferedImageFactory { * pixels. The conversion is done, by masking out the * higher 16 bits of the {@code int}. * - * For eny given {@code int}, the {@code short} value is computed as + * For any given {@code int}, the {@code short} value is computed as * follows: *

{@code * short value = (short) (intValue & 0x0000ffff); @@ -334,9 +332,11 @@ public final class BufferedImageFactory { */ private static short[] toShortPixels(int[] pPixels) { short[] pixels = new short[pPixels.length]; + for (int i = 0; i < pixels.length; i++) { pixels[i] = (short) (pPixels[i] & 0xffff); } + return pixels; } @@ -507,24 +507,11 @@ public final class BufferedImageFactory { } public void setPixels(int pX, int pY, int pWidth, int pHeight, ColorModel pModel, byte[] pPixels, int pOffset, int pScanSize) { - /*if (pModel.getPixelSize() < 8) { - // Byte packed - setPixelsImpl(pX, pY, pWidth, pHeight, pModel, toBytePackedPixels(pPixels, pModel.getPixelSize()), pOffset, pScanSize); - } - /* - else if (pModel.getPixelSize() > 8) { - // Byte interleaved - setPixelsImpl(pX, pY, pWidth, pHeight, pModel, toByteInterleavedPixels(pPixels), pOffset, pScanSize); - } - */ - //else { - // Default, pixelSize == 8, one byte pr pixel - setPixelsImpl(pX, pY, pWidth, pHeight, pModel, pPixels, pOffset, pScanSize); - //} + setPixelsImpl(pX, pY, pWidth, pHeight, pModel, pPixels, pOffset, pScanSize); } public void setPixels(int pX, int pY, int pWeigth, int pHeight, ColorModel pModel, int[] pPixels, int pOffset, int pScanSize) { - if (ImageUtil.getTransferType(pModel) == DataBuffer.TYPE_USHORT) { + if (pModel.getTransferType() == DataBuffer.TYPE_USHORT) { // NOTE: Workaround for limitation in ImageConsumer API // Convert int[] to short[], to be compatible with the ColorModel setPixelsImpl(pX, pY, pWeigth, pHeight, pModel, toShortPixels(pPixels), pOffset, pScanSize); @@ -538,4 +525,86 @@ public final class BufferedImageFactory { sourceProperties = pProperties; } } + + /* + public static void main(String[] args) throws InterruptedException { + Image image = Toolkit.getDefaultToolkit().createImage(args[0]); + System.err.printf("image: %s (which is %sa buffered image)\n", image, image instanceof BufferedImage ? "" : "not "); + + int warmUpLoops = 500; + int testLoops = 100; + + for (int i = 0; i < warmUpLoops; i++) { + // Warm up... + convertUsingFactory(image); + convertUsingPixelGrabber(image); + convertUsingPixelGrabberNaive(image); + } + + BufferedImage bufferedImage = null; + long start = System.currentTimeMillis(); + for (int i = 0; i < testLoops; i++) { + bufferedImage = convertUsingFactory(image); + } + System.err.printf("Conversion time (factory): %f ms (image: %s)\n", (System.currentTimeMillis() - start) / (double) testLoops, bufferedImage); + + start = System.currentTimeMillis(); + for (int i = 0; i < testLoops; i++) { + bufferedImage = convertUsingPixelGrabber(image); + } + System.err.printf("Conversion time (grabber): %f ms (image: %s)\n", (System.currentTimeMillis() - start) / (double) testLoops, bufferedImage); + + start = System.currentTimeMillis(); + for (int i = 0; i < testLoops; i++) { + bufferedImage = convertUsingPixelGrabberNaive(image); + } + System.err.printf("Conversion time (naive g): %f ms (image: %s)\n", (System.currentTimeMillis() - start) / (double) testLoops, bufferedImage); + } + + private static BufferedImage convertUsingPixelGrabberNaive(Image image) throws InterruptedException { + // NOTE: It does not matter if we wait for the image or not, the time is about the same as it will only happen once + if ((image.getWidth(null) < 0 || image.getHeight(null) < 0) && !ImageUtil.waitForImage(image)) { + System.err.printf("Could not get image dimensions for image %s\n", image.getSource()); + } + + int w = image.getWidth(null); + int h = image.getHeight(null); + PixelGrabber grabber = new PixelGrabber(image, 0, 0, w, h, true); // force RGB + grabber.grabPixels(); + + // Following casts are safe, as we force RGB in the pixel grabber + int[] pixels = (int[]) grabber.getPixels(); + + BufferedImage bufferedImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); +// bufferedImage.setRGB(0, 0, w, h, pixels, 0, w); + bufferedImage.getRaster().setDataElements(0, 0, w, h, pixels); + + return bufferedImage; + } + + private static BufferedImage convertUsingPixelGrabber(Image image) throws InterruptedException { + // NOTE: It does not matter if we wait for the image or not, the time is about the same as it will only happen once + if ((image.getWidth(null) < 0 || image.getHeight(null) < 0) && !ImageUtil.waitForImage(image)) { + System.err.printf("Could not get image dimensions for image %s\n", image.getSource()); + } + + int w = image.getWidth(null); + int h = image.getHeight(null); + PixelGrabber grabber = new PixelGrabber(image, 0, 0, w, h, true); // force RGB + grabber.grabPixels(); + + // Following casts are safe, as we force RGB in the pixel grabber +// DirectColorModel cm = (DirectColorModel) grabber.getColorModel(); + DirectColorModel cm = (DirectColorModel) ColorModel.getRGBdefault(); + int[] pixels = (int[]) grabber.getPixels(); + + WritableRaster raster = Raster.createPackedRaster(new DataBufferInt(pixels, pixels.length), w, h, w, cm.getMasks(), null); + + return new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null); + } + + private static BufferedImage convertUsingFactory(Image image) { + return new BufferedImageFactory(image).getBufferedImage(); + } + */ } \ No newline at end of file diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/ImageUtil.java b/common/common-image/src/main/java/com/twelvemonkeys/image/ImageUtil.java index 721051b9..c3d2f87d 100644 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/ImageUtil.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/ImageUtil.java @@ -181,9 +181,6 @@ public final class ImageUtil { /** */ protected static final Point LOCATION_UPPER_LEFT = new Point(0, 0); - /** */ - private static final boolean COLORMODEL_TRANSFERTYPE_SUPPORTED = isColorModelTransferTypeSupported(); - /** */ private static final GraphicsConfiguration DEFAULT_CONFIGURATION = getDefaultGraphicsConfiguration(); @@ -205,22 +202,6 @@ public final class ImageUtil { private ImageUtil() { } - /** - * Tests if {@code ColorModel} has a {@code getTransferType} method. - * - * @return {@code true} if {@code ColorModel} has a - * {@code getTransferType} method - */ - private static boolean isColorModelTransferTypeSupported() { - try { - ColorModel.getRGBdefault().getTransferType(); - return true; - } - catch (Throwable t) { - return false; - } - } - /** * Converts the {@code RenderedImage} to a {@code BufferedImage}. * The new image will have the same {@code ColorModel}, @@ -378,7 +359,7 @@ public final class ImageUtil { /** * Creates a copy of the given image. The image will have the same - * colormodel and raster type, but will not share image (pixel) data. + * color model and raster type, but will not share image (pixel) data. * * @param pImage the image to clone. * @@ -408,11 +389,11 @@ public final class ImageUtil { *

* This method is optimized for the most common cases of {@code ColorModel} * and pixel data combinations. The raster's backing {@code DataBuffer} is - * created directly from the pixel data, as this is faster and with more + * created directly from the pixel data, as this is faster and more * resource-friendly than using * {@code ColorModel.createCompatibleWritableRaster(w, h)}. *

- * For unknown combinations, the method will fallback to using + * For uncommon combinations, the method will fallback to using * {@code ColorModel.createCompatibleWritableRaster(w, h)} and * {@code WritableRaster.setDataElements(w, h, pixels)} *

@@ -438,8 +419,8 @@ public final class ImageUtil { */ static WritableRaster createRaster(int pWidth, int pHeight, Object pPixels, ColorModel pColorModel) { // NOTE: This is optimized code for most common cases. - // We create a DataBuffer with the array from grabber.getPixels() - // directly, and creating a raster based on the ColorModel. + // We create a DataBuffer from the pixel array directly, + // and creating a raster based on the DataBuffer and ColorModel. // Creating rasters this way is faster and more resource-friendly, as // cm.createCompatibleWritableRaster allocates an // "empty" DataBuffer with a storage array of w*h. This array is @@ -453,14 +434,12 @@ public final class ImageUtil { if (pPixels instanceof int[]) { int[] data = (int[]) pPixels; buffer = new DataBufferInt(data, data.length); - //bands = data.length / (w * h); bands = pColorModel.getNumComponents(); } else if (pPixels instanceof short[]) { short[] data = (short[]) pPixels; buffer = new DataBufferUShort(data, data.length); bands = data.length / (pWidth * pHeight); - //bands = cm.getNumComponents(); } else if (pPixels instanceof byte[]) { byte[] data = (byte[]) pPixels; @@ -473,47 +452,30 @@ public final class ImageUtil { else { bands = data.length / (pWidth * pHeight); } - - //bands = pColorModel.getNumComponents(); - //System.out.println("Pixels: " + data.length + " (" + buffer.getSize() + ")"); - //System.out.println("w*h*bands: " + (pWidth * pHeight * bands)); - //System.out.println("Bands: " + bands); - //System.out.println("Numcomponents: " + pColorModel.getNumComponents()); } else { - //System.out.println("Fallback!"); // Fallback mode, slower & requires more memory, but compatible bands = -1; - // Create raster from colormodel, w and h + // Create raster from color model, w and h raster = pColorModel.createCompatibleWritableRaster(pWidth, pHeight); raster.setDataElements(0, 0, pWidth, pHeight, pPixels); // Note: This is known to throw ClassCastExceptions.. } - //System.out.println("Bands: " + bands); - //System.out.println("Pixels: " + pixels.getClass() + " length: " + buffer.getSize()); - //System.out.println("Needed Raster: " + cm.createCompatibleWritableRaster(1, 1)); - if (raster == null) { - //int bits = cm.getPixelSize(); - //if (bits > 4) { if (pColorModel instanceof IndexColorModel && isIndexedPacked((IndexColorModel) pColorModel)) { - //System.out.println("Creating packed indexed model"); raster = Raster.createPackedRaster(buffer, pWidth, pHeight, pColorModel.getPixelSize(), LOCATION_UPPER_LEFT); } else if (pColorModel instanceof PackedColorModel) { - //System.out.println("Creating packed model"); PackedColorModel pcm = (PackedColorModel) pColorModel; raster = Raster.createPackedRaster(buffer, pWidth, pHeight, pWidth, pcm.getMasks(), LOCATION_UPPER_LEFT); } else { - //System.out.println("Creating interleaved model"); // (A)BGR order... For TYPE_3BYTE_BGR/TYPE_4BYTE_ABGR/TYPE_4BYTE_ABGR_PRE. int[] bandsOffsets = new int[bands]; for (int i = 0; i < bands;) { bandsOffsets[i] = bands - (++i); } - //System.out.println("zzz Data array: " + buffer.getSize()); raster = Raster.createInterleavedRaster(buffer, pWidth, pHeight, pWidth * bands, bands, bandsOffsets, LOCATION_UPPER_LEFT); } @@ -1782,7 +1744,7 @@ public final class ImageUtil { * @param pTimeOut the time to wait, in milliseconds. * * @return true if the image was loaded successfully, false if an error - * occured, or the wait was interrupted. + * occurred, or the wait was interrupted. * * @see #waitForImages(Image[],long) */ @@ -1797,7 +1759,7 @@ public final class ImageUtil { * @param pImages an array of Image objects to wait for. * * @return true if the images was loaded successfully, false if an error - * occured, or the wait was interrupted. + * occurred, or the wait was interrupted. * * @see #waitForImages(Image[],long) */ @@ -1813,7 +1775,7 @@ public final class ImageUtil { * @param pTimeOut the time to wait, in milliseconds * * @return true if the images was loaded successfully, false if an error - * occured, or the wait was interrupted. + * occurred, or the wait was interrupted. */ public static boolean waitForImages(Image[] pImages, long pTimeOut) { // TODO: Need to make sure that we don't wait for the same image many times @@ -1868,7 +1830,7 @@ public final class ImageUtil { } /** - * Tests wether the image has any transparent or semi-transparent pixels. + * Tests whether the image has any transparent or semi-transparent pixels. * * @param pImage the image * @param pFast if {@code true}, the method tests maximum 10 x 10 pixels, @@ -1936,7 +1898,7 @@ public final class ImageUtil { } /** - * Blends two ARGB values half and half, to create a tone inbetween. + * Blends two ARGB values half and half, to create a tone in between. * * @param pRGB1 color 1 * @param pRGB2 color 2 @@ -1949,7 +1911,7 @@ public final class ImageUtil { } /** - * Blends two colors half and half, to create a tone inbetween. + * Blends two colors half and half, to create a tone in between. * * @param pColor color 1 * @param pOther color 2 @@ -1967,7 +1929,7 @@ public final class ImageUtil { } /** - * Blends two colors, controlled by the blendfactor. + * Blends two colors, controlled by the blending factor. * A factor of {@code 0.0} will return the first color, * a factor of {@code 1.0} will return the second. * @@ -1989,50 +1951,4 @@ public final class ImageUtil { private static int clamp(float f) { return (int) f; } - /** - * PixelGrabber subclass that stores any potential properties from an image. - */ - /* - private static class MyPixelGrabber extends PixelGrabber { - private Hashtable mProps = null; - - public MyPixelGrabber(Image pImage) { - // Simply grab all pixels, do not convert to default RGB space - super(pImage, 0, 0, -1, -1, false); - } - - // Default implementation does not store the properties... - public void setProperties(Hashtable pProps) { - super.setProperties(pProps); - mProps = pProps; - } - - public Hashtable getProperties() { - return mProps; - } - } - */ - - /** - * Gets the transfer type from the given {@code ColorModel}. - *

- * NOTE: This is a workaround for missing functionality in JDK 1.2. - * - * @param pModel the color model - * @return the transfer type - * - * @throws NullPointerException if {@code pModel} is {@code null}. - * - * @see java.awt.image.ColorModel#getTransferType() - */ - public static int getTransferType(ColorModel pModel) { - if (COLORMODEL_TRANSFERTYPE_SUPPORTED) { - return pModel.getTransferType(); - } - else { - // Stupid workaround - // TODO: Create something that performs better - return pModel.createCompatibleSampleModel(1, 1).getDataType(); - } - } } \ No newline at end of file diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/InverseColorMapIndexColorModel.java b/common/common-image/src/main/java/com/twelvemonkeys/image/InverseColorMapIndexColorModel.java index 8bba6e5e..640fa002 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/InverseColorMapIndexColorModel.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/InverseColorMapIndexColorModel.java @@ -30,6 +30,7 @@ package com.twelvemonkeys.image; import com.twelvemonkeys.lang.StringUtil; +import com.twelvemonkeys.lang.Validate; import java.awt.*; import java.awt.image.DataBuffer; @@ -37,7 +38,7 @@ import java.awt.image.IndexColorModel; /** * A faster implementation of {@code IndexColorModel}, that is backed by an - * inverse color-map, for fast lookups. + * inverse color-map, for fast look-ups. * * @author Harald Kuhr * @author $Author: haku $ @@ -60,19 +61,17 @@ public class InverseColorMapIndexColorModel extends IndexColorModel { * Creates an {@code InverseColorMapIndexColorModel} from an existing * {@code IndexColorModel}. * - * @param pColorModel the colormodel to create from + * @param pColorModel the color model to create from. + * @throws IllegalArgumentException if {@code pColorModel} is {@code null} */ - public InverseColorMapIndexColorModel(IndexColorModel pColorModel) { - this(pColorModel, getRGBs(pColorModel)); + public InverseColorMapIndexColorModel(final IndexColorModel pColorModel) { + this(Validate.notNull(pColorModel, "color model"), getRGBs(pColorModel)); } // NOTE: The pRGBs parameter is used to get around invoking getRGBs two // times. What is wrong with protected?! private InverseColorMapIndexColorModel(IndexColorModel pColorModel, int[] pRGBs) { - super(pColorModel.getComponentSize()[0], pColorModel.getMapSize(), - pRGBs, 0, - ImageUtil.getTransferType(pColorModel), - pColorModel.getValidPixels()); + super(pColorModel.getComponentSize()[0], pColorModel.getMapSize(), pRGBs, 0, pColorModel.getTransferType(), pColorModel.getValidPixels()); rgbs = pRGBs; mapSize = rgbs.length; @@ -82,11 +81,11 @@ public class InverseColorMapIndexColorModel extends IndexColorModel { } /** - * Creates a defensive copy of the RGB colormap in the given + * Creates a defensive copy of the RGB color map in the given * {@code IndexColorModel}. * - * @param pColorModel the indec colormodel to get RGB values from - * @return the RGB colormap + * @param pColorModel the indexed color model to get RGB values from + * @return the RGB color map */ private static int[] getRGBs(IndexColorModel pColorModel) { int[] rgb = new int[pColorModel.getMapSize()]; From 2cf1c6e43b9b20b4b1bc295a49f813de8239d5da Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Wed, 5 Jun 2013 10:46:41 +0200 Subject: [PATCH 44/58] TMC-IMAGE: Fixed some typos. --- .../java/com/twelvemonkeys/image/BrightnessContrastFilter.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/BrightnessContrastFilter.java b/common/common-image/src/main/java/com/twelvemonkeys/image/BrightnessContrastFilter.java index da7e8a48..2b4fb736 100755 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/BrightnessContrastFilter.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/BrightnessContrastFilter.java @@ -66,7 +66,7 @@ public class BrightnessContrastFilter extends RGBImageFilter { canFilterIndexColorModel = true; } - // Use a precalculated lookup table for performace + // Use a pre-calculated lookup table for performance private final int[] LUT; /** @@ -149,7 +149,6 @@ public class BrightnessContrastFilter extends RGBImageFilter { * * @return the filtered pixel value in the default color space */ - public int filterRGB(int pX, int pY, int pARGB) { // Get color components int r = pARGB >> 16 & 0xFF; From 28e2f3c21b06391dac1f4612388f0b62279c96c2 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Wed, 5 Jun 2013 10:47:57 +0200 Subject: [PATCH 45/58] TMC-CORE: Fixed typos --- .../main/resources/com/twelvemonkeys/net/MIMEUtil.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/common-io/src/main/resources/com/twelvemonkeys/net/MIMEUtil.properties b/common/common-io/src/main/resources/com/twelvemonkeys/net/MIMEUtil.properties index 9598cc47..566e0a16 100755 --- a/common/common-io/src/main/resources/com/twelvemonkeys/net/MIMEUtil.properties +++ b/common/common-io/src/main/resources/com/twelvemonkeys/net/MIMEUtil.properties @@ -60,7 +60,7 @@ iff,ilbm=image/x-iff;image/iff jpeg,jpg,jpe,jfif=image/jpeg;image/x-jpeg jpm=image/jpm png=image/png;image/x-png -# NOTE: image/svg-xml is an old reccomendation, should not be used +# NOTE: image/svg-xml is an old recommendation, should not be used svg,svgz=image/svg+xml;image/svg-xml;image/x-svg tga=image/targa;image/x-targa tif,tiff=image/tiff;image/x-tiff From f8c40a3748cd1ada6c19516d579873c1c57d1c49 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Wed, 5 Jun 2013 10:49:37 +0200 Subject: [PATCH 46/58] TMI-JPEG: Better CMYK to RGB algorithm --- .../imageio/plugins/jpeg/FastCMYKToRGB.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/FastCMYKToRGB.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/FastCMYKToRGB.java index 5897a7aa..58a96211 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/FastCMYKToRGB.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/FastCMYKToRGB.java @@ -131,11 +131,12 @@ class FastCMYKToRGB implements /*BufferedImageOp,*/ RasterOp { return dest; } - @SuppressWarnings({"PointlessArithmeticExpression"}) private void convertCMYKToRGB(byte[] cmyk, byte[] rgb) { - rgb[0] = (byte) (((255 - cmyk[0] & 0xFF) * (255 - cmyk[3] & 0xFF)) / 255); - rgb[1] = (byte) (((255 - cmyk[1] & 0xFF) * (255 - cmyk[3] & 0xFF)) / 255); - rgb[2] = (byte) (((255 - cmyk[2] & 0xFF) * (255 - cmyk[3] & 0xFF)) / 255); + // Adapted from http://www.easyrgb.com/index.php?X=MATH + final int k = cmyk[3] & 0xFF; + rgb[0] = (byte) (255 - (((cmyk[0] & 0xFF) * (255 - k) / 255) + k)); + rgb[1] = (byte) (255 - (((cmyk[1] & 0xFF) * (255 - k) / 255) + k)); + rgb[2] = (byte) (255 - (((cmyk[2] & 0xFF) * (255 - k) / 255) + k)); } public Rectangle2D getBounds2D(Raster src) { From 544d60dabb59c25e732ac1ec89187922a85a4335 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Wed, 5 Jun 2013 10:54:51 +0200 Subject: [PATCH 47/58] TMI-JPEG: Fixed ICC profile issue. Now applies profiles when it should. Profiles with bad indexes are now ignored on read. Added support for JPEG-LS SOF55 segment (no further JPEG-LS support) Added class documentation. --- .../imageio/plugins/jpeg/JPEGImageReader.java | 120 +++++++++++++++--- .../jpeg/JPEGSegmentImageInputStream.java | 9 +- .../imageio/plugins/jpeg/SOFSegment.java | 10 +- .../imageio/metadata/jpeg/JPEG.java | 4 + 4 files changed, 118 insertions(+), 25 deletions(-) diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java index 3fa67345..762d8de3 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java @@ -41,10 +41,13 @@ import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment; import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil; import com.twelvemonkeys.imageio.util.ProgressListenerBase; import com.twelvemonkeys.lang.Validate; +import org.w3c.dom.Node; import javax.imageio.*; import javax.imageio.event.IIOReadUpdateListener; import javax.imageio.event.IIOReadWarningListener; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.spi.ImageReaderSpi; import javax.imageio.stream.ImageInputStream; import java.awt.*; @@ -58,8 +61,26 @@ import java.util.List; /** * A JPEG {@code ImageReader} implementation based on the JRE {@code JPEGImageReader}, - * with support for CMYK/YCCK JPEGs, non-standard color spaces, broken ICC profiles - * and more. + * that adds support and properly handles cases where the JRE version throws exceptions. + *

+ * Main features: + *

    + *
  • Support for CMYK JPEGs (converted to RGB by default, using the embedded ICC profile if applicable)
  • + *
  • Support for Adobe YCCK JPEGs (converted to RGB by default, using the embedded ICC profile if applicable)
  • + *
  • Support for JPEGs containing ICC profiles with interpretation other than 'Perceptual' (profile is assumed to be 'Perceptual' and used)
  • + *
  • Support for JPEGs containing ICC profiles with class other than 'Display' (profile is assumed to have class 'Display' and used)
  • + *
  • Support for JPEGs containing ICC profiles that are incompatible with stream data (image data is read, profile is ignored)
  • + *
  • Support for JPEGs with corrupted ICC profiles (image data is read, profile is ignored)
  • + *
  • Support for JPEGs with corrupted {@code ICC_PROFILE} segments (image data is read, profile is ignored)
  • + *
  • Support for JPEGs using non-standard color spaces, unsupported by Java 2D (image data is read, profile is ignored)
  • + *
  • Issues warnings instead of throwing exceptions in cases of corrupted data where ever the image data can still be read in a reasonable way
  • + *
+ * Thumbnail support: + *
    + *
  • Support for JFIF thumbnails (even if stream contains "inconsistent metadata")
  • + *
  • Support for JFXX thumbnails (JPEG, Indexed and RGB)
  • + *
  • Support for EXIF thumbnails (JPEG, RGB and YCbCr)
  • + *
* * @author Harald Kuhr * @author LUT-based YCbCR conversion by Werner Randelshofer @@ -76,7 +97,7 @@ public class JPEGImageReader extends ImageReaderBase { private static final Map> SEGMENT_IDENTIFIERS = createSegmentIds(); private static Map> createSegmentIds() { - Map> map = new HashMap>(); + Map> map = new LinkedHashMap>(); // JFIF/JFXX APP0 markers map.put(JPEG.APP0, JPEGSegmentUtil.ALL_IDS); @@ -216,8 +237,7 @@ public class JPEGImageReader extends ImageReaderBase { } @Override - public - ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException { + public ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException { // If delegate can determine the spec, we'll just go with that ImageTypeSpecifier rawType = delegate.getRawImageType(imageIndex); @@ -276,7 +296,8 @@ public class JPEGImageReader extends ImageReaderBase { if (delegate.canReadRaster() && ( unsupported || adobeDCT != null && adobeDCT.getTransform() == AdobeDCTSegment.YCCK || - profile != null && (ColorSpaces.isOffendingColorProfile(profile) || profile.getColorSpaceType() == ColorSpace.TYPE_CMYK))) { + profile != null && !ColorSpaces.isCS_sRGB(profile))) { +// profile != null && (ColorSpaces.isOffendingColorProfile(profile) || profile.getColorSpaceType() == ColorSpace.TYPE_CMYK))) { if (DEBUG) { System.out.println("Reading using raster and extra conversion"); System.out.println("ICC color profile: " + profile); @@ -316,12 +337,12 @@ public class JPEGImageReader extends ImageReaderBase { } else if (intendedCS != null) { // Handle inconsistencies - if (startOfFrame.componentsInFrame != intendedCS.getNumComponents()) { - if (startOfFrame.componentsInFrame < 4 && (csType == JPEGColorSpace.CMYK || csType == JPEGColorSpace.YCCK)) { + if (startOfFrame.componentsInFrame() != intendedCS.getNumComponents()) { + if (startOfFrame.componentsInFrame() < 4 && (csType == JPEGColorSpace.CMYK || csType == JPEGColorSpace.YCCK)) { processWarningOccurred(String.format( "Invalid Adobe App14 marker. Indicates YCCK/CMYK data, but SOF%d has %d color components. " + "Ignoring Adobe App14 marker, assuming YCbCr/RGB data.", - startOfFrame.marker & 0xf, startOfFrame.componentsInFrame + startOfFrame.marker & 0xf, startOfFrame.componentsInFrame() )); csType = JPEGColorSpace.YCbCr; @@ -332,12 +353,15 @@ public class JPEGImageReader extends ImageReaderBase { "Embedded ICC color profile is incompatible with image data. " + "Profile indicates %d components, but SOF%d has %d color components. " + "Ignoring ICC profile, assuming source color space %s.", - intendedCS.getNumComponents(), startOfFrame.marker & 0xf, startOfFrame.componentsInFrame, csType + intendedCS.getNumComponents(), startOfFrame.marker & 0xf, startOfFrame.componentsInFrame(), csType )); } } // NOTE: Avoid using CCOp if same color space, as it's more compatible that way else if (intendedCS != image.getColorModel().getColorSpace()) { + if (DEBUG) { + System.err.println("Converting from " + intendedCS + " to " + (image.getColorModel().getColorSpace().isCS_sRGB() ? "sRGB" : image.getColorModel().getColorSpace())); + } convert = new ColorConvertOp(intendedCS, image.getColorModel().getColorSpace(), null); } // Else, pass through with no conversion @@ -346,10 +370,20 @@ public class JPEGImageReader extends ImageReaderBase { ColorSpace cmykCS = ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK); if (cmykCS instanceof ICC_ColorSpace) { + processWarningOccurred( + "No embedded ICC color profile, defaulting to \"generic\" CMYK ICC profile. " + + "Colors may look incorrect." + ); + convert = new ColorConvertOp(cmykCS, image.getColorModel().getColorSpace(), null); } else { // ColorConvertOp using non-ICC CS is deadly slow, fall back to fast conversion instead + processWarningOccurred( + "No embedded ICC color profile, will convert using inaccurate CMYK to RGB conversion. " + + "Colors may look incorrect." + ); + convert = new FastCMYKToRGB(); } } @@ -664,7 +698,7 @@ public class JPEGImageReader extends ImageReaderBase { components[i] = new SOFComponent(id, ((sub & 0xF0) >> 4), (sub & 0xF), qtSel); } - return new SOFSegment(segment.marker(), samplePrecision, lines, samplesPerLine, componentsInFrame, components); + return new SOFSegment(segment.marker(), samplePrecision, lines, samplesPerLine, components); } finally { data.close(); @@ -731,6 +765,10 @@ public class JPEGImageReader extends ImageReaderBase { // ICC v 1.42 (2006) annex B: // APP2 marker (0xFFE2) + 2 byte length + ASCII 'ICC_PROFILE' + 0 (termination) // + 1 byte chunk number + 1 byte chunk count (allows ICC profiles chunked in multiple APP2 segments) + + // TODO: Allow metadata to contain the wrongly indexed profiles, if readable + // NOTE: We ignore any profile with wrong index for reading and image types, just to be on the safe side + List segments = getAppSegments(JPEG.APP2, "ICC_PROFILE"); if (segments.size() == 1) { @@ -741,7 +779,8 @@ public class JPEGImageReader extends ImageReaderBase { int chunkCount = stream.readUnsignedByte(); if (chunkNumber != 1 && chunkCount != 1) { - processWarningOccurred(String.format("Bad number of 'ICC_PROFILE' chunks: %d of %d. Assuming single chunk.", chunkNumber, chunkCount)); + processWarningOccurred(String.format("Unexpected number of 'ICC_PROFILE' chunks: %d of %d. Ignoring ICC profile.", chunkNumber, chunkCount)); + return null; } return readICCProfileSafe(stream); @@ -752,13 +791,15 @@ public class JPEGImageReader extends ImageReaderBase { int chunkNumber = stream.readUnsignedByte(); int chunkCount = stream.readUnsignedByte(); + // TODO: Most of the time the ICC profiles are readable and should be obtainable from metadata... boolean badICC = false; if (chunkCount != segments.size()) { // Some weird JPEGs use 0-based indexes... count == 0 and all numbers == 0. // Others use count == 1, and all numbers == 1. // Handle these by issuing warning + processWarningOccurred(String.format("Bad 'ICC_PROFILE' chunk count: %d. Ignoring ICC profile.", chunkCount)); badICC = true; - processWarningOccurred(String.format("Unexpected 'ICC_PROFILE' chunk count: %d. Ignoring count, assuming %d chunks in sequence.", chunkCount, segments.size())); + return null; } if (!badICC && chunkNumber < 1) { @@ -920,6 +961,51 @@ public class JPEGImageReader extends ImageReaderBase { return thumbnails.get(thumbnailIndex).read(); } + + // Metadata + + @Override + public IIOMetadata getImageMetadata(int imageIndex) throws IOException { + // TODO: Nice try, but no cigar.. getAsTree does not return a "live" view, so any modifications are thrown away + IIOMetadata metadata = delegate.getImageMetadata(imageIndex); + + IIOMetadataNode tree = (IIOMetadataNode) metadata.getAsTree(metadata.getNativeMetadataFormatName()); + Node jpegVariety = tree.getElementsByTagName("JPEGvariety").item(0); + + // TODO: Allow EXIF (as app1EXIF) in the JPEGvariety (sic) node. + // As EXIF is (a subset of) TIFF, (and the EXIF data is a valid TIFF stream) probably use something like: + // http://download.java.net/media/jai-imageio/javadoc/1.1/com/sun/media/imageio/plugins/tiff/package-summary.html#ImageMetadata + /* + from: http://docs.oracle.com/javase/6/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html + + In future versions of the JPEG metadata format, other varieties of JPEG metadata may be supported (e.g. Exif) + by defining other types of nodes which may appear as a child of the JPEGvariety node. + + (Note that an application wishing to interpret Exif metadata given a metadata tree structure in the + javax_imageio_jpeg_image_1.0 format must check for an unknown marker segment with a tag indicating an + APP1 marker and containing data identifying it as an Exif marker segment. Then it may use application-specific + code to interpret the data in the marker segment. If such an application were to encounter a metadata tree + formatted according to a future version of the JPEG metadata format, the Exif marker segment might not be + unknown in that format - it might be structured as a child node of the JPEGvariety node. + + Thus, it is important for an application to specify which version to use by passing the string identifying + the version to the method/constructor used to obtain an IIOMetadata object.) + */ + + IIOMetadataNode app2ICC = new IIOMetadataNode("app2ICC"); + app2ICC.setUserObject(getEmbeddedICCProfile()); + jpegVariety.getFirstChild().appendChild(app2ICC); + +// new XMLSerializer(System.err, System.getProperty("file.encoding")).serialize(tree, false); + + return metadata; + } + + @Override + public IIOMetadata getStreamMetadata() throws IOException { + return delegate.getStreamMetadata(); + } + private static void invertCMYK(final Raster raster) { byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData(); @@ -1223,10 +1309,10 @@ public class JPEGImageReader extends ImageReaderBase { // image = new ResampleOp(reader.getWidth(0) / 4, reader.getHeight(0) / 4, ResampleOp.FILTER_LANCZOS).filter(image, null); -// int maxW = 1280; -// int maxH = 800; - int maxW = 400; - int maxH = 400; + int maxW = 1280; + int maxH = 800; +// int maxW = 400; +// int maxH = 400; if (image.getWidth() > maxW || image.getHeight() > maxH) { // start = System.currentTimeMillis(); float aspect = reader.getAspectRatio(0); diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStream.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStream.java index a03f21fe..23ab8f8b 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStream.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStream.java @@ -49,8 +49,9 @@ import static com.twelvemonkeys.lang.Validate.notNull; */ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl { // TODO: Rewrite JPEGSegment (from metadata) to store stream pos/length, and be able to replay data, and use instead of Segment? - // TODO: Change order of segments, to make sure APP0/JFIF is always before APP14/Adobe? + // TODO: Change order of segments, to make sure APP0/JFIF is always before APP14/Adobe? What about EXIF? // TODO: Insert fake APP0/JFIF if needed by the reader? + // TODO: Sort out ICC_PROFILE issues (duplicate sequence numbers etc)? final private ImageInputStream stream; @@ -155,7 +156,7 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl { return segment; } - private static boolean isAppSegmentWithId(String segmentId, ImageInputStream stream) throws IOException { + private static boolean isAppSegmentWithId(final String segmentId, final ImageInputStream stream) throws IOException { notNull(segmentId, "segmentId"); stream.mark(); @@ -228,7 +229,7 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl { } @Override - public int read(byte[] b, int off, int len) throws IOException { + public int read(final byte[] b, final int off, final int len) throws IOException { bitOffset = 0; // NOTE: There is a bug in the JPEGMetadata constructor (JPEGBuffer.loadBuf() method) that expects read to @@ -270,7 +271,7 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl { final long start; final long length; - Segment(int marker, long realStart, long start, long length) { + Segment(final int marker, final long realStart, final long start, final long length) { this.marker = marker; this.realStart = realStart; this.start = start; diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/SOFSegment.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/SOFSegment.java index 20603bde..526f0c00 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/SOFSegment.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/SOFSegment.java @@ -42,23 +42,25 @@ final class SOFSegment { final int samplePrecision; final int lines; // height final int samplesPerLine; // width - final int componentsInFrame; final SOFComponent[] components; - SOFSegment(int marker, int samplePrecision, int lines, int samplesPerLine, int componentsInFrame, SOFComponent[] components) { + SOFSegment(int marker, int samplePrecision, int lines, int samplesPerLine, SOFComponent[] components) { this.marker = marker; this.samplePrecision = samplePrecision; this.lines = lines; this.samplesPerLine = samplesPerLine; - this.componentsInFrame = componentsInFrame; this.components = components; } + final int componentsInFrame() { + return components.length; + } + @Override public String toString() { return String.format( "SOF%d[%04x, precision: %d, lines: %d, samples/line: %d, components: %s]", - marker & 0xf, marker, samplePrecision, lines, samplesPerLine, Arrays.toString(components) + marker & 0xff - 0xc0, marker, samplePrecision, lines, samplesPerLine, Arrays.toString(components) ); } } diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEG.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEG.java index 4d611ad6..72df9263 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEG.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEG.java @@ -82,6 +82,10 @@ public interface JPEG { int SOF14 = 0xFFCE; int SOF15 = 0xFFCF; + // JPEG-LS markers + int SOF55 = 0xFFF7; // NOTE: Equal to a normal SOF segment + int LSE = 0xFFF8; // JPEG-LS Preset Parameter marker + // TODO: Known/Important APPn marker identifiers // "JFIF" APP0 // "JFXX" APP0 From a2effd7ba023a1082b8cc1e497bc75f2216dee00 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Wed, 5 Jun 2013 10:55:18 +0200 Subject: [PATCH 48/58] TMI-META: Added license. --- imageio/imageio-metadata/license.txt | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 imageio/imageio-metadata/license.txt diff --git a/imageio/imageio-metadata/license.txt b/imageio/imageio-metadata/license.txt new file mode 100644 index 00000000..f1d861fb --- /dev/null +++ b/imageio/imageio-metadata/license.txt @@ -0,0 +1,25 @@ +Copyright (c) 2012, Harald Kuhr +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name "TwelveMonkeys" nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 33ffc14e3f0b528daefb6dc35047b034a4a6c8ed Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Wed, 5 Jun 2013 10:56:12 +0200 Subject: [PATCH 49/58] TMS-XXX: Code style. --- .../twelvemonkeys/servlet/cache/HTTPCache.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/cache/HTTPCache.java b/servlet/src/main/java/com/twelvemonkeys/servlet/cache/HTTPCache.java index 16ac0894..1bae14e4 100755 --- a/servlet/src/main/java/com/twelvemonkeys/servlet/cache/HTTPCache.java +++ b/servlet/src/main/java/com/twelvemonkeys/servlet/cache/HTTPCache.java @@ -1089,13 +1089,13 @@ public class HTTPCache { // TODO: Extract and make public? final static class SizedLRUMap extends LRUHashMap { - int mSize; - int mMaxSize; + int currentSize; + int maxSize; public SizedLRUMap(int pMaxSize) { //super(true); - super(); // Note: super.mMaxSize doesn't count... - mMaxSize = pMaxSize; + super(); // Note: super.maxSize doesn't count... + maxSize = pMaxSize; } @@ -1113,11 +1113,11 @@ public class HTTPCache { @Override public V put(K pKey, V pValue) { - mSize += sizeOf(pValue); + currentSize += sizeOf(pValue); V old = super.put(pKey, pValue); if (old != null) { - mSize -= sizeOf(old); + currentSize -= sizeOf(old); } return old; } @@ -1126,14 +1126,14 @@ public class HTTPCache { public V remove(Object pKey) { V old = super.remove(pKey); if (old != null) { - mSize -= sizeOf(old); + currentSize -= sizeOf(old); } return old; } @Override protected boolean removeEldestEntry(Map.Entry pEldest) { - if (mMaxSize <= mSize) { // NOTE: mMaxSize here is mem size + if (maxSize <= currentSize) { // NOTE: maxSize here is mem size removeLRU(); } return false; @@ -1141,7 +1141,7 @@ public class HTTPCache { @Override public void removeLRU() { - while (mMaxSize <= mSize) { // NOTE: mMaxSize here is mem size + while (maxSize <= currentSize) { // NOTE: maxSize here is mem size super.removeLRU(); } } From f5b5e818c50c2912eccdaa7cb28bdac1d5268498 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Thu, 6 Jun 2013 16:57:35 +0200 Subject: [PATCH 50/58] TMC-LANG: Made test case OS agnostic (now builds on Windows too). Thanks Omri Spector! --- .../convert/DefaultConverterTestCase.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/DefaultConverterTestCase.java b/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/DefaultConverterTestCase.java index 65c879bd..bbced569 100755 --- a/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/DefaultConverterTestCase.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/DefaultConverterTestCase.java @@ -1,5 +1,7 @@ package com.twelvemonkeys.util.convert; +import com.twelvemonkeys.lang.Validate; + import java.io.File; import java.net.URI; @@ -47,7 +49,7 @@ public class DefaultConverterTestCase extends PropertyConverterAbstractTestCase // Object array test new Conversion("foo, bar", new FooBar[] {new FooBar("foo"), new FooBar("bar")}), - new Conversion("/temp, /usr/local/bin", new File[] {new File("/temp"), new File("/usr/local/bin")}), + new Conversion("/temp, /usr/local/bin".replace('/', File.separatorChar), new File[] {new File("/temp"), new File("/usr/local/bin")}), new Conversion("file:/temp, http://java.net/", new URI[] {URI.create("file:/temp"), URI.create("http://java.net/")}), // TODO: More tests @@ -57,13 +59,12 @@ public class DefaultConverterTestCase extends PropertyConverterAbstractTestCase // TODO: Test boolean -> Boolean conversion public static class FooBar { - private final String mBar; + private final String bar; public FooBar(String pFoo) { - if (pFoo == null) { - throw new IllegalArgumentException("pFoo == null"); - } - mBar = reverse(pFoo); + Validate.notNull(pFoo, "foo"); + + bar = reverse(pFoo); } private String reverse(String pFoo) { @@ -77,16 +78,15 @@ public class DefaultConverterTestCase extends PropertyConverterAbstractTestCase } public String toString() { - return reverse(mBar); + return reverse(bar); } public boolean equals(Object obj) { - return obj == this || (obj instanceof FooBar && ((FooBar) obj).mBar.equals(mBar)); + return obj == this || (obj != null && obj.getClass() == getClass() && ((FooBar) obj).bar.equals(bar)); } public int hashCode() { - return 7 * mBar.hashCode(); + return 7 * bar.hashCode(); } } - } From cdce0aebff4460edf00f7791e80ac8c9cb835d44 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Thu, 6 Jun 2013 19:51:07 +0200 Subject: [PATCH 51/58] TMC-LANG: Code clean-up, added tests and fixed issues. --- .../java/com/twelvemonkeys/lang/BeanUtil.java | 59 +++++++-------- .../twelvemonkeys/util/convert/Converter.java | 24 +++---- .../util/convert/DefaultConverter.java | 65 ++++++++++++----- .../twelvemonkeys/lang/BeanUtilTestCase.java | 71 ++++++++++--------- .../convert/DefaultConverterTestCase.java | 60 +++++++++++++++- 5 files changed, 182 insertions(+), 97 deletions(-) diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/lang/BeanUtil.java b/common/common-lang/src/main/java/com/twelvemonkeys/lang/BeanUtil.java index 0c09176d..7eae35dd 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/lang/BeanUtil.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/lang/BeanUtil.java @@ -41,8 +41,7 @@ import java.util.Arrays; /** * A utility class with some useful bean-related functions. *

- * NOTE: This class is not considered part of the public API and may be - * changed without notice + * NOTE: This class is not considered part of the public API and may be changed without notice * * @author Harald Kuhr * @author last modified by $Author: haku $ @@ -60,10 +59,10 @@ public final class BeanUtil { * Now supports getting values from properties of properties * (recursive). * - * @param pObject The object to get the property from + * @param pObject The object to get the property from * @param pProperty The name of the property * - * @return A string containing the value of the given property, or null + * @return A string containing the value of the given property, or {@code null} * if it can not be found. * @todo Remove System.err's... Create new Exception? Hmm.. */ @@ -77,7 +76,7 @@ public final class BeanUtil { return null; } - Class objClass = pObject.getClass(); + Class objClass = pObject.getClass(); Object result = pObject; @@ -154,9 +153,8 @@ public final class BeanUtil { catch (NoSuchMethodException e) { System.err.print("No method named \"" + methodName + "()\""); // The array might be of size 0... - if (paramClass != null && paramClass.length > 0) { - System.err.print(" with the parameter " - + paramClass[0].getName()); + if (paramClass.length > 0 && paramClass[0] != null) { + System.err.print(" with the parameter " + paramClass[0].getName()); } System.err.println(" in class " + objClass.getName() + "!"); @@ -177,8 +175,7 @@ public final class BeanUtil { result = method.invoke(result, param); } catch (InvocationTargetException e) { - System.err.println("property=" + pProperty + " & result=" - + result + " & param=" + Arrays.toString(param)); + System.err.println("property=" + pProperty + " & result=" + result + " & param=" + Arrays.toString(param)); e.getTargetException().printStackTrace(); e.printStackTrace(); return null; @@ -188,8 +185,7 @@ public final class BeanUtil { return null; } catch (NullPointerException e) { - System.err.println(objClass.getName() + "." + method.getName() - + "(" + ((paramClass != null && paramClass.length > 0) ? paramClass[0].getName() : "") + ")"); + System.err.println(objClass.getName() + "." + method.getName() + "(" + ((paramClass.length > 0 && paramClass[0] != null) ? paramClass[0].getName() : "") + ")"); e.printStackTrace(); return null; } @@ -221,10 +217,8 @@ public final class BeanUtil { * @throws IllegalAccessException if the caller class has no access to the * write method */ - public static void setPropertyValue(Object pObject, String pProperty, - Object pValue) - throws NoSuchMethodException, InvocationTargetException, - IllegalAccessException { + public static void setPropertyValue(Object pObject, String pProperty, Object pValue) + throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { // // TODO: Support set(Object, Object)/put(Object, Object) methods @@ -255,7 +249,8 @@ public final class BeanUtil { method.invoke(obj, params); } - private static Method getMethodMayModifyParams(Object pObject, String pName, Class[] pParams, Object[] pValues) throws NoSuchMethodException { + private static Method getMethodMayModifyParams(Object pObject, String pName, Class[] pParams, Object[] pValues) + throws NoSuchMethodException { // NOTE: This method assumes pParams.length == 1 && pValues.length == 1 Method method = null; @@ -307,10 +302,8 @@ public final class BeanUtil { if (method == null) { Method[] methods = pObject.getClass().getMethods(); for (Method candidate : methods) { - if (Modifier.isPublic(candidate.getModifiers()) - && candidate.getName().equals(pName) - && candidate.getReturnType() == Void.TYPE - && candidate.getParameterTypes().length == 1) { + if (Modifier.isPublic(candidate.getModifiers()) && candidate.getName().equals(pName) + && candidate.getReturnType() == Void.TYPE && candidate.getParameterTypes().length == 1) { // NOTE: Assumes paramTypes.length == 1 Class type = candidate.getParameterTypes()[0]; @@ -337,7 +330,7 @@ public final class BeanUtil { return method; } - private static Object convertValueToType(Object pValue, Class pType) throws ConversionException { + private static Object convertValueToType(Object pValue, Class pType) throws ConversionException { if (pType.isPrimitive()) { if (pType == Boolean.TYPE && pValue instanceof Boolean) { return pValue; @@ -395,7 +388,7 @@ public final class BeanUtil { * @throws InvocationTargetException if the constructor failed */ // TODO: Move to ReflectUtil - public static Object createInstance(Class pClass, Object pParam) + public static T createInstance(Class pClass, Object pParam) throws InvocationTargetException { return createInstance(pClass, new Object[] {pParam}); } @@ -414,9 +407,9 @@ public final class BeanUtil { * @throws InvocationTargetException if the constructor failed */ // TODO: Move to ReflectUtil - public static Object createInstance(Class pClass, Object... pParams) + public static T createInstance(Class pClass, Object... pParams) throws InvocationTargetException { - Object value; + T value; try { // Create param and argument arrays @@ -429,8 +422,7 @@ public final class BeanUtil { } // Get constructor - //Constructor constructor = pClass.getDeclaredConstructor(paramTypes); - Constructor constructor = pClass.getConstructor(paramTypes); + Constructor constructor = pClass.getConstructor(paramTypes); // Invoke and create instance value = constructor.newInstance(pParams); @@ -468,12 +460,11 @@ public final class BeanUtil { * If the return type of the method is void, null is returned. * If the method could not be invoked for any reason, null is returned. * - * @throws InvocationTargetException if the invocaton failed + * @throws InvocationTargetException if the invocation failed */ // TODO: Move to ReflectUtil // TODO: Rename to invokeStatic? - public static Object invokeStaticMethod(Class pClass, String pMethod, - Object pParam) + public static Object invokeStaticMethod(Class pClass, String pMethod, Object pParam) throws InvocationTargetException { return invokeStaticMethod(pClass, pMethod, new Object[] {pParam}); @@ -492,12 +483,11 @@ public final class BeanUtil { * If the return type of the method is void, null is returned. * If the method could not be invoked for any reason, null is returned. * - * @throws InvocationTargetException if the invocaton failed + * @throws InvocationTargetException if the invocation failed */ // TODO: Move to ReflectUtil // TODO: Rename to invokeStatic? - public static Object invokeStaticMethod(Class pClass, String pMethod, - Object[] pParams) + public static Object invokeStaticMethod(Class pClass, String pMethod, Object... pParams) throws InvocationTargetException { Object value = null; @@ -518,8 +508,7 @@ public final class BeanUtil { Method method = pClass.getMethod(pMethod, paramTypes); // Invoke public static method - if (Modifier.isPublic(method.getModifiers()) - && Modifier.isStatic(method.getModifiers())) { + if (Modifier.isPublic(method.getModifiers()) && Modifier.isStatic(method.getModifiers())) { value = method.invoke(null, pParams); } diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/Converter.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/Converter.java index aa4015ef..106b6164 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/Converter.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/Converter.java @@ -36,7 +36,7 @@ import java.util.Map; /** * The converter (singleton). Converts strings to objects and back. - * This is the entrypoint to the converter framework. + * This is the entry point to the converter framework. *

* By default, converters for {@link com.twelvemonkeys.util.Time}, {@link Date} * and {@link Object} @@ -53,17 +53,17 @@ import java.util.Map; */ // TODO: Get rid of singleton stuff // Can probably be a pure static class, but is that a good idea? -// Maybe have BeanUtil act as a "proxy", and hide this class alltogheter? +// Maybe have BeanUtil act as a "proxy", and hide this class all together? // TODO: ServiceRegistry for registering 3rd party converters // TODO: URI scheme, for implicit typing? Is that a good idea? // TODO: Array converters? public abstract class Converter implements PropertyConverter { /** Our singleton instance */ - protected static Converter sInstance = new ConverterImpl(); // Thread safe & EASY + protected static final Converter sInstance = new ConverterImpl(); // Thread safe & EASY - /** The conveters Map */ - protected Map converters = new Hashtable(); + /** The converters Map */ + protected final Map converters = new Hashtable(); // Register our predefined converters static { @@ -115,20 +115,21 @@ public abstract class Converter implements PropertyConverter { * * @see #unregisterConverter(Class) */ - public static void registerConverter(Class pType, PropertyConverter pConverter) { + public static void registerConverter(final Class pType, final PropertyConverter pConverter) { getInstance().converters.put(pType, pConverter); } /** - * Unregisters a converter for a given type. That is, making it unavailable + * Un-registers a converter for a given type. That is, making it unavailable * for the converter framework, and making it (potentially) available for - * garbabe collection. + * garbage collection. * * @param pType the (super) type to remove converter for * * @see #registerConverter(Class,PropertyConverter) */ - public static void unregisterConverter(Class pType) { + @SuppressWarnings("UnusedDeclaration") + public static void unregisterConverter(final Class pType) { getInstance().converters.remove(pType); } @@ -143,8 +144,7 @@ public abstract class Converter implements PropertyConverter { * @throws ConversionException if the string cannot be converted for any * reason. */ - public Object toObject(String pString, Class pType) - throws ConversionException { + public Object toObject(final String pString, final Class pType) throws ConversionException { return toObject(pString, pType, null); } @@ -174,7 +174,7 @@ public abstract class Converter implements PropertyConverter { * @throws ConversionException if the object cannot be converted to a * string for any reason. */ - public String toString(Object pObject) throws ConversionException { + public String toString(final Object pObject) throws ConversionException { return toString(pObject, null); } diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/DefaultConverter.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/DefaultConverter.java index 81e4d589..59236d7c 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/DefaultConverter.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/convert/DefaultConverter.java @@ -67,9 +67,9 @@ public final class DefaultConverter implements PropertyConverter { * * @throws ConversionException if the type is null, or if the string cannot * be converted into the given type, using a string constructor or static - * {@code valueof} method. + * {@code valueOf} method. */ - public Object toObject(String pString, final Class pType, String pFormat) throws ConversionException { + public Object toObject(final String pString, final Class pType, final String pFormat) throws ConversionException { if (pString == null) { return null; } @@ -87,13 +87,7 @@ public final class DefaultConverter implements PropertyConverter { // But what about generic type?! It's erased... // Primitive -> wrapper - Class type; - if (pType == Boolean.TYPE) { - type = Boolean.class; - } - else { - type = pType; - } + Class type = unBoxType(pType); try { // Try to create instance from (String) @@ -101,13 +95,15 @@ public final class DefaultConverter implements PropertyConverter { if (value == null) { // createInstance failed for some reason - - // Try to invoke the static method valueof(String) + // Try to invoke the static method valueOf(String) value = BeanUtil.invokeStaticMethod(type, "valueOf", pString); if (value == null) { // If the value is still null, well, then I cannot help... - throw new ConversionException("Could not convert String to " + pType.getName() + ": No constructor " + type.getName() + "(String) or static " + type.getName() + ".valueof(String) method found!"); + throw new ConversionException(String.format( + "Could not convert String to %1$s: No constructor %1$s(String) or static %1$s.valueOf(String) method found!", + type.getName() + )); } } @@ -116,12 +112,15 @@ public final class DefaultConverter implements PropertyConverter { catch (InvocationTargetException ite) { throw new ConversionException(ite.getTargetException()); } + catch (ConversionException ce) { + throw ce; + } catch (RuntimeException rte) { throw new ConversionException(rte); } } - private Object toArray(String pString, Class pType, String pFormat) { + private Object toArray(final String pString, final Class pType, final String pFormat) { String[] strings = StringUtil.toStringArray(pString, pFormat != null ? pFormat : StringUtil.DELIMITER_STRING); Class type = pType.getComponentType(); if (type == String.class) { @@ -152,10 +151,9 @@ public final class DefaultConverter implements PropertyConverter { * @param pObject the object to convert. * @param pFormat ignored. * - * @return the string representation of the object, or {@code null} if - * {@code pObject == null} + * @return the string representation of the object, or {@code null} if {@code pObject == null} */ - public String toString(Object pObject, String pFormat) + public String toString(final Object pObject, final String pFormat) throws ConversionException { try { @@ -170,7 +168,7 @@ public final class DefaultConverter implements PropertyConverter { return pFormat == null ? StringUtil.toCSVString(pArray) : StringUtil.toCSVString(pArray, pFormat); } - private Object[] toObjectArray(Object pObject) { + private Object[] toObjectArray(final Object pObject) { // TODO: Extract util method for wrapping/unwrapping native arrays? Object[] array; Class componentType = pObject.getClass().getComponentType(); @@ -232,4 +230,37 @@ public final class DefaultConverter implements PropertyConverter { } return array; } + + private Class unBoxType(final Class pType) { + if (pType.isPrimitive()) { + if (pType == boolean.class) { + return Boolean.class; + } + if (pType == byte.class) { + return Byte.class; + } + if (pType == char.class) { + return Character.class; + } + if (pType == short.class) { + return Short.class; + } + if (pType == int.class) { + return Integer.class; + } + if (pType == float.class) { + return Float.class; + } + if (pType == long.class) { + return Long.class; + } + if (pType == double.class) { + return Double.class; + } + + throw new IllegalArgumentException("Unknown type: " + pType); + } + + return pType; + } } diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/lang/BeanUtilTestCase.java b/common/common-lang/src/test/java/com/twelvemonkeys/lang/BeanUtilTestCase.java index 2b567ed6..59e69b97 100755 --- a/common/common-lang/src/test/java/com/twelvemonkeys/lang/BeanUtilTestCase.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/lang/BeanUtilTestCase.java @@ -108,13 +108,13 @@ public class BeanUtilTestCase extends TestCase { assertEquals(0.3, bean.getDoubleValue()); } - public void testConfigureAmbigious1() { + public void testConfigureAmbiguous1() { TestBean bean = new TestBean(); Map map = new HashMap(); String value = "one"; - map.put("ambigious", value); + map.put("ambiguous", value); try { BeanUtil.configure(bean, map); @@ -123,20 +123,20 @@ public class BeanUtilTestCase extends TestCase { fail(e.getMessage()); } - assertNotNull(bean.getAmbigious()); - assertEquals("String converted rather than invoking setAmbigiouos(String), ordering not predictable", - "one", bean.getAmbigious()); - assertSame("String converted rather than invoking setAmbigiouos(String), ordering not predictable", - value, bean.getAmbigious()); + assertNotNull(bean.getAmbiguous()); + assertEquals("String converted rather than invoking setAmbiguous(String), ordering not predictable", + "one", bean.getAmbiguous()); + assertSame("String converted rather than invoking setAmbiguous(String), ordering not predictable", + value, bean.getAmbiguous()); } - public void testConfigureAmbigious2() { + public void testConfigureAmbiguous2() { TestBean bean = new TestBean(); Map map = new HashMap(); Integer value = 2; - map.put("ambigious", value); + map.put("ambiguous", value); try { BeanUtil.configure(bean, map); @@ -145,20 +145,20 @@ public class BeanUtilTestCase extends TestCase { fail(e.getMessage()); } - assertNotNull(bean.getAmbigious()); - assertEquals("Integer converted rather than invoking setAmbigiouos(Integer), ordering not predictable", - 2, bean.getAmbigious()); - assertSame("Integer converted rather than invoking setAmbigiouos(Integer), ordering not predictable", - value, bean.getAmbigious()); + assertNotNull(bean.getAmbiguous()); + assertEquals("Integer converted rather than invoking setAmbiguous(Integer), ordering not predictable", + 2, bean.getAmbiguous()); + assertSame("Integer converted rather than invoking setAmbiguous(Integer), ordering not predictable", + value, bean.getAmbiguous()); } - public void testConfigureAmbigious3() { + public void testConfigureAmbiguous3() { TestBean bean = new TestBean(); Map map = new HashMap(); Double value = .3; - map.put("ambigious", value); + map.put("ambiguous", value); try { BeanUtil.configure(bean, map); @@ -167,11 +167,11 @@ public class BeanUtilTestCase extends TestCase { fail(e.getMessage()); } - assertNotNull(bean.getAmbigious()); - assertEquals("Object converted rather than invoking setAmbigious(Object), ordering not predictable", - value.getClass(), bean.getAmbigious().getClass()); - assertSame("Object converted rather than invoking setAmbigious(Object), ordering not predictable", - value, bean.getAmbigious()); + assertNotNull(bean.getAmbiguous()); + assertEquals("Object converted rather than invoking setAmbiguous(Object), ordering not predictable", + value.getClass(), bean.getAmbiguous().getClass()); + assertSame("Object converted rather than invoking setAmbiguous(Object), ordering not predictable", + value, bean.getAmbiguous()); } static class TestBean { @@ -179,7 +179,7 @@ public class BeanUtilTestCase extends TestCase { private int intVal; private Double doubleVal; - private Object ambigious; + private Object ambiguous; public Double getDoubleValue() { return doubleVal; @@ -193,36 +193,43 @@ public class BeanUtilTestCase extends TestCase { return stringVal; } + @SuppressWarnings("UnusedDeclaration") public void setStringValue(String pString) { stringVal = pString; } + @SuppressWarnings("UnusedDeclaration") public void setIntValue(int pInt) { intVal = pInt; } + @SuppressWarnings("UnusedDeclaration") public void setDoubleValue(Double pDouble) { doubleVal = pDouble; } - public void setAmbigious(String pString) { - ambigious = pString; + @SuppressWarnings("UnusedDeclaration") + public void setAmbiguous(String pString) { + ambiguous = pString; } - public void setAmbigious(Object pObject) { - ambigious = pObject; + @SuppressWarnings("UnusedDeclaration") + public void setAmbiguous(Object pObject) { + ambiguous = pObject; } - public void setAmbigious(Integer pInteger) { - ambigious = pInteger; + @SuppressWarnings("UnusedDeclaration") + public void setAmbiguous(Integer pInteger) { + ambiguous = pInteger; } - public void setAmbigious(int pInt) { - ambigious = (long) pInt; // Just to differentiate... + @SuppressWarnings("UnusedDeclaration") + public void setAmbiguous(int pInt) { + ambiguous = (long) pInt; // Just to differentiate... } - public Object getAmbigious() { - return ambigious; + public Object getAmbiguous() { + return ambiguous; } } } diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/DefaultConverterTestCase.java b/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/DefaultConverterTestCase.java index bbced569..6f1f19e7 100755 --- a/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/DefaultConverterTestCase.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/util/convert/DefaultConverterTestCase.java @@ -1,10 +1,14 @@ package com.twelvemonkeys.util.convert; import com.twelvemonkeys.lang.Validate; +import org.junit.Ignore; +import org.junit.Test; import java.io.File; import java.net.URI; +import static org.junit.Assert.*; + /** * DefaultConverterTestCase *

@@ -56,7 +60,61 @@ public class DefaultConverterTestCase extends PropertyConverterAbstractTestCase }; } - // TODO: Test boolean -> Boolean conversion + @Test + public void testConvertBooleanPrimitive() { + PropertyConverter converter = makePropertyConverter(); + assertTrue((Boolean) converter.toObject("true", boolean.class, null)); + assertFalse((Boolean) converter.toObject("FalsE", Boolean.TYPE, null)); + } + + @Test + public void testConvertShortPrimitive() { + PropertyConverter converter = makePropertyConverter(); + assertEquals(1, (short) (Short) converter.toObject("1", short.class, null)); + assertEquals(-2, (short) (Short) converter.toObject("-2", Short.TYPE, null)); + } + @Test + public void testConvertIntPrimitive() { + PropertyConverter converter = makePropertyConverter(); + assertEquals(1, (int) (Integer) converter.toObject("1", int.class, null)); + assertEquals(-2, (int) (Integer) converter.toObject("-2", Integer.TYPE, null)); + } + + @Test + public void testConvertLongPrimitive() { + PropertyConverter converter = makePropertyConverter(); + assertEquals(Long.MAX_VALUE, (long) (Long) converter.toObject("9223372036854775807", long.class, null)); + assertEquals(-2, (long) (Long) converter.toObject("-2", Long.TYPE, null)); + } + + @Test + public void testConvertBytePrimitive() { + PropertyConverter converter = makePropertyConverter(); + assertEquals(1, (byte) (Byte) converter.toObject("1", byte.class, null)); + assertEquals(-2, (byte) (Byte) converter.toObject("-2", Byte.TYPE, null)); + } + + @Test + public void testConvertFloatPrimitive() { + PropertyConverter converter = makePropertyConverter(); + assertEquals(1f, (Float) converter.toObject("1.0", float.class, null), 0); + assertEquals(-2.3456f, (Float) converter.toObject("-2.3456", Float.TYPE, null), 0); + } + + @Test + public void testConvertDoublePrimitive() { + PropertyConverter converter = makePropertyConverter(); + assertEquals(1d, (Double) converter.toObject("1.0", double.class, null), 0); + assertEquals(-2.3456, (Double) converter.toObject("-2.3456", Double.TYPE, null), 0); + } + + @Ignore("Known issue. Why would anyone do something like this?") + @Test + public void testConvertCharPrimitive() { + PropertyConverter converter = makePropertyConverter(); + assertEquals('A', (char) (Character) converter.toObject("A", char.class, null)); + assertEquals('Z', (char) (Character) converter.toObject("Z", Character.TYPE, null)); + } public static class FooBar { private final String bar; From ff3fbc8bd23d44d250675afa18f566c51df64217 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Wed, 12 Jun 2013 21:54:28 +0200 Subject: [PATCH 52/58] TMI-TIFF: Getting close to full baseline support! - Added Modified Huffman decoding (needs a proper test image) - Improved predictor support (16/32 bpp) - Fixed handling of bogus RowsPerStrip --- .../plugins/tiff/CCITTFaxDecoderStream.java | 452 ++++++++++++++++++ .../imageio/plugins/tiff/G31DDecoder.java | 47 -- .../imageio/plugins/tiff/TIFFBaseline.java | 2 +- .../imageio/plugins/tiff/TIFFCustom.java | 2 +- .../imageio/plugins/tiff/TIFFImageReader.java | 145 +++--- .../resources/tiff-image-metadata-sun.dtd | 137 ++++++ .../resources/tiff-stream-metadata-sun.dtd | 9 + 7 files changed, 670 insertions(+), 124 deletions(-) create mode 100644 imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStream.java delete mode 100644 imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/G31DDecoder.java create mode 100644 imageio/imageio-tiff/src/main/resources/tiff-image-metadata-sun.dtd create mode 100644 imageio/imageio-tiff/src/main/resources/tiff-stream-metadata-sun.dtd diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStream.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStream.java new file mode 100644 index 00000000..cdb895f9 --- /dev/null +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStream.java @@ -0,0 +1,452 @@ +/* + * Copyright (c) 2012, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.tiff; + +import com.twelvemonkeys.lang.Validate; + +import java.io.EOFException; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * CCITT Modified Huffman RLE, Group 3 (T4) and Group 4 (T6) fax compression. + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: CCITTFaxDecoderStream.java,v 1.0 23.05.12 15:55 haraldk Exp$ + */ +final class CCITTFaxDecoderStream extends FilterInputStream { + // See TIFF 6.0 Specification, Section 10: "Modified Huffman Compression", page 43. + + private final int columns; + private final byte[] decodedRow; + + private int decodedLength; + private int decodedPos; + + private int bitBuffer; + private int bitBufferLength; + + // Need to take fill order into account (?) (use flip table?) + private final int fillOrder; + private final int type; + + private final int[] changes; + private int changesCount; + + private static final int EOL_CODE = 0x01; // 12 bit + + public CCITTFaxDecoderStream(final InputStream stream, final int columns, final int type, final int fillOrder) { + super(Validate.notNull(stream, "stream")); + + this.columns = Validate.isTrue(columns > 0, columns, "width must be greater than 0"); + // We know this is only used for b/w (1 bit) + this.decodedRow = new byte[(columns + 7) / 8]; + this.type = Validate.isTrue(type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, type, "Only CCITT Modified Huffman RLE compression (2) supported: %s"); // TODO: Implement group 3 and 4 + this.fillOrder = Validate.isTrue(fillOrder == 1, fillOrder, "Only fill order 1 supported: %s"); // TODO: Implement fillOrder == 2 + + this.changes = new int[columns]; + } + + // IDEA: Would it be faster to keep all bit combos of each length (>=2) that is NOT a code, to find bit length, then look up value in table? + // -- If white run, start at 4 bits to determine length, if black, start at 2 bits + + private void fetch() throws IOException { + if (decodedPos >= decodedLength) { + decodedLength = 0; + try { + decodeRow(); + } + catch (EOFException e) { + // TODO: Rewrite to avoid throw/catch for normal flow... + if (decodedLength != 0) { + throw e; + } + + // ..otherwise, just client code trying to read past the end of stream + decodedLength = -1; + } + + decodedPos = 0; + } + } + + private void decodeRow() throws IOException { + resetBuffer(); + + boolean literalRun = true; + + /* + if (type == TIFFExtension.COMPRESSION_CCITT_T4) { + int eol = readBits(12); + System.err.println("eol: " + eol); + while (eol != EOL_CODE) { + eol = readBits(1); + System.err.println("eol: " + eol); +// throw new IOException("Missing EOL"); + } + + literalRun = readBits(1) == 1; + } + + System.err.println("literalRun: " + literalRun); + */ + int index = 0; + + if (literalRun) { + changesCount = 0; + boolean white = true; + + do { + int completeRun = 0; + + int run; + do { + if (white) { + run = decodeRun(WHITE_CODES, WHITE_RUN_LENGTHS, 4); + } + else { + run = decodeRun(BLACK_CODES, BLACK_RUN_LENGTHS, 2); + } + + completeRun += run; + } + while (run >= 64); // Additional makeup codes are packed into both b/w codes, terminating codes are < 64 bytes + + changes[changesCount++] = index + completeRun; + +// System.err.printf("%s run: %d\n", white ? "white" : "black", run); + + // TODO: Optimize with lookup for 0-7 bits? + // Fill bits to byte boundary... + while (index % 8 != 0 && completeRun-- > 0) { + decodedRow[index++ / 8] |= (white ? 1 << 8 - (index % 8) : 0); + } + + // ...then fill complete bytes to either 0xff or 0x00... + if (index % 8 == 0) { + final byte value = (byte) (white ? 0xff : 0x00); + + while (completeRun > 7) { + decodedRow[index / 8] = value; + completeRun -= 8; + index += 8; + } + } + + // ...finally fill any remaining bits + while (completeRun-- > 0) { + decodedRow[index++ / 8] |= (white ? 1 << 8 - (index % 8) : 0); + } + + // Flip color for next run + white = !white; + } + while (index < columns); + } + else { + // non-literal run + } + + if (type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE && index != columns) { + throw new IOException("Sum of run-lengths does not equal scan line width: " + index + " > " + columns); + } + + decodedLength = (index / 8) + 1; + } + + private int decodeRun(short[][] codes, short[][] runLengths, int minCodeSize) throws IOException { + // TODO: Optimize... + // Looping and comparing is the most straight-forward, but probably not the most effective way... + int code = readBits(minCodeSize); + + for (int bits = 0; bits < codes.length; bits++) { + short[] bitCodes = codes[bits]; + + for (int i = 0; i < bitCodes.length; i++) { + if (bitCodes[i] == code) { +// System.err.println("code: " + code); + + // Code found, return matching run length + return runLengths[bits][i]; + } + } + + // No code found, read one more bit and try again + code = fillOrder == 1 ? (code << 1) | readBits(1) : readBits(1) << (bits + minCodeSize) | code; + } + + throw new IOException("Unknown code in Huffman RLE stream"); + } + + private void resetBuffer() { + for (int i = 0; i < decodedRow.length; i++) { + decodedRow[i] = 0; + } + + bitBuffer = 0; + bitBufferLength = 0; + } + + private int readBits(int bitCount) throws IOException { + while (bitBufferLength < bitCount) { + int read = in.read(); + if (read == -1) { + throw new EOFException("Unexpected end of Huffman RLE stream"); + } + + int bits = read & 0xff; + bitBuffer = (bitBuffer << 8) | bits; + bitBufferLength += 8; + } + + // TODO: Take fill order into account + bitBufferLength -= bitCount; + int result = bitBuffer >> bitBufferLength; + bitBuffer &= (1 << bitBufferLength) - 1; + + return result; + } + + @Override + public int read() throws IOException { + if (decodedLength < 0) { + return -1; + } + + if (decodedPos >= decodedLength) { + fetch(); + + if (decodedLength < 0) { + return -1; + } + } + + return decodedRow[decodedPos++] & 0xff; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (decodedLength < 0) { + return -1; + } + + if (decodedPos >= decodedLength) { + fetch(); + + if (decodedLength < 0) { + return -1; + } + } + + int read = Math.min(decodedLength - decodedPos, len); + System.arraycopy(decodedRow, decodedPos, b, off, read); + decodedPos += read; + + return read; + } + + @Override + public long skip(long n) throws IOException { + if (decodedLength < 0) { + return -1; + } + + if (decodedPos >= decodedLength) { + fetch(); + + if (decodedLength < 0) { + return -1; + } + } + + int skipped = (int) Math.min(decodedLength - decodedPos, n); + decodedPos += skipped; + + return skipped; + } + + @Override + public boolean markSupported() { + return false; + } + + @Override + public synchronized void reset() throws IOException { + throw new IOException("mark/reset not supported"); + } + + static final short[][] BLACK_CODES = { + { // 2 bits + 0x2, 0x3, + }, + { // 3 bits + 0x2, 0x3, + }, + { // 4 bits + 0x2, 0x3, + }, + { // 5 bits + 0x3, + }, + { // 6 bits + 0x4, 0x5, + }, + { // 7 bits + 0x4, 0x5, 0x7, + }, + { // 8 bits + 0x4, 0x7, + }, + { // 9 bits + 0x18, + }, + { // 10 bits + 0x17, 0x18, 0x37, 0x8, 0xf, + }, + { // 11 bits + 0x17, 0x18, 0x28, 0x37, 0x67, 0x68, 0x6c, 0x8, 0xc, 0xd, + }, + { // 12 bits + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1c, 0x1d, 0x1e, 0x1f, 0x24, 0x27, 0x28, 0x2b, 0x2c, 0x33, + 0x34, 0x35, 0x37, 0x38, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x64, 0x65, + 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xd2, 0xd3, + 0xd4, 0xd5, 0xd6, 0xd7, 0xda, 0xdb, + }, + { // 13 bits + 0x4a, 0x4b, 0x4c, 0x4d, 0x52, 0x53, 0x54, 0x55, 0x5a, 0x5b, 0x64, 0x65, 0x6c, 0x6d, 0x72, 0x73, + 0x74, 0x75, 0x76, 0x77, + } + }; + static final short[][] BLACK_RUN_LENGTHS = { + { // 2 bits + 3, 2, + }, + { // 3 bits + 1, 4, + }, + { // 4 bits + 6, 5, + }, + { // 5 bits + 7, + }, + { // 6 bits + 9, 8, + }, + { // 7 bits + 10, 11, 12, + }, + { // 8 bits + 13, 14, + }, + { // 9 bits + 15, + }, + { // 10 bits + 16, 17, 0, 18, 64, + }, + { // 11 bits + 24, 25, 23, 22, 19, 20, 21, 1792, 1856, 1920, + }, + { // 12 bits + 1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560, 52, 55, 56, 59, 60, 320, + 384, 448, 53, 54, 50, 51, 44, 45, 46, 47, 57, 58, 61, 256, 48, 49, + 62, 63, 30, 31, 32, 33, 40, 41, 128, 192, 26, 27, 28, 29, 34, 35, + 36, 37, 38, 39, 42, 43, + }, + { // 13 bits + 640, 704, 768, 832, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 512, 576, 896, 960, + 1024, 1088, 1152, 1216, + } + }; + + public static final short[][] WHITE_CODES = { + { // 4 bits + 0x7, 0x8, 0xb, 0xc, 0xe, 0xf, + }, + { // 5 bits + 0x12, 0x13, 0x14, 0x1b, 0x7, 0x8, + }, + { // 6 bits + 0x17, 0x18, 0x2a, 0x2b, 0x3, 0x34, 0x35, 0x7, 0x8, + }, + { // 7 bits + 0x13, 0x17, 0x18, 0x24, 0x27, 0x28, 0x2b, 0x3, 0x37, 0x4, 0x8, 0xc, + }, + { // 8 bits + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1a, 0x1b, 0x2, 0x24, 0x25, 0x28, 0x29, 0x2a, 0x2b, 0x2c, + 0x2d, 0x3, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x4, 0x4a, 0x4b, 0x5, 0x52, 0x53, 0x54, 0x55, + 0x58, 0x59, 0x5a, 0x5b, 0x64, 0x65, 0x67, 0x68, 0xa, 0xb, + }, + { // 9 bits + 0x98, 0x99, 0x9a, 0x9b, 0xcc, 0xcd, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, + }, + { // 10 bits + }, + { // 11 bits + 0x8, 0xc, 0xd, + }, + { // 12 bits + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1c, 0x1d, 0x1e, 0x1f, + } + }; + + public static final short[][] WHITE_RUN_LENGTHS = { + { // 4 bits + 2, 3, 4, 5, 6, 7, + }, + { // 5 bits + 128, 8, 9, 64, 10, 11, + }, + { // 6 bits + 192, 1664, 16, 17, 13, 14, 15, 1, 12, + }, + { // 7 bits + 26, 21, 28, 27, 18, 24, 25, 22, 256, 23, 20, 19, + }, + { // 8 bits + 33, 34, 35, 36, 37, 38, 31, 32, 29, 53, 54, 39, 40, 41, 42, 43, + 44, 30, 61, 62, 63, 0, 320, 384, 45, 59, 60, 46, 49, 50, 51, + 52, 55, 56, 57, 58, 448, 512, 640, 576, 47, 48, + }, + { // 9 bits + 1472, 1536, 1600, 1728, 704, 768, 832, 896, 960, 1024, 1088, 1152, 1216, 1280, 1344, 1408, + }, + { // 10 bits + }, + { // 11 bits + 1792, 1856, 1920, + }, + { // 12 bits + 1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560, + } + }; +} diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/G31DDecoder.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/G31DDecoder.java deleted file mode 100644 index cb02dd9b..00000000 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/G31DDecoder.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2012, Harald Kuhr - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name "TwelveMonkeys" nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.twelvemonkeys.imageio.plugins.tiff; - -import com.twelvemonkeys.io.enc.Decoder; - -import java.io.IOException; -import java.io.InputStream; - -/** - * CCITT Group 3 One-Dimensional (G31D) "No EOLs" Decoder. - * - * @author Harald Kuhr - * @author last modified by $Author: haraldk$ - * @version $Id: G31DDecoder.java,v 1.0 23.05.12 15:55 haraldk Exp$ - */ -final class G31DDecoder implements Decoder { - public int decode(final InputStream stream, final byte[] buffer) throws IOException { - throw new UnsupportedOperationException("Method decode not implemented"); // TODO: Implement - } -} diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFBaseline.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFBaseline.java index 3cc3e5f6..1f76d655 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFBaseline.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFBaseline.java @@ -37,7 +37,7 @@ package com.twelvemonkeys.imageio.plugins.tiff; */ interface TIFFBaseline { int COMPRESSION_NONE = 1; - int COMPRESSION_CCITT_HUFFMAN = 2; + int COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE = 2; int COMPRESSION_PACKBITS = 32773; int PHOTOMETRIC_WHITE_IS_ZERO = 0; diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFCustom.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFCustom.java index 5f452b57..d72b489d 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFCustom.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFCustom.java @@ -49,7 +49,7 @@ interface TIFFCustom { int COMPRESSION_JBIG = 34661; int COMPRESSION_SGILOG = 34676; int COMPRESSION_SGILOG24 = 34677; - int COMPRESSION_JP2000 = 34712; + int COMPRESSION_JPEG2000 = 34712; int PHOTOMETRIC_LOGL = 32844; int PHOTOMETRIC_LOGLUV = 32845; diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java index b6f57a39..87a6efaf 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java @@ -106,16 +106,16 @@ public class TIFFImageReader extends ImageReaderBase { // TODO: Source region (*tests should be failing*) // TODO: TIFFImageWriter + Spi + // TODOs Full BaseLine support: + // TODO: Support ExtraSamples (an array, if multiple extra samples!) + // (0: Unspecified (not alpha), 1: Associated Alpha (pre-multiplied), 2: Unassociated Alpha (non-multiplied) + // TODOs ImageIO advanced functionality: // TODO: Implement readAsRenderedImage to allow tiled renderImage? // For some layouts, we could do reads super-fast with a memory mapped buffer. // TODO: Implement readAsRaster directly // TODO: IIOMetadata (stay close to Sun's TIFF metadata) - - // TODOs Full BaseLine support: - // TODO: Support ExtraSamples (an array, if multiple extra samples!) - // (0: Unspecified (not alpha), 1: Associated Alpha (pre-multiplied), 2: Unassociated Alpha (non-multiplied) - // TODO: Support Compression 2 (CCITT Modified Huffman) for bi-level images + // http://download.java.net/media/jai-imageio/javadoc/1.1/com/sun/media/imageio/plugins/tiff/package-summary.html#ImageMetadata // TODOs Extension support // TODO: Support PlanarConfiguration 2 @@ -127,6 +127,7 @@ public class TIFFImageReader extends ImageReaderBase { // DONE: // Handle SampleFormat (and give up if not == 1) // Support Compression 6 ('Old-style' JPEG) + // Support Compression 2 (CCITT Modified Huffman RLE) for bi-level images final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.tiff.debug")); @@ -175,7 +176,7 @@ public class TIFFImageReader extends ImageReaderBase { return IFDs.directoryCount(); } - private int getValueAsIntWithDefault(final int tag, String tagName, Integer defaultValue) throws IIOException { + private Number getValueAsNumberWithDefault(final int tag, final String tagName, final Number defaultValue) throws IIOException { Entry entry = currentIFD.getEntryById(tag); if (entry == null) { @@ -186,7 +187,19 @@ public class TIFFImageReader extends ImageReaderBase { throw new IIOException("Missing TIFF tag: " + (tagName != null ? tagName : tag)); } - return ((Number) entry.getValue()).intValue(); + return (Number) entry.getValue(); + } + + private long getValueAsLongWithDefault(final int tag, final String tagName, final Long defaultValue) throws IIOException { + return getValueAsNumberWithDefault(tag, tagName, defaultValue).longValue(); + } + + private long getValueAsLongWithDefault(final int tag, final Long defaultValue) throws IIOException { + return getValueAsLongWithDefault(tag, null, defaultValue); + } + + private int getValueAsIntWithDefault(final int tag, final String tagName, final Integer defaultValue) throws IIOException { + return getValueAsNumberWithDefault(tag, tagName, defaultValue).intValue(); } private int getValueAsIntWithDefault(final int tag, Integer defaultValue) throws IIOException { @@ -364,7 +377,6 @@ public class TIFFImageReader extends ImageReaderBase { case TIFFBaseline.PHOTOMETRIC_MASK: // Transparency mask - // TODO: Known extensions throw new IIOException("Unsupported TIFF PhotometricInterpretation value: " + interpretation); default: throw new IIOException("Unknown TIFF PhotometricInterpretation value: " + interpretation); @@ -464,7 +476,9 @@ public class TIFFImageReader extends ImageReaderBase { // NOTE: We handle strips as tiles of tileWidth == width by tileHeight == rowsPerStrip // Strips are top/down, tiles are left/right, top/down int stripTileWidth = width; - int stripTileHeight = getValueAsIntWithDefault(TIFF.TAG_ROWS_PER_STRIP, height); + long rowsPerStrip = getValueAsLongWithDefault(TIFF.TAG_ROWS_PER_STRIP, (1l << 32) - 1); + int stripTileHeight = rowsPerStrip < height ? (int) rowsPerStrip : height; + long[] stripTileOffsets = getValueAsLongArray(TIFF.TAG_TILE_OFFSETS, "TileOffsets", false); long[] stripTileByteCounts; @@ -507,6 +521,13 @@ public class TIFFImageReader extends ImageReaderBase { // LZW case TIFFExtension.COMPRESSION_ZLIB: // 'Adobe-style' Deflate + case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE: + // CCITT modified Huffman + // Additionally, the specification defines these values as part of the TIFF extensions: +// case TIFFExtension.COMPRESSION_CCITT_T4: + // CCITT Group 3 fax encoding +// case TIFFExtension.COMPRESSION_CCITT_T6: + // CCITT Group 4 fax encoding int[] yCbCrSubsampling = null; int yCbCrPos = 1; @@ -585,7 +606,8 @@ public class TIFFImageReader extends ImageReaderBase { ? IIOUtil.createStreamAdapter(imageInput, stripTileByteCounts[i]) : IIOUtil.createStreamAdapter(imageInput); - adapter = createDecoderInputStream(compression, adapter); + adapter = createDecompressorStream(compression, width, adapter); + adapter = createUnpredictorStream(predictor, width, planarConfiguration == 2 ? 1 : raster.getNumBands(), getBitsPerSample(), adapter, imageInput.getByteOrder()); if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR) { adapter = new YCbCrUpsamplerStream(adapter, yCbCrSubsampling, yCbCrPos, colsInTile, yCbCrCoefficients); @@ -598,7 +620,7 @@ public class TIFFImageReader extends ImageReaderBase { } // Read a full strip/tile - readStripTileData(rowRaster, interpretation, predictor, raster, numBands, col, row, colsInTile, rowsInTile, input); + readStripTileData(rowRaster, interpretation, raster, col, row, colsInTile, rowsInTile, input); if (abortRequested()) { break; @@ -917,14 +939,28 @@ public class TIFFImageReader extends ImageReaderBase { break; - case TIFFBaseline.COMPRESSION_CCITT_HUFFMAN: - // CCITT modified Huffman // Additionally, the specification defines these values as part of the TIFF extensions: case TIFFExtension.COMPRESSION_CCITT_T4: // CCITT Group 3 fax encoding case TIFFExtension.COMPRESSION_CCITT_T6: // CCITT Group 4 fax encoding + // Known, but unsupported compression types + case TIFFCustom.COMPRESSION_NEXT: + case TIFFCustom.COMPRESSION_CCITTRLEW: + case TIFFCustom.COMPRESSION_THUNDERSCAN: + case TIFFCustom.COMPRESSION_IT8CTPAD: + case TIFFCustom.COMPRESSION_IT8LW: + case TIFFCustom.COMPRESSION_IT8MP: + case TIFFCustom.COMPRESSION_IT8BL: + case TIFFCustom.COMPRESSION_PIXARFILM: + case TIFFCustom.COMPRESSION_PIXARLOG: + case TIFFCustom.COMPRESSION_DCS: + case TIFFCustom.COMPRESSION_JBIG: // Doable with JBIG plugin? + case TIFFCustom.COMPRESSION_SGILOG: + case TIFFCustom.COMPRESSION_SGILOG24: + case TIFFCustom.COMPRESSION_JPEG2000: // Doable with JPEG2000 plugin? + throw new IIOException("Unsupported TIFF Compression value: " + compression); default: throw new IIOException("Unknown TIFF Compression value: " + compression); @@ -1004,8 +1040,8 @@ public class TIFFImageReader extends ImageReaderBase { return stream.createInputStream(); } - private void readStripTileData(final WritableRaster rowRaster, final int interpretation, final int predictor, - final WritableRaster raster, final int numBands, final int col, final int startRow, + private void readStripTileData(final WritableRaster rowRaster, final int interpretation, + final WritableRaster raster, final int col, final int startRow, final int colsInStrip, final int rowsInStrip, final DataInput input) throws IOException { switch (rowRaster.getTransferType()) { @@ -1020,8 +1056,6 @@ public class TIFFImageReader extends ImageReaderBase { } input.readFully(rowData); - - unPredict(predictor, colsInStrip, 1, numBands, rowData); normalizeBlack(interpretation, rowData); if (colsInStrip == rowRaster.getWidth() && col + colsInStrip <= raster.getWidth()) { @@ -1048,7 +1082,6 @@ public class TIFFImageReader extends ImageReaderBase { rowDataShort[k] = input.readShort(); } - unPredict(predictor, colsInStrip, 1, numBands, rowDataShort); normalizeBlack(interpretation, rowDataShort); if (colsInStrip == rowRaster.getWidth() && col + colsInStrip <= raster.getWidth()) { @@ -1075,7 +1108,6 @@ public class TIFFImageReader extends ImageReaderBase { rowDataInt[k] = input.readInt(); } - unPredict(predictor, colsInStrip, 1, numBands, rowDataInt); normalizeBlack(interpretation, rowDataInt); if (colsInStrip == rowRaster.getWidth() && col + colsInStrip <= raster.getWidth()) { @@ -1118,61 +1150,7 @@ public class TIFFImageReader extends ImageReaderBase { } } - @SuppressWarnings("UnusedParameters") - private void unPredict(final int predictor, int scanLine, int rows, int bands, int[] data) throws IIOException { - // See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64. - switch (predictor) { - case TIFFBaseline.PREDICTOR_NONE: - break; - case TIFFExtension.PREDICTOR_HORIZONTAL_DIFFERENCING: - // TODO: Implement - case TIFFExtension.PREDICTOR_HORIZONTAL_FLOATINGPOINT: - throw new IIOException("Unsupported TIFF Predictor value: " + predictor); - default: - throw new IIOException("Unknown TIFF Predictor value: " + predictor); - } - } - - @SuppressWarnings("UnusedParameters") - private void unPredict(final int predictor, int scanLine, int rows, int bands, short[] data) throws IIOException { - // See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64. - switch (predictor) { - case TIFFBaseline.PREDICTOR_NONE: - break; - case TIFFExtension.PREDICTOR_HORIZONTAL_DIFFERENCING: - // TODO: Implement - case TIFFExtension.PREDICTOR_HORIZONTAL_FLOATINGPOINT: - throw new IIOException("Unsupported TIFF Predictor value: " + predictor); - default: - throw new IIOException("Unknown TIFF Predictor value: " + predictor); - } - } - - private void unPredict(final int predictor, int scanLine, int rows, final int bands, byte[] data) throws IIOException { - // See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64. - switch (predictor) { - case TIFFBaseline.PREDICTOR_NONE: - break; - case TIFFExtension.PREDICTOR_HORIZONTAL_DIFFERENCING: - for (int y = 0; y < rows; y++) { - for (int x = 1; x < scanLine; x++) { - // TODO: For planar data (PlanarConfiguration == 2), treat as bands == 1 - for (int b = 0; b < bands; b++) { - int off = y * scanLine + x; - data[off * bands + b] = (byte) (data[(off - 1) * bands + b] + data[off * bands + b]); - } - } - } - - break; - case TIFFExtension.PREDICTOR_HORIZONTAL_FLOATINGPOINT: - throw new IIOException("Unsupported TIFF Predictor value: " + predictor); - default: - throw new IIOException("Unknown TIFF Predictor value: " + predictor); - } - } - - private InputStream createDecoderInputStream(final int compression, final InputStream stream) throws IOException { + private InputStream createDecompressorStream(final int compression, final int width, final InputStream stream) throws IOException { switch (compression) { case TIFFBaseline.COMPRESSION_NONE: return stream; @@ -1181,14 +1159,31 @@ public class TIFFImageReader extends ImageReaderBase { case TIFFExtension.COMPRESSION_LZW: return new DecoderStream(stream, LZWDecoder.create(LZWDecoder.isOldBitReversedStream(stream)), 1024); case TIFFExtension.COMPRESSION_ZLIB: - case TIFFExtension.COMPRESSION_DEFLATE: // TIFFphotoshop.pdf (aka TIFF specification, supplement 2) says ZLIB (8) and DEFLATE (32946) algorithms are identical + case TIFFExtension.COMPRESSION_DEFLATE: return new InflaterInputStream(stream, new Inflater(), 1024); + case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE: + case TIFFExtension.COMPRESSION_CCITT_T4: + case TIFFExtension.COMPRESSION_CCITT_T6: + return new CCITTFaxDecoderStream(stream, width, compression, getValueAsIntWithDefault(TIFF.TAG_FILL_ORDER, 1)); default: throw new IllegalArgumentException("Unsupported TIFF compression: " + compression); } } + private InputStream createUnpredictorStream(final int predictor, final int width, final int samplesPerPixel, final int bitsPerSample, final InputStream stream, final ByteOrder byteOrder) throws IOException { + switch (predictor) { + case TIFFBaseline.PREDICTOR_NONE: + return stream; + case TIFFExtension.PREDICTOR_HORIZONTAL_DIFFERENCING: + return new HorizontalDeDifferencingStream(stream, width, samplesPerPixel, bitsPerSample, byteOrder); + case TIFFExtension.PREDICTOR_HORIZONTAL_FLOATINGPOINT: + throw new IIOException("Unsupported TIFF Predictor value: " + predictor); + default: + throw new IIOException("Unknown TIFF Predictor value: " + predictor); + } + } + private long[] getValueAsLongArray(final int tag, final String tagName, boolean required) throws IIOException { Entry entry = currentIFD.getEntryById(tag); if (entry == null) { diff --git a/imageio/imageio-tiff/src/main/resources/tiff-image-metadata-sun.dtd b/imageio/imageio-tiff/src/main/resources/tiff-image-metadata-sun.dtd new file mode 100644 index 00000000..74451957 --- /dev/null +++ b/imageio/imageio-tiff/src/main/resources/tiff-image-metadata-sun.dtd @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]> \ No newline at end of file diff --git a/imageio/imageio-tiff/src/main/resources/tiff-stream-metadata-sun.dtd b/imageio/imageio-tiff/src/main/resources/tiff-stream-metadata-sun.dtd new file mode 100644 index 00000000..862a0fc0 --- /dev/null +++ b/imageio/imageio-tiff/src/main/resources/tiff-stream-metadata-sun.dtd @@ -0,0 +1,9 @@ + + + + + + + +]> From 6ce9543c009408c303566496a74cf001179fb856 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Wed, 12 Jun 2013 21:57:09 +0200 Subject: [PATCH 53/58] TMI-TIFF: Test case for CCITT decoding. --- .../tiff/CCITTFaxDecoderStreamTest.java | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStreamTest.java diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStreamTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStreamTest.java new file mode 100644 index 00000000..8c735d82 --- /dev/null +++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStreamTest.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2013, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.tiff; + +import org.junit.Before; +import org.junit.Test; + +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferByte; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; + +import static org.junit.Assert.*; + +/** + * CCITTFaxDecoderStreamTest + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: CCITTFaxDecoderStreamTest.java,v 1.0 09.03.13 14:44 haraldk Exp$ + */ +public class CCITTFaxDecoderStreamTest { + + // TODO: Better tests (full A4 width scan lines?) + + // From http://www.mikekohn.net/file_formats/tiff.php + static final byte[] DATA_TYPE_2 = { + (byte) 0x84, (byte) 0xe0, // 10000100 11100000 + (byte) 0x84, (byte) 0xe0, // 10000100 11100000 + (byte) 0x84, (byte) 0xe0, // 10000100 11100000 + (byte) 0x7d, (byte) 0xc0, // 01111101 11000000 + }; + + static final byte[] DATA_TYPE_3 = { + 0x00, 0x01, (byte) 0xc2, 0x70, + 0x00, 0x01, 0x70, + 0x01, + + }; + + static final byte[] DATA_TYPE_4 = { + 0x26, (byte) 0xb0, 95, (byte) 0xfa, (byte) 0xc0 + }; + + // Image should be (6 x 4): + // 1 1 1 0 1 1 x x + // 1 1 1 0 1 1 x x + // 1 1 1 0 1 1 x x + // 1 1 0 0 1 1 x x + BufferedImage image; + + @Before + public void init() { + image = new BufferedImage(6, 4, BufferedImage.TYPE_BYTE_BINARY); + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 6; x++) { + image.setRGB(x, y, x == 3 ? 0xff000000 : 0xffffffff); + } + } + + image.setRGB(2, 3, 0xff000000); + } + + @Test + public void testReadCountType2() throws IOException { + InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_TYPE_2), 6, TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 1); + + int count = 0; + int read; + while ((read = stream.read()) >= 0) { + count++; + } + + // Just make sure we'll have 4 bytes + assertEquals(4, count); + + // Verify that we don't return arbitrary values + assertEquals(-1, read); + } + + @Test + public void testDecodeType2() throws IOException { + InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_TYPE_2), 6, TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 1); + + byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData(); + byte[] bytes = new byte[imageData.length]; + new DataInputStream(stream).readFully(bytes); + +// JPanel panel = new JPanel(); +// panel.add(new JLabel("Expected", new BufferedImageIcon(image, 300, 300, true), JLabel.CENTER)); +// panel.add(new JLabel("Actual", new BufferedImageIcon(new BufferedImage(image.getColorModel(), Raster.createPackedRaster(new DataBufferByte(bytes, bytes.length), 6, 4, 1, null), false, null), 300, 300, true), JLabel.CENTER)); +// JOptionPane.showConfirmDialog(null, panel); + + assertArrayEquals(imageData, bytes); + } + + @Test(expected = IllegalArgumentException.class) + public void testDecodeType3() throws IOException { + InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_TYPE_3), 6, TIFFExtension.COMPRESSION_CCITT_T4, 1); + + byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData(); + byte[] bytes = new byte[imageData.length]; + DataInputStream dataInput = new DataInputStream(stream); + + for (int y = 0; y < image.getHeight(); y++) { + System.err.println("y: " + y); + dataInput.readFully(bytes, y * image.getWidth(), image.getWidth()); + } + +// JPanel panel = new JPanel(); +// panel.add(new JLabel("Expected", new BufferedImageIcon(image, 300, 300, true), JLabel.CENTER)); +// panel.add(new JLabel("Actual", new BufferedImageIcon(new BufferedImage(image.getColorModel(), Raster.createPackedRaster(new DataBufferByte(bytes, bytes.length), 6, 4, 1, null), false, null), 300, 300, true), JLabel.CENTER)); +// JOptionPane.showConfirmDialog(null, panel); + + assertArrayEquals(imageData, bytes); + } + + @Test(expected = IllegalArgumentException.class) + public void testDecodeType4() throws IOException { + InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_TYPE_4), 6, TIFFExtension.COMPRESSION_CCITT_T6, 1); + + byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData(); + byte[] bytes = new byte[imageData.length]; + DataInputStream dataInput = new DataInputStream(stream); + + for (int y = 0; y < image.getHeight(); y++) { + System.err.println("y: " + y); + dataInput.readFully(bytes, y * image.getWidth(), image.getWidth()); + } + +// JPanel panel = new JPanel(); +// panel.add(new JLabel("Expected", new BufferedImageIcon(image, 300, 300, true), JLabel.CENTER)); +// panel.add(new JLabel("Actual", new BufferedImageIcon(new BufferedImage(image.getColorModel(), Raster.createPackedRaster(new DataBufferByte(bytes, bytes.length), 6, 4, 1, null), false, null), 300, 300, true), JLabel.CENTER)); +// JOptionPane.showConfirmDialog(null, panel); + + assertArrayEquals(imageData, bytes); + } +} From 24330755784fa4c66d42c9c2b0ac27cfc0fda74a Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Thu, 13 Jun 2013 09:30:18 +0200 Subject: [PATCH 54/58] TMI-JPEG: Removed experimental metadata code to avoid NPE. --- .../imageio/plugins/jpeg/JPEGImageReader.java | 36 ++++++++++--------- .../plugins/jpeg/JPEGImageReaderTest.java | 35 ++++++++++++++---- 2 files changed, 48 insertions(+), 23 deletions(-) diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java index 762d8de3..81b7269e 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java @@ -41,13 +41,11 @@ import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment; import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil; import com.twelvemonkeys.imageio.util.ProgressListenerBase; import com.twelvemonkeys.lang.Validate; -import org.w3c.dom.Node; import javax.imageio.*; import javax.imageio.event.IIOReadUpdateListener; import javax.imageio.event.IIOReadWarningListener; import javax.imageio.metadata.IIOMetadata; -import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.spi.ImageReaderSpi; import javax.imageio.stream.ImageInputStream; import java.awt.*; @@ -65,8 +63,8 @@ import java.util.List; *

* Main features: *

    - *
  • Support for CMYK JPEGs (converted to RGB by default, using the embedded ICC profile if applicable)
  • - *
  • Support for Adobe YCCK JPEGs (converted to RGB by default, using the embedded ICC profile if applicable)
  • + *
  • Support for CMYK JPEGs (converted to RGB by default or as CMYK, using the embedded ICC profile if applicable)
  • + *
  • Support for Adobe YCCK JPEGs (converted to RGB by default or as CMYK, using the embedded ICC profile if applicable)
  • *
  • Support for JPEGs containing ICC profiles with interpretation other than 'Perceptual' (profile is assumed to be 'Perceptual' and used)
  • *
  • Support for JPEGs containing ICC profiles with class other than 'Display' (profile is assumed to have class 'Display' and used)
  • *
  • Support for JPEGs containing ICC profiles that are incompatible with stream data (image data is read, profile is ignored)
  • @@ -202,7 +200,7 @@ public class JPEGImageReader extends ImageReaderBase { typeList.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_BGR)); // We also read and return CMYK if the source image is CMYK/YCCK + original color profile if present - ICC_Profile profile = getEmbeddedICCProfile(); + ICC_Profile profile = getEmbeddedICCProfile(false); if (csType == JPEGColorSpace.CMYK || csType == JPEGColorSpace.YCCK) { if (profile != null) { @@ -251,7 +249,7 @@ public class JPEGImageReader extends ImageReaderBase { switch (csType) { case CMYK: // Create based on embedded profile if exists, or create from "Generic CMYK" - ICC_Profile profile = getEmbeddedICCProfile(); + ICC_Profile profile = getEmbeddedICCProfile(false); if (profile != null) { return ImageTypeSpecifier.createInterleaved(ColorSpaces.createColorSpace(profile), new int[] {3, 2, 1, 0}, DataBuffer.TYPE_BYTE, false, false); @@ -287,7 +285,7 @@ public class JPEGImageReader extends ImageReaderBase { // Might want to look into the metadata, to see if there's a better way to identify these. boolean unsupported = !delegate.getImageTypes(imageIndex).hasNext(); - ICC_Profile profile = getEmbeddedICCProfile(); + ICC_Profile profile = getEmbeddedICCProfile(false); AdobeDCTSegment adobeDCT = getAdobeDCT(); // TODO: Probably something bogus here, as ICC profile isn't applied if reading through the delegate any more... @@ -761,7 +759,7 @@ public class JPEGImageReader extends ImageReaderBase { return data; } - private ICC_Profile getEmbeddedICCProfile() throws IOException { + private ICC_Profile getEmbeddedICCProfile(final boolean allowBadIndexes) throws IOException { // ICC v 1.42 (2006) annex B: // APP2 marker (0xFFE2) + 2 byte length + ASCII 'ICC_PROFILE' + 0 (termination) // + 1 byte chunk number + 1 byte chunk count (allows ICC profiles chunked in multiple APP2 segments) @@ -799,13 +797,19 @@ public class JPEGImageReader extends ImageReaderBase { // Handle these by issuing warning processWarningOccurred(String.format("Bad 'ICC_PROFILE' chunk count: %d. Ignoring ICC profile.", chunkCount)); badICC = true; - return null; + + if (!allowBadIndexes) { + return null; + } } if (!badICC && chunkNumber < 1) { // Anything else is just ignored processWarningOccurred(String.format("Invalid 'ICC_PROFILE' chunk index: %d. Ignoring ICC profile.", chunkNumber)); - return null; + + if (!allowBadIndexes) { + return null; + } } int count = badICC ? segments.size() : chunkCount; @@ -969,8 +973,8 @@ public class JPEGImageReader extends ImageReaderBase { // TODO: Nice try, but no cigar.. getAsTree does not return a "live" view, so any modifications are thrown away IIOMetadata metadata = delegate.getImageMetadata(imageIndex); - IIOMetadataNode tree = (IIOMetadataNode) metadata.getAsTree(metadata.getNativeMetadataFormatName()); - Node jpegVariety = tree.getElementsByTagName("JPEGvariety").item(0); +// IIOMetadataNode tree = (IIOMetadataNode) metadata.getAsTree(metadata.getNativeMetadataFormatName()); +// Node jpegVariety = tree.getElementsByTagName("JPEGvariety").item(0); // TODO: Allow EXIF (as app1EXIF) in the JPEGvariety (sic) node. // As EXIF is (a subset of) TIFF, (and the EXIF data is a valid TIFF stream) probably use something like: @@ -992,11 +996,11 @@ public class JPEGImageReader extends ImageReaderBase { the version to the method/constructor used to obtain an IIOMetadata object.) */ - IIOMetadataNode app2ICC = new IIOMetadataNode("app2ICC"); - app2ICC.setUserObject(getEmbeddedICCProfile()); - jpegVariety.getFirstChild().appendChild(app2ICC); +// IIOMetadataNode app2ICC = new IIOMetadataNode("app2ICC"); +// app2ICC.setUserObject(getEmbeddedICCProfile()); +// jpegVariety.getFirstChild().appendChild(app2ICC); -// new XMLSerializer(System.err, System.getProperty("file.encoding")).serialize(tree, false); + // new XMLSerializer(System.err, System.getProperty("file.encoding")).serialize(tree, false); return metadata; } diff --git a/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java b/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java index c2b7ba31..37a34f73 100644 --- a/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java +++ b/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java @@ -31,10 +31,12 @@ package com.twelvemonkeys.imageio.plugins.jpeg; import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase; import org.junit.Test; +import javax.imageio.IIOException; import javax.imageio.ImageIO; import javax.imageio.ImageReadParam; import javax.imageio.ImageTypeSpecifier; import javax.imageio.event.IIOReadWarningListener; +import javax.imageio.metadata.IIOMetadata; import javax.imageio.spi.IIORegistry; import javax.imageio.spi.ImageReaderSpi; import java.awt.*; @@ -256,7 +258,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase> 16) & 0xff, (expectedRGB[i] >> 16) & 0xff, 5); - assertEquals((actualRGB >> 8) & 0xff, (expectedRGB[i] >> 8) & 0xff, 5); - assertEquals((actualRGB) & 0xff, (expectedRGB[i]) & 0xff, 5); + assertEquals((actualRGB >> 8) & 0xff, (expectedRGB[i] >> 8) & 0xff, 5); + assertEquals((actualRGB) & 0xff, (expectedRGB[i]) & 0xff, 5); } } @@ -488,8 +490,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase> 16) & 0xff, (expectedRGB[i] >> 16) & 0xff, 5); - assertEquals((actualRGB >> 8) & 0xff, (expectedRGB[i] >> 8) & 0xff, 5); - assertEquals((actualRGB) & 0xff, (expectedRGB[i]) & 0xff, 5); + assertEquals((actualRGB >> 8) & 0xff, (expectedRGB[i] >> 8) & 0xff, 5); + assertEquals((actualRGB) & 0xff, (expectedRGB[i]) & 0xff, 5); } } @@ -550,7 +552,6 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase Date: Wed, 19 Jun 2013 16:58:03 +0200 Subject: [PATCH 55/58] TMI-TIFF: Added license. --- imageio/imageio-tiff/license.txt | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 imageio/imageio-tiff/license.txt diff --git a/imageio/imageio-tiff/license.txt b/imageio/imageio-tiff/license.txt new file mode 100644 index 00000000..4913b243 --- /dev/null +++ b/imageio/imageio-tiff/license.txt @@ -0,0 +1,25 @@ +Copyright (c) 2013, Harald Kuhr +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name "TwelveMonkeys" nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 0860db2166f3208d545a6e1bee388c18c04e2acc Mon Sep 17 00:00:00 2001 From: justwrote Date: Fri, 21 Jun 2013 12:07:49 +0200 Subject: [PATCH 56/58] mockito should be a test dependency --- imageio/pom.xml | 5 +++-- servlet/pom.xml | 7 ++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/imageio/pom.xml b/imageio/pom.xml index f91ba124..fc95d0f6 100644 --- a/imageio/pom.xml +++ b/imageio/pom.xml @@ -45,7 +45,7 @@ imageio-jmagick - imageio-reference + imageio-reference @@ -90,6 +90,7 @@ org.mockito mockito-all 1.8.5 + test @@ -115,5 +116,5 @@ test - + diff --git a/servlet/pom.xml b/servlet/pom.xml index 28bb1982..a59056e0 100644 --- a/servlet/pom.xml +++ b/servlet/pom.xml @@ -29,7 +29,7 @@ common-image ${project.version} - + com.twelvemonkeys.common common-lang @@ -65,7 +65,7 @@ 1.2.14 provided - + commons-fileupload commons-fileupload @@ -84,6 +84,7 @@ org.mockito mockito-all 1.8.5 + test @@ -117,5 +118,5 @@ - + From de8172391205e2bbd7ac5cde42d9bab0a25f2646 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Thu, 27 Jun 2013 10:20:32 +0200 Subject: [PATCH 57/58] TMI-JPEG: Added license. --- imageio/imageio-jpeg/license.txt | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 imageio/imageio-jpeg/license.txt diff --git a/imageio/imageio-jpeg/license.txt b/imageio/imageio-jpeg/license.txt new file mode 100644 index 00000000..4913b243 --- /dev/null +++ b/imageio/imageio-jpeg/license.txt @@ -0,0 +1,25 @@ +Copyright (c) 2013, Harald Kuhr +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name "TwelveMonkeys" nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 4de927b657f00712622c6a190bdbd36b60c380e2 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Mon, 15 Jul 2013 09:39:13 +0200 Subject: [PATCH 58/58] TMI-META: Removed misleading TODO/comment --- .../com/twelvemonkeys/imageio/metadata/jpeg/JPEGQuality.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGQuality.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGQuality.java index 77a107d0..f4d0ae29 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGQuality.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGQuality.java @@ -90,10 +90,7 @@ public final class JPEGQuality { private static int getJPEGQuality(final int[][] quantizationTables) throws IOException { // System.err.println("tables: " + Arrays.deepToString(tables)); - // TODO: Determine lossless JPEG -// if (lossless) { -// return 100; // TODO: Sums can be 100... Is lossless not 100? -// } + // TODO: Determine lossless JPEG, it's an entirely different algorithm int qvalue;