diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImage10Metadata.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImage10Metadata.java
index d3a59730..2b8e49ae 100644
--- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImage10Metadata.java
+++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImage10Metadata.java
@@ -148,7 +148,7 @@ class JPEGImage10Metadata extends AbstractMetadata {
if (segment instanceof Frame) {
Frame sofSegment = (Frame) segment;
IIOMetadataNode colorSpaceType = new IIOMetadataNode("ColorSpaceType");
- colorSpaceType.setAttribute("name", sofSegment.componentsInFrame() == 1 ? "Gray" : "RGB"); // TODO YCC, YCCK, CMYK etc
+ colorSpaceType.setAttribute("name", sofSegment.componentsInFrame() == 1 ? "GRAY" : "RGB"); // TODO YCC, YCCK, CMYK etc
chroma.appendChild(colorSpaceType);
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
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 ec522ca9..725bc03b 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
@@ -352,10 +352,24 @@ public final class JPEGImageReader extends ImageReaderBase {
JPEGColorSpace sourceCSType = getSourceCSType(getJFIF(), adobeDCT, sof);
if (sof.marker == JPEG.SOF3) {
+ // Read image as lossless
+ if (DEBUG) {
+ System.out.println("Reading using Lossless decoder");
+ }
+
// TODO: What about stream position?
// TODO: Param handling: Source region, offset, subsampling, destination, destination type, etc....
- // Read image as lossless
- return new JPEGLosslessDecoderWrapper(this).readImage(segments, imageInput);
+ BufferedImage bufferedImage = new JPEGLosslessDecoderWrapper(this).readImage(segments, imageInput);
+
+ // TODO: This is QnD, move param handling to lossless wrapper
+ // TODO: Create test!
+ BufferedImage destination = param != null ? param.getDestination() : null;
+ if (destination != null) {
+ destination.getRaster().setDataElements(0, 0, bufferedImage.getRaster());
+ return destination;
+ }
+
+ return bufferedImage;
}
// We need to apply ICC profile unless the profile is sRGB/default gray (whatever that is)
@@ -382,6 +396,18 @@ public final class JPEGImageReader extends ImageReaderBase {
return delegate.read(imageIndex, param);
}
+ static void drawOnto(final BufferedImage pDestination, final Image pSource) {
+ Graphics2D g = pDestination.createGraphics();
+ try {
+ g.setComposite(AlphaComposite.Src);
+ g.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
+ g.drawImage(pSource, 0, 0, null);
+ }
+ finally {
+ g.dispose();
+ }
+ }
+
private BufferedImage readImageAsRasterAndReplaceColorProfile(int imageIndex, ImageReadParam param, Frame startOfFrame, JPEGColorSpace csType, ICC_Profile profile) throws IOException {
int origWidth = getWidth(imageIndex);
int origHeight = getHeight(imageIndex);
diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGLosslessDecoderWrapper.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGLosslessDecoderWrapper.java
index ccea1e14..8c44f21c 100644
--- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGLosslessDecoderWrapper.java
+++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGLosslessDecoderWrapper.java
@@ -33,10 +33,9 @@ import com.twelvemonkeys.imageio.stream.BufferedImageInputStream;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
-import java.awt.image.BufferedImage;
-import java.awt.image.DataBufferByte;
-import java.awt.image.DataBufferUShort;
-import java.awt.image.Raster;
+import java.awt.*;
+import java.awt.color.ColorSpace;
+import java.awt.image.*;
import java.io.IOException;
import java.util.List;
@@ -71,7 +70,7 @@ final class JPEGLosslessDecoderWrapper {
* - 16Bit, Grayscale -> BufferedImage.TYPE_USHORT_GRAY
*
* @param segments segments
- * @param input input stream which contains a jpeg lossless data
+ * @param input input stream which contains JPEG Lossless data
* @return if successfully a BufferedImage is returned
* @throws IOException is thrown if the decoder failed or a conversion is not supported
*/
@@ -92,8 +91,11 @@ final class JPEGLosslessDecoderWrapper {
switch (decoder.getPrecision()) {
case 8:
return to8Bit1ComponentGrayScale(decoded, width, height);
+ case 10:
+ case 12:
+ case 14:
case 16:
- return to16Bit1ComponentGrayScale(decoded, width, height);
+ return to16Bit1ComponentGrayScale(decoded, decoder.getPrecision(), width, height);
}
}
// 3 components, assumed to be RGB
@@ -121,12 +123,20 @@ final class JPEGLosslessDecoderWrapper {
* precision: 16 bit, componentCount = 1
*
* @param decoded data buffer
+ * @param precision
* @param width of the image
- * @param height of the image
- * @return a BufferedImage.TYPE_USHORT_GRAY
+ * @param height of the image @return a BufferedImage.TYPE_USHORT_GRAY
*/
- private BufferedImage to16Bit1ComponentGrayScale(int[][] decoded, int width, int height) {
- BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_USHORT_GRAY);
+ private BufferedImage to16Bit1ComponentGrayScale(int[][] decoded, int precision, int width, int height) {
+ BufferedImage image;
+ if (precision == 16) {
+ image = new BufferedImage(width, height, BufferedImage.TYPE_USHORT_GRAY);
+ }
+ else {
+ ColorModel colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {precision}, false, false, Transparency.OPAQUE, DataBuffer.TYPE_USHORT);
+ image = new BufferedImage(colorModel, colorModel.createCompatibleWritableRaster(width, height), colorModel.isAlphaPremultiplied(), null);
+ }
+
short[] imageBuffer = ((DataBufferUShort) image.getRaster().getDataBuffer()).getData();
for (int i = 0; i < imageBuffer.length; i++) {
diff --git a/imageio/imageio-tiff/pom.xml b/imageio/imageio-tiff/pom.xml
index 7fc02cce..64472584 100644
--- a/imageio/imageio-tiff/pom.xml
+++ b/imageio/imageio-tiff/pom.xml
@@ -21,6 +21,11 @@
com.twelvemonkeys.imageio
imageio-metadata
+
+ com.twelvemonkeys.imageio
+ imageio-jpeg
+ test
+
com.twelvemonkeys.imageio
imageio-core
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 2276b5ea..46ff9a4d 100755
--- 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
@@ -38,6 +38,7 @@ import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.iptc.IPTCReader;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
+import com.twelvemonkeys.imageio.metadata.psd.PSD;
import com.twelvemonkeys.imageio.metadata.psd.PSDReader;
import com.twelvemonkeys.imageio.metadata.tiff.Rational;
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
@@ -51,6 +52,7 @@ import com.twelvemonkeys.io.FastByteArrayOutputStream;
import com.twelvemonkeys.io.LittleEndianDataInputStream;
import com.twelvemonkeys.io.enc.DecoderStream;
import com.twelvemonkeys.io.enc.PackBitsDecoder;
+import com.twelvemonkeys.lang.StringUtil;
import org.w3c.dom.NodeList;
import javax.imageio.*;
@@ -239,9 +241,97 @@ public final class TIFFImageReader extends ImageReaderBase {
if (Arrays.equals(foo.getBytes(StandardCharsets.US_ASCII), Arrays.copyOf(value, foo.length()))) {
System.err.println("foo: " + foo);
-// int offset = foo.length() + 1;
-// ImageInputStream input = new ByteArrayImageInputStream(value, offset, value.length - offset);
+ int offset = foo.length() + 1;
+ ImageInputStream input = new ByteArrayImageInputStream(value, offset, value.length - offset);
// input.setByteOrder(ByteOrder.LITTLE_ENDIAN); // TODO: WHY???!
+
+ while (input.getStreamPosition() < value.length - offset) {
+ int resourceId = input.readInt();
+ if (resourceId != PSD.RESOURCE_TYPE) {
+ System.err.println("Not a PSD resource: " + resourceId);
+ break;
+ }
+
+ int resourceKey = input.readInt();
+ System.err.println("resourceKey: " + intToStr(resourceKey));
+ long resourceLength = input.readUnsignedInt();
+ System.err.println("resourceLength: " + resourceLength);
+
+ long pad = (4 - (resourceLength % 4)) % 4;
+ long resourceLengthPadded = resourceLength + pad; // Padded to 32 bit boundary, possibly 64 bit for 8B64 resources
+ long streamPosition = input.getStreamPosition();
+
+ if (resourceKey == ('L' << 24 | 'a' << 16 | 'y' << 8 | 'r')) {
+ short count = input.readShort();
+ System.err.println("layer count: " + count);
+
+ for (int layer = 0; layer < count; layer++) {
+ int top = input.readInt();
+ int left = input.readInt();
+ int bottom = input.readInt();
+ int right = input.readInt();
+ System.err.printf("%d, %d, %d, %d\n", top, left, bottom, right);
+
+ short channels = input.readShort();
+ System.err.println("channels: " + channels);
+
+ for (int channel = 0; channel < channels; channel++) {
+ short channelId = input.readShort();
+ System.err.println("channelId: " + channelId);
+ long channelLength = input.readUnsignedInt();
+ System.err.println("channelLength: " + channelLength);
+ }
+
+ System.err.println("8BIM: " + intToStr(input.readInt()));
+ int blendMode = input.readInt();
+ System.err.println("blend mode key: " + intToStr(blendMode));
+
+ int opacity = input.readUnsignedByte();
+ System.err.println("opacity: " + opacity);
+ int clipping = input.readUnsignedByte();
+ System.err.println("clipping: " + clipping);
+ byte flags = input.readByte();
+ System.err.printf("flags: 0x%02x\n", flags);
+ input.readByte(); // Pad
+
+ long layerExtraDataLength = input.readUnsignedInt();
+ long pos = input.getStreamPosition();
+ System.err.println("length: " + layerExtraDataLength);
+
+ long layerMaskSize = input.readUnsignedInt();
+ input.skipBytes(layerMaskSize);
+ long layerBlendingRangesSize = input.readUnsignedInt();
+ input.skipBytes(layerBlendingRangesSize);
+
+ String layerName = readPascalString(input);
+ System.err.println("layerName: " + layerName);
+ int mod = (layerName.length() + 1) % 4; // len + 1 for null-term
+ System.err.println("mod: " + mod);
+ if (mod != 0) {
+ input.skipBytes(4 - mod);
+ }
+ System.err.println("input.getStreamPosition(): " + input.getStreamPosition());
+
+ // TODO: More data here
+ System.err.println(TIFFReader.HexDump.dump(0, value, (int) (offset + input.getStreamPosition()), 64));;
+
+ input.seek(pos + layerExtraDataLength);
+ }
+
+
+// long len = input.readUnsignedInt();
+// System.err.println("len: " + len);
+//
+// int count = input.readUnsignedShort();
+// System.err.println("count: " + count);
+
+ System.err.println(TIFFReader.HexDump.dump(0, value, (int) (offset + input.getStreamPosition()), 64));;
+
+ }
+ input.seek(streamPosition + resourceLengthPadded);
+ System.out.println("input.getStreamPosition(): " + input.getStreamPosition());
+ }
+
// Directory psd2 = new PSDReader().read(input);
// System.err.println("-----------------------------------------------------------------------------");
// System.err.println("psd2: " + psd2);
@@ -251,6 +341,30 @@ public final class TIFFImageReader extends ImageReaderBase {
}
}
+ static String readPascalString(final DataInput pInput) throws IOException {
+ int length = pInput.readUnsignedByte();
+
+ if (length == 0) {
+ return "";
+ }
+
+ byte[] bytes = new byte[length];
+ pInput.readFully(bytes);
+
+ return StringUtil.decode(bytes, 0, bytes.length, "ASCII");
+ }
+
+ static String intToStr(int value) {
+ return new String(
+ new byte[]{
+ (byte) ((value & 0xff000000) >>> 24),
+ (byte) ((value & 0x00ff0000) >> 16),
+ (byte) ((value & 0x0000ff00) >> 8),
+ (byte) ((value & 0x000000ff))
+ }
+ );
+ }
+
private void readIFD(final int imageIndex) throws IOException {
readMetadata();
checkBounds(imageIndex);
@@ -1048,7 +1162,18 @@ public final class TIFFImageReader extends ImageReaderBase {
// Otherwise, it's likely CMYK or some other interpretation we don't need to convert.
// We'll have to use readAsRaster and later apply color space conversion ourselves
Raster raster = jpegReader.readRaster(0, jpegParam);
- normalizeColor(interpretation, ((DataBufferByte) raster.getDataBuffer()).getData());
+ // TODO: Refactor + duplicate this for all JPEG-in-TIFF cases
+ switch (raster.getTransferType()) {
+ case DataBuffer.TYPE_BYTE:
+ normalizeColor(interpretation, ((DataBufferByte) raster.getDataBuffer()).getData());
+ break;
+ case DataBuffer.TYPE_USHORT:
+ normalizeColor(interpretation, ((DataBufferUShort) raster.getDataBuffer()).getData());
+ break;
+ default:
+ throw new IllegalStateException("Unsupported transfer type: " + raster.getTransferType());
+ }
+
destination.getRaster().setDataElements(offset.x, offset.y, raster);
}
}
@@ -1081,9 +1206,8 @@ public final class TIFFImageReader extends ImageReaderBase {
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)");
+ break; // Supported
default:
throw new IIOException("Unknown TIFF JPEGProcessingMode value: " + mode);
}
@@ -1350,7 +1474,7 @@ public final class TIFFImageReader extends ImageReaderBase {
return jpegReader.getImageMetadata(0);
}
catch (IIOException e) {
- processWarningOccurred("Could not read metadata metadata JPEG compressed TIFF: " + e.getMessage() + " colors may look incorrect");
+ processWarningOccurred("Could not read metadata for JPEG compressed TIFF (" + e.getMessage() + "): Colors may look incorrect");
return null;
}
@@ -1391,20 +1515,8 @@ public final class TIFFImageReader extends ImageReaderBase {
}
private ImageReader createJPEGDelegate() throws IOException {
- // TIFF is strictly ISO JPEG, so we should probably stick to the standard reader
- ImageReaderSpi jpegProvider = lookupProviderByName(IIORegistry.getDefaultInstance(), "com.sun.imageio.plugins.jpeg.JPEGImageReaderSpi");
-
- if (jpegProvider != null) {
- return jpegProvider.createReaderInstance();
- }
-
- // Fall back to default reader below
- if (DEBUG) {
- System.err.println("Could not create " + "com.sun.imageio.plugins.jpeg.JPEGImageReader"
- + ", falling back to default JPEG capable ImageReader");
- }
-
- // If we can't get the standard reader, fall back to the default (first) reader
+ // We'll just use the default (first) reader
+ // If it's the TwelveMonkeys one, we will be able to read JPEG Lossless etc.
Iterator readers = ImageIO.getImageReadersByFormatName("JPEG");
if (!readers.hasNext()) {
throw new IIOException("Could not instantiate JPEGImageReader");
@@ -1415,28 +1527,17 @@ public final class TIFFImageReader extends ImageReaderBase {
private static InputStream createJFIFStream(int bands, int stripTileWidth, int stripTileHeight, byte[][] qTables, byte[][] dcTables, byte[][] acTables) throws IOException {
FastByteArrayOutputStream stream = new FastByteArrayOutputStream(
- 2 + 2 + 2 + 6 + 3 * bands +
+ 2 +
5 * qTables.length + qTables.length * qTables[0].length +
5 * dcTables.length + dcTables.length * dcTables[0].length +
5 * acTables.length + acTables.length * acTables[0].length +
+ 2 + 2 + 6 + 3 * bands +
8 + 2 * bands
);
DataOutputStream out = new DataOutputStream(stream);
out.writeShort(JPEG.SOI);
- out.writeShort(JPEG.SOF0);
- out.writeShort(2 + 6 + 3 * bands); // 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(bands); // Number of components
-
- for (int comp = 0; comp < bands; 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++) {
@@ -1465,6 +1566,19 @@ public final class TIFFImageReader extends ImageReaderBase {
out.write(table); // Table data
}
+ out.writeShort(JPEG.SOF0);
+ out.writeShort(2 + 6 + 3 * bands); // 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(bands); // Number of components
+
+ for (int comp = 0; comp < bands; 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
+ }
+
out.writeShort(JPEG.SOS);
out.writeShort(6 + 2 * bands); // SOS length
out.writeByte(bands); // Num comp
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 36b5b6cb..5fbfea63 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
@@ -149,11 +149,15 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest getUnsupportedTestData() {
+ private List getUnsupportedTestData() {
return Arrays.asList(
// RGB Interleaved (PlanarConfiguration: 1)
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-02.tif"), new Dimension(73, 43)), // RGB 2 bit/sample
@@ -265,7 +269,7 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest${project.version}
+
+ ${project.groupId}
+ imageio-jpeg
+ ${project.version}
+
+
${project.groupId}
imageio-core