getImageTypes(int pIndex) throws IOException {
+ // TODO: The images look slightly different in Preview.. Could indicate the color space is wrong...
+ return Arrays.asList(
+ ImageTypeSpecifier.createPacked(
+ ColorSpace.getInstance(ColorSpace.CS_sRGB),
+ 0xff0000, 0xff00, 0xff, 0xff000000, DataBuffer.TYPE_INT, false
+ )
+ ).iterator();
+ }
+
+ public static void main(String[] pArgs) throws IOException {
+ ImageReader reader = new PICTImageReader(new PICTImageReaderSpi());
+
+ ImageInputStream input;
+ String title;
+ if (pArgs.length >= 1) {
+ File file = new File(pArgs[0]);
+ input = ImageIO.createImageInputStream(file);
+ title = file.getName();
+ }
+ else {
+ input = ImageIO.createImageInputStream(new ByteArrayInputStream(DATA_V1_OVERPAINTED_ARC));
+ title = "PICT test data";
+ }
+
+ System.out.println("canRead: " + reader.getOriginatingProvider().canDecodeInput(input));
+
+ reader.setInput(input);
+ long start = System.currentTimeMillis();
+ BufferedImage image = reader.read(0);
+
+ System.out.println("time: " + (System.currentTimeMillis() - start));
+
+ showIt(image, title);
+
+ System.out.println("image = " + image);
+ }
+
+ // Sample data from http://developer.apple.com/documentation/mac/QuickDraw/QuickDraw-458.html
+ // TODO: Create test case(s)!
+ private static final byte[] DATA_EXT_V2 = {
+ 0x00, 0x78, /* picture size; don't use this value for picture size */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x6C, 0x00, (byte) 0xA8, /* bounding rectangle of picture at 72 dpi */
+ 0x00, 0x11, /* VersionOp opcode; always $0011 for extended version 2 */
+ 0x02, (byte) 0xFF, /* Version opcode; always $02FF for extended version 2 */
+ 0x0C, 0x00, /* HeaderOp opcode; always $0C00 for extended version 2 */
+ /* next 24 bytes contain header information */
+ (byte) 0xFF, (byte) 0xFE, /* version; always -2 for extended version 2 */
+ 0x00, 0x00, /* reserved */
+ 0x00, 0x48, 0x00, 0x00, /* best horizontal resolution: 72 dpi */
+ 0x00, 0x48, 0x00, 0x00, /* best vertical resolution: 72 dpi */
+ 0x00, 0x02, 0x00, 0x02, 0x00, 0x6E, 0x00, (byte) 0xAA, /* optimal source rectangle for 72 dpi horizontal
+ and 72 dpi vertical resolutions */
+ 0x00, 0x00, /* reserved */
+ 0x00, 0x1E, /* DefHilite opcode to use default hilite color */
+ 0x00, 0x01, /* Clip opcode to define clipping region for picture */
+ 0x00, 0x0A, /* region size */
+ 0x00, 0x02, 0x00, 0x02, 0x00, 0x6E, 0x00, (byte) 0xAA, /* bounding rectangle for clipping region */
+ 0x00, 0x0A, /* FillPat opcode; fill pattern specified in next 8 bytes */
+ 0x77, (byte) 0xDD, 0x77, (byte) 0xDD, 0x77, (byte) 0xDD, 0x77, (byte) 0xDD, /* fill pattern */
+ 0x00, 0x34, /* fillRect opcode; rectangle specified in next 8 bytes */
+ 0x00, 0x02, 0x00, 0x02, 0x00, 0x6E, 0x00, (byte) 0xAA, /* rectangle to fill */
+ 0x00, 0x0A, /* FillPat opcode; fill pattern specified in next 8 bytes */
+ (byte) 0x88, 0x22, (byte) 0x88, 0x22, (byte) 0x88, 0x22, (byte) 0x88, 0x22, /* fill pattern */
+ 0x00, 0x5C, /* fillSameOval opcode */
+ 0x00, 0x08, /* PnMode opcode */
+ 0x00, 0x08, /* pen mode data */
+ 0x00, 0x71, /* paintPoly opcode */
+ 0x00, 0x1A, /* size of polygon */
+ 0x00, 0x02, 0x00, 0x02, 0x00, 0x6E, 0x00, (byte) 0xAA, /* bounding rectangle for polygon */
+ 0x00, 0x6E, 0x00, 0x02, 0x00, 0x02, 0x00, 0x54, 0x00, 0x6E, 0x00, (byte) 0xAA, 0x00, 0x6E, 0x00, 0x02, /* polygon points */
+ 0x00, (byte) 0xFF, /* OpEndPic opcode; end of picture */
+ };
+
+ private static final byte[] DATA_V2 = {
+ 0x00, 0x78, /* picture size; don't use this value for picture size */
+ 0x00, 0x02, 0x00, 0x02, 0x00, 0x6E, 0x00, (byte) 0xAA, /* bounding rectangle of picture */
+ 0x00, 0x11, /* VersionOp opcode; always $0x00, 0x11, for version 2 */
+ 0x02, (byte) 0xFF, /* Version opcode; always $0x02, 0xFF, for version 2 */
+ 0x0C, 0x00, /* HeaderOp opcode; always $0C00 for version 2 */
+ /* next 24 bytes contain header information */
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, /* version; always -1 (long) for version 2 */
+ 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, (byte) 0xAA, 0x00, 0x00, 0x00, 0x6E, 0x00, 0x00, /* fixed-point bounding
+ rectangle for picture */
+ 0x00, 0x00, 0x00, 0x00, /* reserved */
+ 0x00, 0x1E, /* DefHilite opcode to use default hilite color */
+ 0x00, 0x01, /* Clip opcode to define clipping region for picture */
+ 0x00, 0x0A, /* region size */
+ 0x00, 0x02, 0x00, 0x02, 0x00, 0x6E, 0x00, (byte) 0xAA, /* bounding rectangle for clipping region */
+ 0x00, 0x0A, /* FillPat opcode; fill pattern specifed in next 8 bytes */
+ 0x77, (byte) 0xDD, 0x77, (byte) 0xDD, 0x77, (byte) 0xDD, 0x77, (byte) 0xDD, /* fill pattern */
+ 0x00, 0x34, /* fillRect opcode; rectangle specified in next 8 bytes */
+ 0x00, 0x02, 0x00, 0x02, 0x00, 0x6E, 0x00, (byte) 0xAA, /* rectangle to fill */
+ 0x00, 0x0A, /* FillPat opcode; fill pattern specified in next 8 bytes */
+ (byte) 0x88, 0x22, (byte) 0x88, 0x22, (byte) 0x88, 0x22, (byte) 0x88, 0x22, /* fill pattern */
+ 0x00, 0x5C, /* fillSameOval opcode */
+ 0x00, 0x08, /* PnMode opcode */
+ 0x00, 0x08, /* pen mode data */
+ 0x00, 0x71, /* paintPoly opcode */
+ 0x00, 0x1A, /* size of polygon */
+ 0x00, 0x02, 0x00, 0x02, 0x00, 0x6E, 0x00, (byte) 0xAA, /* bounding rectangle for polygon */
+ 0x00, 0x6E, 0x00, 0x02, 0x00, 0x02, 0x00, 0x54, 0x00, 0x6E, 0x00, (byte) 0xAA, 0x00, 0x6E, 0x00, 0x02, /* polygon points */
+ 0x00, (byte) 0xFF, /* OpEndPic opcode; end of picture */
+ };
+
+ private static final byte[] DATA_V1 = {
+ 0x00, 0x4F, /* picture size; this value is reliable for version 1 pictures */
+ 0x00, 0x02, 0x00, 0x02, 0x00, 0x6E, 0x00, (byte) 0xAA, /* bounding rectangle of picture */
+ 0x11, /* picVersion opcode for version 1 */
+ 0x01, /* version number 1 */
+ 0x01, /* ClipRgn opcode to define clipping region for picture */
+ 0x00, 0x0A, /* region size */
+ 0x00, 0x02, 0x00, 0x02, 0x00, 0x6E, 0x00, (byte) 0xAA, /* bounding rectangle for region */
+ 0x0A, /* FillPat opcode; fill pattern specified in next 8 bytes */
+ 0x77, (byte) 0xDD, 0x77, (byte) 0xDD, 0x77, (byte) 0xDD, 0x77, (byte) 0xDD, /* fill pattern */
+ 0x34, /* fillRect opcode; rectangle specified in next 8 bytes */
+ 0x00, 0x02, 0x00, 0x02, 0x00, 0x6E, 0x00, (byte) 0xAA, /* rectangle to fill */
+ 0x0A, /* FillPat opcode; fill pattern specified in next 8 bytes */
+ (byte) 0x88, 0x22, (byte) 0x88, 0x22, (byte) 0x88, 0x22, (byte) 0x88, 0x22, /* fill pattern */
+ 0x5C, /* fillSameOval opcode */
+ 0x71, /* paintPoly opcode */
+ 0x00, 0x1A, /* size of polygon */
+ 0x00, 0x02, 0x00, 0x02, 0x00, 0x6E, 0x00, (byte) 0xAA, /* bounding rectangle for polygon */
+ 0x00, 0x6E, 0x00, 0x02, 0x00, 0x02, 0x00, 0x54, 0x00, 0x6E, 0x00, (byte) 0xAA, 0x00, 0x6E, 0x00, 0x02, /* polygon points */
+ (byte) 0xFF, /* EndOfPicture opcode; end of picture */
+ };
+
+ // Examples from http://developer.apple.com/technotes/qd/qd_14.html
+ private static final byte[] DATA_V1_OVAL_RECT = {
+ 0x00, 0x26, /*size */
+ 0x00, 0x0A, 0x00, 0x14, 0x00, (byte) 0xAF, 0x00, 0x78, /* picFrame */
+ 0x11, 0x01, /* version 1 */
+ 0x01, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xFA, 0x01, (byte) 0x90, /* clipRgn -- 10 byte region */
+ 0x0B, 0x00, 0x04, 0x00, 0x05, /* ovSize point */
+ 0x40, 0x00, 0x0A, 0x00, 0x14, 0x00, (byte) 0xAF, 0x00, 0x78, /* frameRRect rectangle */
+ (byte) 0xFF, /* fin */
+ };
+
+ private static final byte[] DATA_V1_OVERPAINTED_ARC = {
+ 0x00, 0x36, /* size */
+ 0x00, 0x0A, 0x00, 0x14, 0x00, (byte) 0xAF, 0x00, 0x78, /* picFrame */
+ 0x11, 0x01, /* version 1 */
+ 0x01, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xFA, 0x01, (byte) 0x90, /* clipRgn -- 10 byte region */
+ 0x61, 0x00, 0x0A, 0x00, 0x14, 0x00, (byte) 0xAF, 0x00, 0x78, 0x00, 0x03, 0x00, 0x2D, /* paintArc rectangle,startangle,endangle */
+ 0x08, 0x00, 0x0A, /* pnMode patXor -- note that the pnMode comes before the pnPat */
+ 0x09, (byte) 0xAA, 0x55, (byte) 0xAA, 0x55, (byte) 0xAA, 0x55, (byte) 0xAA, 0x55, /* pnPat gray */
+ 0x69, 0x00, 0x03, 0x00, 0x2D, /* paintSameArc startangle,endangle */
+ (byte) 0xFF, /* fin */
+ };
+
+ private static final byte[] DATA_V1_COPY_BITS = {
+ 0x00, 0x48, /* size */
+ 0x00, 0x0A, 0x00, 0x14, 0x00, (byte) 0xAF, 0x00, 0x78, /* picFrame */
+ 0x11, 0x01, /* version 1 */
+ 0x01, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xFA, 0x01, (byte) 0x90, /* clipRgn -- 10 byte region */
+ 0x31, 0x00, 0x0A, 0x00, 0x14, 0x00, (byte) 0xAF, 0x00, 0x78, /* paintRect rectangle */
+ (byte) 0x90, 0x00, 0x02, 0x00, 0x0A, 0x00, 0x14, 0x00, 0x0F, 0x00, 0x1C, /* BitsRect rowbytes bounds (note that bounds is wider than smallr) */
+ 0x00, 0x0A, 0x00, 0x14, 0x00, 0x0F, 0x00, 0x19, /* srcRect */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x1E, /* dstRect */
+ 0x00, 0x06, /* mode=notSrcXor */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5 rows of empty bitmap (we copied from a
+ still-blank window) */
+ (byte) 0xFF, /* fin */
+ };
+}
diff --git a/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderSpi.java b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderSpi.java
new file mode 100755
index 00000000..f7529745
--- /dev/null
+++ b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderSpi.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2008, 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.pict;
+
+import javax.imageio.ImageReader;
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.stream.ImageInputStream;
+import java.io.IOException;
+import java.io.EOFException;
+import java.util.Locale;
+
+/**
+ * PICTImageReaderSpi
+ *
+ *
+ * @author Harald Kuhr
+ * @version $Id: PICTImageReaderSpi.java,v 1.0 28.feb.2006 19:21:05 haku Exp$
+ */
+public class PICTImageReaderSpi extends ImageReaderSpi {
+
+ /**
+ * Creates an PICTImageReaderSpi
+ */
+ public PICTImageReaderSpi() {
+ super(
+ "TwelveMonkeys",
+ "2.2",
+ new String[]{"pct", "PCT", "pict", "PICT"},
+ new String[]{"pct", "pict"},
+ new String[]{"image/pict", "image/x-pict"},
+ "com.twelvemkonkeys.imageio.plugins.pict.PICTImageReader",
+ STANDARD_INPUT_TYPE,
+ new String[]{"com.twelvemkonkeys.imageio.plugins.pict.PICTImageWriterSpi"},
+ true, null, null, null, null,
+ true, null, null, null, null
+ );
+ }
+
+ public boolean canDecodeInput(final Object pSource) throws IOException {
+ if (!(pSource instanceof ImageInputStream)) {
+ return false;
+ }
+
+ ImageInputStream stream = (ImageInputStream) pSource;
+
+ stream.mark();
+ try {
+ if (isPICT(stream)) {
+ // If PICT Clipping format, return true immediately
+ return true;
+ }
+ else {
+ // Skip header 512 bytes for file-based streams
+ stream.reset();
+ PICTImageReader.skipNullHeader(stream);
+ }
+ return isPICT(stream);
+ }
+ catch (EOFException ignore) {
+ return false;
+ }
+ finally {
+ stream.reset();
+ }
+ }
+
+ private boolean isPICT(final ImageInputStream pStream) throws IOException {
+ // Size may be 0, so we can't use this for validation...
+ pStream.readUnsignedShort();
+
+ // Sanity check bounding box
+ int y1 = pStream.readUnsignedShort();
+ int x1 = pStream.readUnsignedShort();
+ // TODO: Figure out if frame can ever start at negative bounds...
+ // if (x1 != 0 || y1 != 0) {
+ // return false;
+ // }
+
+ int y2 = pStream.readUnsignedShort();
+ int x2 = pStream.readUnsignedShort();
+ if (x2 - x1 < 0 || y2 - y1 < 0) {
+ return false;
+ }
+
+ int magic = pStream.readInt();
+
+ return (magic & 0xffff0000) == PICT.MAGIC_V1 || magic == PICT.MAGIC_V2;
+ }
+
+ public ImageReader createReaderInstance(final Object pExtension) throws IOException {
+ return new PICTImageReader(this);
+ }
+
+ public String getDescription(final Locale pLocale) {
+ return "Apple Mac Paint Picture (PICT) image reader";
+ }
+}
diff --git a/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageWriter.java b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageWriter.java
new file mode 100755
index 00000000..9149fb5b
--- /dev/null
+++ b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageWriter.java
@@ -0,0 +1,414 @@
+/*
+Copyright (c) 2008, 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.
+
+
+Parts of this software is based on JVG/JIS.
+See http://www.cs.hut.fi/~framling/JVG/index.html for more information.
+Redistribution under BSD authorized by Kary Främling:
+
+Copyright (c) 2003, Kary Främling
+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 of the JIS/JVG 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.pict;
+
+import com.twelvemonkeys.imageio.ImageWriterBase;
+import com.twelvemonkeys.imageio.util.IIOUtil;
+import com.twelvemonkeys.io.FastByteArrayOutputStream;
+import com.twelvemonkeys.io.enc.EncoderStream;
+import com.twelvemonkeys.io.enc.PackBitsEncoder;
+
+import javax.imageio.*;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.spi.ImageWriterSpi;
+import javax.imageio.stream.ImageOutputStream;
+import java.awt.color.ColorSpace;
+import java.awt.image.*;
+import java.io.*;
+
+/**
+ * Writer for Apple Mac Paint Picture (PICT) format.
+ *
+ * Images are stored using the "opDirectBitsRect" opcode, which directly
+ * stores RGB values (using PackBits run-length encoding).
+ *
+ * @author Kary Främling
+ * @author Harald Kuhr
+ * @version $Id: PICTWriter.java,v 1.0 05.apr.2006 15:20:48 haku Exp$
+ */
+public class PICTImageWriter extends ImageWriterBase {
+
+ // TODO: Inline these?
+ private int mRowBytes;
+ private byte[] mScanlineBytes;
+ private int mScanWidthLeft;
+
+ public PICTImageWriter() {
+ this(null);
+ }
+
+ /**
+ * Constructs an {@code ImageWriter} and sets its
+ * {@code originatingProvider} instance variable to the
+ * supplied value.
+ *
+ * Subclasses that make use of extensions should provide a
+ * constructor with signature {@code (ImageWriterSpi,
+ *Object)} in order to retrieve the extension object. If
+ * the extension object is unsuitable, an
+ * {@code IllegalArgumentException} should be thrown.
+ *
+ * @param pProvider the {@code ImageWriterSpi} that
+ * is constructing this object, or {@code null}.
+ */
+ protected PICTImageWriter(final ImageWriterSpi pProvider) {
+ super(pProvider);
+ }
+
+ private void writePICTHeader(RenderedImage pImage) throws IOException {
+ // TODO: Make 512 byte header optional
+ // Write empty 512-byte header
+ byte[] buf = new byte[PICT.PICT_NULL_HEADER_SIZE];
+ mImageOutput.write(buf);
+
+ // Write out the size, leave as 0, this is ok
+ mImageOutput.writeShort(0);
+
+ // Write image frame (same as image bounds)
+ mImageOutput.writeShort(0);
+ mImageOutput.writeShort(0);
+ mImageOutput.writeShort(pImage.getHeight());
+ mImageOutput.writeShort(pImage.getWidth());
+
+ // Write version, version 2
+ mImageOutput.writeShort(PICT.OP_VERSION);
+ mImageOutput.writeShort(PICT.OP_VERSION_2);
+
+ // Version 2 HEADER_OP, extended version.
+ mImageOutput.writeShort(PICT.OP_HEADER_OP);
+ mImageOutput.writeInt(PICT.HEADER_V2_EXT); // incl 2 bytes reseverd
+
+ // Image resolution, 72 dpi
+ mImageOutput.writeShort(PICT.MAC_DEFAULT_DPI);
+ mImageOutput.writeShort(0);
+ mImageOutput.writeShort(PICT.MAC_DEFAULT_DPI);
+ mImageOutput.writeShort(0);
+
+ // Optimal source rectangle (same as image bounds)
+ mImageOutput.writeShort(0);
+ mImageOutput.writeShort(0);
+ mImageOutput.writeShort(pImage.getHeight());
+ mImageOutput.writeShort(pImage.getWidth());
+
+ // Reserved (4 bytes)
+ mImageOutput.writeInt(0);
+
+ // TODO: The header really ends here...
+
+ // Highlight
+ mImageOutput.writeShort(PICT.OP_DEF_HILITE);
+
+ // Set the clip rectangle
+ mImageOutput.writeShort(PICT.OP_CLIP_RGN);
+ mImageOutput.writeShort(10);
+ mImageOutput.writeShort(0);
+ mImageOutput.writeShort(0);
+ mImageOutput.writeShort(pImage.getHeight());
+ mImageOutput.writeShort(pImage.getWidth());
+
+ // Pixmap operation
+ mImageOutput.writeShort(PICT.OP_DIRECT_BITS_RECT);
+
+ // PixMap pointer (always 0x000000FF);
+ mImageOutput.writeInt(0x000000ff);
+
+ // Write rowBytes, this is 4 times the width.
+ // Set the high bit, to indicate a PixMap.
+ mRowBytes = 4 * pImage.getWidth();
+ mImageOutput.writeShort(0x8000 | mRowBytes);
+
+ // Write bounds rectangle (same as image bounds)
+ mImageOutput.writeShort(0);
+ mImageOutput.writeShort(0);
+ mImageOutput.writeShort(pImage.getHeight()); // TODO: Handle overflow?
+ mImageOutput.writeShort(pImage.getWidth());
+
+ // PixMap record version
+ mImageOutput.writeShort(0);
+
+ // Packing format (always 4: PackBits)
+ mImageOutput.writeShort(4);
+
+ // Size of packed data (leave as 0)
+ mImageOutput.writeInt(0);
+
+ // Pixmap resolution, 72 dpi
+ mImageOutput.writeShort(PICT.MAC_DEFAULT_DPI);
+ mImageOutput.writeShort(0);
+ mImageOutput.writeShort(PICT.MAC_DEFAULT_DPI);
+ mImageOutput.writeShort(0);
+
+ // Pixel type, 16 is allright for direct pixels
+ mImageOutput.writeShort(16);
+
+ // Pixel size
+ mImageOutput.writeShort(32);
+
+ // TODO: Allow alpha? Allow 5 bit per pixel component (16 bit)?
+ // Pixel component count
+ mImageOutput.writeShort(3);
+
+ // Pixel component size
+ mImageOutput.writeShort(8);
+
+ // PlaneBytes, ignored for now
+ mImageOutput.writeInt(0);
+
+ // TODO: Allow IndexColorModel?
+ // ColorTable record (for RGB direct pixels, just write 0)
+ mImageOutput.writeInt(0);
+
+ // Reserved (4 bytes)
+ mImageOutput.writeInt(0);
+
+ // Source and dest rect (both are same as image bounds)
+ mImageOutput.writeShort(0);
+ mImageOutput.writeShort(0);
+ mImageOutput.writeShort(pImage.getHeight());
+ mImageOutput.writeShort(pImage.getWidth());
+
+ mImageOutput.writeShort(0);
+ mImageOutput.writeShort(0);
+ mImageOutput.writeShort(pImage.getHeight());
+ mImageOutput.writeShort(pImage.getWidth());
+
+ // Transfer mode
+ mImageOutput.writeShort(QuickDraw.SRC_COPY);
+
+ // TODO: Move to writePICTData?
+ // TODO: Alpha support
+ // Set up the buffers for storing scanline bytes
+ mScanlineBytes = new byte[3 * pImage.getWidth()];
+ mScanWidthLeft = pImage.getWidth();
+ }
+
+ private void writePICTData(int x, int y, int w, int h, ColorModel model,
+ byte[] pixels, int off, int scansize) throws IOException {
+
+ ByteArrayOutputStream bytes = new FastByteArrayOutputStream(mScanlineBytes.length / 2);
+
+ int components = model.getNumComponents();
+
+ // TODO: Clean up, as we only have complete scanlines
+
+ // Fill the scanline buffer. We get problems if ever we have several
+ // lines (h > 1) and (w < width). This should never be the case.
+ for (int i = 0; i < h; i++) {
+ // Reduce the counter of bytes left on the scanline.
+ mScanWidthLeft -= w;
+
+ // Treat the scanline.
+ for (int j = 0; j < w; j++) {
+ if (model instanceof ComponentColorModel && model.getColorSpace().getType() == ColorSpace.TYPE_RGB) {
+ // TODO: Component order?
+ // TODO: Alpha support
+ mScanlineBytes[x + j] = pixels[off + i * scansize * components + components * j + 2];
+ mScanlineBytes[x + w + j] = pixels[off + i * scansize * components + components * j + 1];
+ mScanlineBytes[x + 2 * w + j] = pixels[off + i * scansize * components + components * j];
+ }
+ else {
+ int rgb = model.getRGB(pixels[off + i * scansize + j] & 0xFF);
+ // Set red, green and blue components.
+ mScanlineBytes[x + j] = (byte) ((rgb >> 16) & 0xFF);
+ mScanlineBytes[x + w + j] = (byte) ((rgb >> 8) & 0xFF);
+ mScanlineBytes[x + 2 * w + j] = (byte) (rgb & 0xFF);
+ }
+
+ }
+
+ // If we have a complete scanline, then pack it and write it out.
+ if (mScanWidthLeft == 0) {
+ // Pack using PackBitsEncoder/EncoderStream
+ bytes.reset();
+ DataOutput packBits = new DataOutputStream(new EncoderStream(bytes, new PackBitsEncoder(), true));
+
+ packBits.write(mScanlineBytes);
+
+ if (mRowBytes > 250) {
+ mImageOutput.writeShort(bytes.size());
+ }
+ else {
+ mImageOutput.writeByte(bytes.size());
+ }
+
+ bytes.writeTo(IIOUtil.createStreamAdapter(mImageOutput));
+
+ mScanWidthLeft = w;
+ }
+ }
+ }
+
+ private void writePICTData(int x, int y, int w, int h, ColorModel model,
+ int[] pixels, int off, int scansize) throws IOException {
+
+ ByteArrayOutputStream bytes = new FastByteArrayOutputStream(mScanlineBytes.length / 2);
+
+ // TODO: Clean up, as we only have complete scanlines
+
+ // Fill the scanline buffer. We get problems if ever we have several
+ // lines (h > 1) and (w < width). This should never be the case.
+ for (int i = 0; i < h; i++) {
+ // Reduce the counter of bytes left on the scanline.
+ mScanWidthLeft -= w;
+
+ // Treat the scanline.
+ for (int j = 0; j < w; j++) {
+ int rgb = model.getRGB(pixels[off + i * scansize + j]);
+
+ // Set red, green and blue components.
+ mScanlineBytes[x + j] = (byte) ((rgb >> 16) & 0xFF);
+ mScanlineBytes[x + w + j] = (byte) ((rgb >> 8) & 0xFF);
+ mScanlineBytes[x + 2 * w + j] = (byte) (rgb & 0xFF);
+ }
+
+ // If we have a complete scanline, then pack it and write it out.
+ if (mScanWidthLeft == 0) {
+ // Pack using PackBitsEncoder/EncoderStream
+ bytes.reset();
+ DataOutput packBits = new DataOutputStream(new EncoderStream(bytes, new PackBitsEncoder(), true));
+
+ packBits.write(mScanlineBytes);
+
+ if (mRowBytes > 250) {
+ mImageOutput.writeShort(bytes.size());
+ }
+ else {
+ mImageOutput.writeByte(bytes.size());
+ }
+
+ bytes.writeTo(IIOUtil.createStreamAdapter(mImageOutput));
+
+ mScanWidthLeft = w;
+ }
+ }
+ }
+
+ private void writePICTTrailer() throws IOException {
+ // Write out end opcode. Be sure to be word-aligned.
+ long length = mImageOutput.length();
+ if (length == -1) {
+ throw new IIOException("Cannot write trailer without knowing length");
+ }
+
+ if ((length & 1) > 0) {
+ mImageOutput.writeByte(0);
+ }
+ mImageOutput.writeShort(PICT.OP_END_OF_PICTURE);
+ }
+
+ public void write(IIOMetadata pStreamMetadata, IIOImage pImage, ImageWriteParam pParam) throws IOException {
+ assertOutput();
+
+ if (pImage.hasRaster()) {
+ throw new UnsupportedOperationException("Cannot write raster");
+ }
+
+ processImageStarted(0);
+
+ RenderedImage image = pImage.getRenderedImage();
+ writePICTHeader(image);
+
+ // NOTE: getRaster is much faster than getData, as it does no copying
+ Raster raster = image instanceof BufferedImage ? ((BufferedImage) image).getRaster() : image.getData();
+ DataBuffer buf = raster.getDataBuffer();
+ if (buf instanceof DataBufferByte) {
+ writePICTData(0, 0, image.getWidth(), image.getHeight(),
+ image.getColorModel(), ((DataBufferByte) buf).getData(),
+ 0, image.getWidth());
+ }
+ else if (buf instanceof DataBufferInt) {
+ writePICTData(0, 0, image.getWidth(), image.getHeight(),
+ image.getColorModel(), ((DataBufferInt) buf).getData(),
+ 0, image.getWidth());
+ }
+ else {
+ throw new IIOException("DataBuffer type " + buf.getDataType() + " not supported");
+ }
+ // TODO: Support 16 bit USHORT type
+
+ writePICTTrailer();
+
+ processImageComplete();
+ }
+
+ public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param) {
+ throw new UnsupportedOperationException("Method getDefaultImageMetadata not implemented");// TODO: Implement
+ }
+
+ public IIOMetadata convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType, ImageWriteParam param) {
+ throw new UnsupportedOperationException("Method convertImageMetadata not implemented");// TODO: Implement
+ }
+
+ public static void main(String[] pArgs) throws IOException {
+ System.out.print("Reading image.. ");
+
+ BufferedImage image = ImageIO.read(new File(pArgs[0]));
+
+ System.out.println("image read");
+
+ System.out.println("image: " + image);
+
+ ImageWriter writer = new PICTImageWriter(null);
+ ImageOutputStream stream = ImageIO.createImageOutputStream(new File(pArgs[1]));
+ writer.setOutput(stream);
+ writer.write(image);
+ stream.close();
+ }
+}
diff --git a/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageWriterSpi.java b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageWriterSpi.java
new file mode 100755
index 00000000..b349c56b
--- /dev/null
+++ b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageWriterSpi.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2008, 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.pict;
+
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.ImageWriter;
+import javax.imageio.spi.ImageWriterSpi;
+import java.io.IOException;
+import java.util.Locale;
+
+/**
+ * PICTImageWriterSpi
+ *
+ *
+ * @author Harald Kuhr
+ * @version $Id: PICTImageWriterSpi.java,v 1.0 02.mar.2006 19:21:05 haku Exp$
+ */
+public class PICTImageWriterSpi extends ImageWriterSpi {
+
+ /**
+ * Creates an PICTImageWriterSpi
+ */
+ public PICTImageWriterSpi() {
+ super(
+ "TwelveMonkeys",
+ "2.0",
+ new String[]{"pct", "PCT",
+ "pict", "PICT"},
+ new String[]{"pct", "pict"},
+ new String[]{"image/pict", "image/x-pict"},
+ "com.twelvemonkeys.imageio.plugins.pict.PICTImageWriter",
+ STANDARD_OUTPUT_TYPE,
+ new String[]{"com.twelvemonkeys.imageio.plugins.pict.PICTImageReaderSpi"},
+ true, null, null, null, null,
+ true, null, null, null, null
+ );
+ }
+
+ public boolean canEncodeImage(ImageTypeSpecifier pType) {
+ // TODO: FixMe
+ return true;
+ }
+
+ public ImageWriter createWriterInstance(Object pExtension) throws IOException {
+ return new PICTImageWriter();
+ }
+
+ public String getDescription(Locale pLocale) {
+ return "Apple Mac Paint Picture (PICT) image writer";
+ }
+}
diff --git a/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTUtil.java b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTUtil.java
new file mode 100755
index 00000000..ef503715
--- /dev/null
+++ b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTUtil.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (c) 2008, 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.pict;
+
+import javax.imageio.IIOException;
+import java.awt.*;
+import java.awt.image.DataBuffer;
+import java.awt.image.IndexColorModel;
+import java.io.DataInput;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+/**
+ * PICTUtil
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: PICTUtil.java,v 1.0 Feb 16, 2009 8:46:27 PM haraldk Exp$
+ */
+final class PICTUtil {
+
+ private static final String ENC_MAC_ROMAN = "MacRoman";
+
+ public static final String ENCODING = initEncoding();
+
+ private static String initEncoding() {
+ try {
+ new String("\uF8FF".getBytes(), ENC_MAC_ROMAN);
+ return ENC_MAC_ROMAN;
+ }
+ catch (UnsupportedEncodingException e) {
+ return "ISO-8859-1";
+ }
+ }
+
+ /**
+ * Reads a fixed point number from the given stream.
+ *
+ * @param pStream the input stream
+ * @return the number as a {@code double}.
+ *
+ * @throws java.io.IOException if an I/O error occurs during read
+ */
+ public static double readFixedPoint(final DataInput pStream) throws IOException {
+ return pStream.readInt() / (double) (1 << 16);
+ }
+
+ static String readIdString(final DataInput pStream) throws IOException {
+ byte[] bytes = new byte[4];
+ pStream.readFully(bytes);
+ return new String(bytes, "ASCII");
+ }
+
+ /**
+ * Reads a dimension from the given stream.
+ *
+ * @param pStream the input stream
+ * @return the dimension read
+ *
+ * @throws java.io.IOException if an I/O error occurs during read
+ */
+ public static Dimension readDimension(final DataInput pStream) throws IOException {
+ final int h = pStream.readShort() ;
+ final int v = pStream.readShort() ;
+ return new Dimension(h,v);
+ }
+
+ /**
+ * Reads a 32 byte fixed length Pascal string from the given input.
+ * The input stream must be positioned at the length byte of the text,
+ * the text will be no longer than 31 characters long.
+ *
+ * @param pStream the input stream
+ * @return the text read
+ *
+ * @throws IOException if an I/O exception occurs during reading
+ */
+ public static String readStr31(final DataInput pStream) throws IOException {
+ String text = readPascalString(pStream);
+ int length = 31 - text.length();
+ if (length < 0) {
+ throw new IOException("String length exceeds maximum (31): " + text.length());
+ }
+ pStream.skipBytes(length);
+ return text;
+ }
+
+ /**
+ * Reads a Pascal String from the given strean.
+ * The input stream must be positioned at the length byte of the text,
+ * which can thus be a maximum of 255 characters long.
+ *
+ * @param pStream the input stream
+ * @return the text read
+ *
+ * @throws IOException if an I/O exception occurs during reading
+ */
+ public static String readPascalString(final DataInput pStream) throws IOException {
+ // Get as many bytes as indicated by byte count
+ int length = pStream.readUnsignedByte();
+
+ byte[] bytes = new byte[length];
+ pStream.readFully(bytes, 0, length);
+
+ return new String(bytes, ENCODING);
+ }
+
+ /**
+ * Reads a {@link Pattern pattern} from the given stream.
+ *
+ * @param pStream the input stream
+ * @return the pattern read
+ *
+ * @throws java.io.IOException if an I/O error occurs during read
+ */
+ public static Pattern readPattern(final DataInput pStream) throws IOException {
+ // Get the data (8 bytes)
+ byte[] data = new byte[8];
+ pStream.readFully(data);
+ return new BitMapPattern(data);
+ }
+
+ /**
+ * Reads a variable width {@link Pattern color pattern} from the given stream
+ *
+ * @param pStream the input stream
+ * @return the pattern read
+ *
+ * @throws java.io.IOException if an I/O error occurs during read
+ */
+ /*
+ http://developer.apple.com/DOCUMENTATION/mac/QuickDraw/QuickDraw-461.html#MARKER-9-243
+ IF patType = ditherPat
+ THEN
+ PatType: word; {pattern type = 2}
+ Pat1Data: Pattern; {old pattern data}
+ RGB: RGBColor; {desired RGB for pattern}
+ ELSE
+ PatType: word; {pattern type = 1}
+ Pat1Data: Pattern; {old pattern data}
+ PixMap: PixMap;
+ ColorTable: ColorTable;
+ PixData: PixData;
+ END;
+ */
+ public static Pattern readColorPattern(final DataInput pStream) throws IOException {
+ short type = pStream.readShort();
+
+ Pattern pattern;
+ Pattern fallback = readPattern(pStream);
+
+ if (type == 1) {
+ // TODO: This is foobar...
+ // PixMap
+ // ColorTable
+ // PixData
+ throw new IIOException(String.format("QuickDraw pattern type '0x%04x' not implemented (yet)", type));
+ }
+ else if (type == 2) {
+ Color color = readRGBColor(pStream);
+ pattern = new PixMapPattern(color, fallback);
+ }
+ else {
+ throw new IIOException(String.format("Unknown QuickDraw pattern type '0x%04x'", type));
+ }
+
+ return pattern;
+ }
+
+ /**
+ * Reads an {@link RGBColor} record from the given stream.
+ *
+ * @param pStream the input stream
+ * @return the color read
+ *
+ * @throws java.io.IOException if an I/O error occurs during read
+ */
+ /*
+ http://developer.apple.com/DOCUMENTATION/mac/QuickDraw/QuickDraw-269.html#HEADING269-11
+ RGBColor =
+ RECORD
+ red: Integer; {red component}
+ green: Integer; {green component}
+ blue: Integer; {blue component}
+ END;
+ */
+ public static Color readRGBColor(final DataInput pStream) throws IOException {
+ short r = pStream.readShort();
+ short g = pStream.readShort();
+ short b = pStream.readShort();
+
+ return new RGBColor(r, g, b);
+ }
+
+ /**
+ * Reads a {@code ColorTable} data structure from the given stream.
+ *
+ * @param pStream the input stream
+ * @param pPixelSize the pixel size
+ * @return the indexed color model created from the {@code ColorSpec} records read.
+ *
+ * @throws java.io.IOException if an I/O error occurs during read
+ */
+ /*
+ http://developer.apple.com/DOCUMENTATION/mac/QuickDraw/QuickDraw-269.html#HEADING269-11
+ ColorSpec =
+ RECORD
+ value: Integer; {index or other value}
+ rgb: RGBColor; {true color}
+ END;
+
+ ColorTable =
+ RECORD
+ ctSeed: LongInt; {unique identifier from table}
+ ctFlags: Integer; {contains flags describing the ctTable field; }
+ { clear for a PixMap record}
+ ctSize: Integer; {number of entries in the next field minus 1}
+ ctTable: cSpecArray; {an array of ColorSpec records}
+ END;
+ */
+ public static IndexColorModel readColorTable(final DataInput pStream, final int pPixelSize) throws IOException {
+ // TODO: Do we need to support these?
+ /*int seed = */pStream.readInt();
+ /*int flags = */pStream.readUnsignedShort();
+ int size = pStream.readUnsignedShort() + 1; // data is size - 1
+
+ int[] colors = new int[size];
+
+ for (int i = 0; i < size ; i++) {
+ // Read ColorSpec records
+ int index = pStream.readUnsignedShort();
+ Color color = readRGBColor(pStream);
+ colors[index] = color.getRGB();
+ }
+
+ return new IndexColorModel(pPixelSize, size, colors, 0, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
+ }
+}
diff --git a/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/Pattern.java b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/Pattern.java
new file mode 100755
index 00000000..d7d073d0
--- /dev/null
+++ b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/Pattern.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2008, 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.pict;
+
+import java.awt.*;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.AffineTransform;
+import java.awt.image.*;
+import java.util.Collections;
+
+/**
+ * Pattern
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: Pattern.java,v 1.0 Oct 9, 2007 1:21:38 AM haraldk Exp$
+ */
+abstract class Pattern implements Paint {
+ private final Paint mPaint;
+
+ Pattern(final Paint pPaint) {
+ mPaint = pPaint;
+ }
+
+ public PaintContext createContext(final ColorModel pModel, final Rectangle pDeviceBounds,
+ final Rectangle2D pUserBounds, final AffineTransform pTransform,
+ final RenderingHints pHints) {
+ return mPaint.createContext(
+ pModel, pDeviceBounds,
+ pUserBounds, pTransform,
+ pHints != null ? pHints : new RenderingHints(Collections.emptyMap())
+ );
+ }
+
+ public int getTransparency() {
+ return mPaint.getTransparency();
+ }
+}
diff --git a/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PenState.java b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PenState.java
new file mode 100755
index 00000000..8fd251f0
--- /dev/null
+++ b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PenState.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2008, 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.pict;
+
+import java.awt.*;
+
+/**
+ * PenState
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: PenState.java,v 1.0 Oct 9, 2007 1:56:33 AM haraldk Exp$
+ */
+class PenState {
+ public final Point mPenLocation; /* pen location */
+ public final Dimension mPenSize; /* pen size */
+ public final int mPenMode; /* pen's pattern mode */
+ public final Pattern mPenPattern; /* pen pattern */
+
+ public PenState(final Point pPenLocation, final int pPenMode, final Pattern pPenPattern, final Dimension pPenSize) {
+ mPenLocation = pPenLocation;
+ mPenMode = pPenMode;
+ mPenPattern = pPenPattern;
+ mPenSize = pPenSize;
+ }
+}
diff --git a/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PixMapPattern.java b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PixMapPattern.java
new file mode 100755
index 00000000..470a04a0
--- /dev/null
+++ b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PixMapPattern.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2008, 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.pict;
+
+import java.awt.*;
+
+/**
+ * PixMapPattern
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: PixMapPattern.java,v 1.0 Mar 1, 2009 11:36:10 PM haraldk Exp$
+ */
+final class PixMapPattern extends Pattern {
+ private final Pattern mFallback;
+
+ PixMapPattern(final Paint pPaint, final Pattern pBitMapFallback) {
+ super(pPaint);
+ mFallback = pBitMapFallback;
+ }
+
+ /**
+ * @return the fallback B/W pattern
+ */
+ public Pattern getPattern() {
+ return mFallback;
+ }
+}
diff --git a/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QDTest.java b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QDTest.java
new file mode 100755
index 00000000..a58cbeeb
--- /dev/null
+++ b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QDTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2008, 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.pict;
+
+import com.twelvemonkeys.image.ConvolveTester;
+
+import java.awt.image.BufferedImage;
+import java.awt.*;
+
+/**
+ * QDTest
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: QDTest.java,v 1.0 Oct 10, 2007 6:06:55 PM haraldk Exp$
+ */
+public class QDTest {
+ public static void main(String[] pArgs) {
+ BufferedImage image = new BufferedImage(200, 200, BufferedImage.TYPE_INT_ARGB);
+
+ Graphics2D g = image.createGraphics();
+// g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ QuickDrawContext context = new QuickDrawContext(g);
+ try {
+ // Fill background, as Xor don't work with transparent bg
+ context.fillRect(new Rectangle(200, 200), QuickDraw.WHITE);
+
+ context.moveTo(10, 10);
+ context.lineTo(10, 190);
+ context.lineTo(190, 190);
+ context.lineTo(190, 10);
+ context.lineTo(10, 10);
+
+ context.moveTo(10, 10);
+ context.lineTo(190, 190);
+
+ context.setPenSize(new Dimension(2, 2));
+ context.frameRect(new Rectangle(15, 15, 20, 20));
+ context.paintRect(new Rectangle(15, 45, 20, 20));
+ context.fillRect(new Rectangle(15, 75, 20, 20), QuickDraw.DARK_GRAY);
+ context.fillRect(new Rectangle(12, 102, 26, 26), new BitMapPattern(Color.GRAY));
+ context.eraseRect(new Rectangle(15, 105, 20, 20));
+ context.fillRect(new Rectangle(12, 132, 26, 8), QuickDraw.LIGT_GRAY);
+ context.fillRect(new Rectangle(12, 140, 26, 10), new BitMapPattern(Color.RED));
+ context.fillRect(new Rectangle(12, 150, 26, 8), QuickDraw.DARK_GRAY);
+ context.invertRect(new Rectangle(15, 135, 20, 20));
+
+ context.setPenSize(new Dimension(10, 10));
+ context.moveTo(80, 30);
+ context.line(80, 20);
+ context.move(20, 0);
+ context.line(0, -25);
+
+ context.setPenPattern(QuickDraw.GRAY);
+ context.moveTo(80, 70);
+ context.line(80, 20);
+ context.move(20, 0);
+ context.line(0, -25);
+
+ context.setPenPattern(new BitMapPattern(Color.GRAY));
+ context.moveTo(80, 110);
+ context.line(80, 20);
+ context.move(20, 0);
+ context.line(0, -25);
+
+ context.setPenPattern(new BitMapPattern(Color.RED));
+ context.moveTo(80, 150);
+ context.line(80, 20);
+ context.move(20, 0);
+ context.line(0, -25);
+
+ context.setPenPattern(new BitMapPattern(Color.ORANGE));
+ context.setPenSize(new Dimension(2, 2));
+ context.frameRoundRect(new Rectangle(45, 15, 20, 20), 4, 4);
+ context.setPenPattern(new BitMapPattern(Color.DARK_GRAY));
+ context.paintOval(new Rectangle(45, 45, 20, 20));
+ context.invertArc(new Rectangle(45 + 1, 45, 20, 20), 45, 90);
+ context.frameArc(new Rectangle(45 - 1, 75, 20, 20), 45, -270);
+ context.fillArc(new Rectangle(45 + 1, 75, 20, 20), 45, 90, new BitMapPattern(Color.RED));
+
+ context.invertPoly(new Polygon(new int[]{43, 55, 67}, new int[]{125, 103, 125}, 3));
+ context.setPenPattern(new BitMapPattern(Color.ORANGE));
+ Polygon star = new Polygon(
+ new int[]{43, 52, 55, 58, 68, 59, 63, 55, 47, 51},
+ new int[]{143, 143, 133, 143, 143, 148, 157, 152, 157, 148},
+ 10
+ );
+ context.paintPoly(star);
+ context.setPenNormal();
+ context.framePoly(star);
+
+ // TODO: FixMe: Seems like rectangle should be INSIDE? Or at least, one pixel less than AWT thinks..
+// context.frameRoundRect(new Rectangle(20, 10, 100, 165), 5, 4);
+
+ context.moveTo(15, 185);
+ context.drawString("Java QuickDraw test");
+ }
+ finally {
+ context.closePicture();
+ }
+
+ ConvolveTester.showIt(image, "QuickDraw Test");
+ }
+}
diff --git a/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QTBMPDecompressor.java b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QTBMPDecompressor.java
new file mode 100755
index 00000000..c6e0d7dc
--- /dev/null
+++ b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QTBMPDecompressor.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2008, 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.pict;
+
+import com.twelvemonkeys.io.FastByteArrayOutputStream;
+import com.twelvemonkeys.io.LittleEndianDataOutputStream;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.*;
+
+/**
+ * QTBMPDecompressor
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: QTBMPDecompressor.java,v 1.0 Feb 16, 2009 9:18:28 PM haraldk Exp$
+ */
+final class QTBMPDecompressor extends QTDecompressor {
+
+ public boolean canDecompress(final QuickTime.ImageDesc pDescription) {
+ return QuickTime.VENDOR_APPLE.equals(pDescription.compressorVendor) && "WRLE".equals(pDescription.compressorIdentifer)
+ && "bmp ".equals(idString(pDescription.extraDesc, 4));
+ }
+
+ private static String idString(final byte[] pData, final int pOffset) {
+ try {
+ return new String(pData, pOffset, 4, "ASCII");
+ }
+ catch (UnsupportedEncodingException e) {
+ throw new Error("ASCII charset must always be supported", e);
+ }
+ }
+
+ public BufferedImage decompress(final QuickTime.ImageDesc pDescription, final InputStream pStream) throws IOException {
+ return ImageIO.read(new SequenceInputStream(fakeBMPHeader(pDescription), pStream));
+ }
+
+ private InputStream fakeBMPHeader(final QuickTime.ImageDesc pDescription) throws IOException {
+ int bmpHeaderSize = 14;
+ int dibHeaderSize = 12; // 12: OS/2 V1
+ ByteArrayOutputStream out = new FastByteArrayOutputStream(bmpHeaderSize + dibHeaderSize);
+
+ LittleEndianDataOutputStream stream = new LittleEndianDataOutputStream(out);
+
+ // BMP header
+ stream.writeByte('B');
+ stream.writeByte('M');
+
+ stream.writeInt(pDescription.dataSize + bmpHeaderSize + dibHeaderSize); // Data size + BMP header + DIB header
+
+ stream.writeShort(0x0); // Reserved
+ stream.writeShort(0x0); // Reserved
+
+ stream.writeInt(bmpHeaderSize + dibHeaderSize); // Image offset
+
+ // DIB header
+ stream.writeInt(dibHeaderSize); // DIB header size
+
+ stream.writeShort(pDescription.width);
+ stream.writeShort(pDescription.height);
+
+ stream.writeShort(1); // Planes, only legal value: 1
+ stream.writeShort(pDescription.depth); // Bit depth
+
+ return new ByteArrayInputStream(out.toByteArray());
+ }
+}
diff --git a/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QTDecompressor.java b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QTDecompressor.java
new file mode 100755
index 00000000..502276cf
--- /dev/null
+++ b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QTDecompressor.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2008, 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.pict;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Abstract base class for a stateless image decompressor, for use by {@link QuickTime}.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: QTDecompressor.java,v 1.0 Feb 16, 2009 7:21:27 PM haraldk Exp$
+ */
+abstract class QTDecompressor {
+
+ /**
+ * Returns wether this decompressor is capable of decompressing the image
+ * data described by the given image description.
+ *
+ * @param pDescription the image description ({@code 'idsc'} Atom).
+ * @return {@code true} if this decompressor is capable of decompressing
+ * he data in the given image description, otherwise {@code false}.
+ */
+ public abstract boolean canDecompress(QuickTime.ImageDesc pDescription);
+
+ /**
+ * Decompresses an image.
+ *
+ * @param pDescription the image description ({@code 'idsc'} Atom).
+ * @param pStream the image data stream
+ * @return the decompressed image
+ *
+ * @throws java.io.IOException if an I/O exception occurs during reading.
+ */
+ public abstract BufferedImage decompress(QuickTime.ImageDesc pDescription, InputStream pStream) throws IOException;
+}
diff --git a/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QTGenericDecompressor.java b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QTGenericDecompressor.java
new file mode 100755
index 00000000..74b71bbf
--- /dev/null
+++ b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QTGenericDecompressor.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2008, 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.pict;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * QTGenericDecompressor
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: QTGenericDecompressor.java,v 1.0 Feb 16, 2009 9:26:13 PM haraldk Exp$
+ */
+final class QTGenericDecompressor extends QTDecompressor {
+ public boolean canDecompress(final QuickTime.ImageDesc pDescription) {
+ return true;
+ }
+
+ public BufferedImage decompress(final QuickTime.ImageDesc pDescription, final InputStream pStream) throws IOException {
+ return ImageIO.read(pStream);
+ }
+}
diff --git a/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QTRAWDecompressor.java b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QTRAWDecompressor.java
new file mode 100755
index 00000000..3c026c97
--- /dev/null
+++ b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QTRAWDecompressor.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2008, 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.pict;
+
+import javax.imageio.IIOException;
+import java.awt.*;
+import java.awt.color.ColorSpace;
+import java.awt.image.*;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * QTRAWDecompressor
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: QTRAWDecompressor.java,v 1.0 Feb 16, 2009 9:29:18 PM haraldk Exp$
+ */
+final class QTRAWDecompressor extends QTDecompressor {
+ // TODO: Create a RAWImageReader for ImageIO to delegate to?
+ // - Would have to require a parameter controlling bit depth and pixel layout
+ // - Have a look at com.sun.media.imageio.stream.RawImageInputStream...
+ // TODO: Support different bit depths
+
+ public boolean canDecompress(final QuickTime.ImageDesc pDescription) {
+ return QuickTime.VENDOR_APPLE.equals(pDescription.compressorVendor)
+ && "raw ".equals(pDescription.compressorIdentifer)
+ && (pDescription.depth == 24 || pDescription.depth == 32);
+ }
+
+ public BufferedImage decompress(final QuickTime.ImageDesc pDescription, final InputStream pStream) throws IOException {
+ DataInputStream stream = new DataInputStream(pStream);
+
+ byte[] data = new byte[pDescription.dataSize];
+ stream.readFully(data, 0, pDescription.dataSize);
+ stream.close();
+
+ DataBuffer buffer = new DataBufferByte(data, data.length);
+
+ WritableRaster raster;
+ // TODO: Depth parameter can be 1-32 (color) or 33-40 (grayscale)
+ switch (pDescription.depth) {
+ case 24:
+ raster = Raster.createInterleavedRaster(buffer, pDescription.width, pDescription.height, pDescription.width * 3, 3, new int[] {0, 1, 2}, null);
+ break;
+ case 32:
+ raster = Raster.createInterleavedRaster(buffer, pDescription.width, pDescription.height, pDescription.width * 4, 4, new int[] {1, 2, 3, 0}, null);
+ break;
+ default:
+ throw new IIOException("Unsupported RAW depth: " + pDescription.depth);
+ }
+
+ ColorModel cm = new ComponentColorModel(
+ ColorSpace.getInstance(ColorSpace.CS_sRGB),
+ pDescription.depth == 32,
+ false,
+ Transparency.TRANSLUCENT,
+ DataBuffer.TYPE_BYTE
+ );
+
+ return new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
+ }
+}
diff --git a/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QuickDraw.java b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QuickDraw.java
new file mode 100755
index 00000000..1c6809a3
--- /dev/null
+++ b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QuickDraw.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2008, 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.pict;
+
+import java.awt.image.*;
+import java.awt.*;
+
+/**
+ * QuickDraw constants.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: QuickDraw.java,v 1.0 Oct 9, 2007 1:15:09 AM haraldk Exp$
+ */
+interface QuickDraw {
+ // Reversed from default BufferedImage.TYPE_BYTE_BINARY
+ IndexColorModel MONOCHROME = new IndexColorModel(1, 2, new int[] {0xffffff, 0x00000000}, 0, false, -1, DataBuffer.TYPE_BYTE);
+
+ /** QuickDraw {@code white} pattern */
+ Pattern WHITE = new BitMapPattern(Color.WHITE);
+
+ /** QuickDraw {@code black} pattern */
+ Pattern BLACK = new BitMapPattern(Color.BLACK);
+
+ /** QuickDraw {@code gray} pattern */
+ Pattern GRAY = new BitMapPattern(0xAA55AA55);
+
+ /** QuickDraw {@code ltGray} pattern */
+ Pattern LIGT_GRAY = new BitMapPattern(0x88228822);
+
+ /** QuickDraw {@code dkGray} pattern */
+ Pattern DARK_GRAY = new BitMapPattern(0x77DD77DD);
+
+ // Boolean Transfer modes.
+ // http://developer.apple.com/documentation/mac/quickdraw/QuickDraw-196.html#HEADING196-2
+ // http://developer.apple.com/documentation/mac/quickdraw/QuickDraw-269.html#HEADING269-2
+ // See http://developer.apple.com/documentation/mac/quickdraw/QuickDraw-199.html#HEADING199-76 for color!
+ int SRC_COPY = 0;
+ int SRC_OR = 1;
+ int SRC_XOR = 2;
+ int SRC_BIC = 3;
+ int NOT_SRC_COPY = 4;
+ int NOT_SRC_OR = 5;
+ int NOT_SRC_XOR = 6;
+ int NOT_SRC_BIC = 7;
+
+ int PAT_COPY = 8;
+ int PAT_OR = 9;
+ int PAT_XOR = 10;
+ int PAT_BIC = 11;
+ int NOT_PAT_COPY = 12;
+ int NOT_PAT_OR = 13;
+ int NOT_PAT_XOR = 14;
+ int NOT_PAT_BIC = 15;
+
+ int DITHER_COPY = 64; // Add to src mode for dither
+ int HILITE = 50; // Add to src or pattern mode for highlight
+
+ // Arithmetic Transfer Modes
+ // http://developer.apple.com/documentation/mac/quickdraw/QuickDraw-199.html#HEADING199-112
+ int BLEND = 32; // dest = source weight/65,535 + destination (1 - weight/65,535)
+ int ADD_PIN = 33;
+ int ADD_OVER = 34;
+ int SUB_PIN = 35;
+ int TRANSPARENT = 36;
+ int AD_MAX = 37;
+ int SUB_OVER = 38;
+ int AD_MIN = 39;
+ int GRAYISH_TEXT_OR = 49;
+// int MASK = 64; // ?! From Käry's code..
+
+ /*
+ * Text face masks.
+ */
+ int TX_BOLD_MASK = 1;
+ int TX_ITALIC_MASK = 2;
+ int TX_UNDERLINE_MASK = 4;
+ int TX_OUTLINE_MASK = 8;
+ int TX_SHADOWED_MASK = 16;
+ int TX_CONDENSED_MASK = 32;
+ int TX_EXTENDED_MASK = 64;
+}
diff --git a/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QuickDrawContext.java b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QuickDrawContext.java
new file mode 100755
index 00000000..92d504d0
--- /dev/null
+++ b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QuickDrawContext.java
@@ -0,0 +1,1053 @@
+/*
+ * Copyright (c) 2008, 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.pict;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.awt.geom.*;
+
+/**
+ * Emulates an Apple QuickDraw rendering context, backed by a Java {@link Graphics2D}.
+ *
+ *
+ * @author Harald Kuhr
+ * @version $Id: QuickDrawContext.java,v 1.0 Oct 3, 2007 1:24:35 AM haraldk Exp$
+ */
+// TODO: It would actually be possible to implement a version of this interface
+// that wrote opcodes/data to a stream... Or maybe a QDGraphics would be better.
+// TODO: Optimize for pensize 1,1?
+// TODO: Do we really need the Xxx2D stuff?
+// TODO: Support COPY_DITHER
+class QuickDrawContext {
+
+ /*
+ // The useful parts of the QD Graphics Port:
+ portRect: Rect; {port rectangle}
+ visRgn: RgnHandle; {visible region}
+ clipRgn: RgnHandle; {clipping region}
+ bkPat: Pattern; {background pattern}
+ fillPat: Pattern; {fill pattern}
+ pnLoc: Point; {pen location}
+ pnSize: Point; {pen size}
+ pnMode: Integer; {pattern mode}
+ pnPat: Pattern; {pen pattern}
+ pnVis: Integer; {pen visibility}
+ txFont: Integer; {font number for text}
+ txFace: Style; {text's font style}
+ txMode: Integer; {source mode for text}
+ txSize: Integer; {font size for text}
+ spExtra: Fixed; {extra space}
+ fgColor: LongInt; {foreground color}
+ bkColor: LongInt; {background color}
+ colrBit: Integer; {color bit}
+ ..
+ picSave: Handle; {picture being saved, used internally}
+ rgnSave: Handle; {region being saved, used internally}
+ polySave: Handle; {polygon being saved, used internally}
+ */
+
+ /*
+ // Color Graphics Port;
+ chExtra: Integer; {added width for nonspace characters}
+ pnLocHFrac: Integer; {pen fraction}
+ portRect: Rect; {port rectangle}
+ visRgn: RgnHandle; {visible region}
+ clipRgn: RgnHandle; {clipping region}
+ bkPixPat: PixPatHandle; {background pattern}
+ rgbFgColor: RGBColor; {requested foreground color}
+ rgbBkColor: RGBColor; {requested background color}
+ pnLoc: Point; {pen location}
+ pnSize: Point; {pen size}
+ pnMode: Integer; {pattern mode}
+ pnPixPat: PixPatHandle; {pen pattern}
+ fillPixPat: PixPatHandle; {fill pattern}
+ pnVis: Integer; {pen visibility}
+ txFont: Integer; {font number for text}
+ txFace: Style; {text's font style}
+ txMode: Integer; {source mode for text}
+ txSize: Integer; {font size for text}
+ spExtra: Fixed; {added width for space characters}
+ fgColor: LongInt; {actual foreground color}
+ bkColor: LongInt; {actual background color}
+ colrBit: Integer; {plane being drawn}
+ ..
+ picSave: Handle; {picture being saved, used internally}
+ rgnSave: Handle; {region being saved, used internally}
+ polySave: Handle; {polygon being saved, used internally}
+ */
+ private final Graphics2D mGraphics;
+
+ private Pattern mBackground;
+
+ // http://developer.apple.com/documentation/mac/quickdraw/QuickDraw-68.html#HEADING68-0
+ // Upon the creation of a graphics port, QuickDraw assigns these initial
+ // values to the graphics pen: a size of (1,1), a pattern of all-black pixels,
+ // and the patCopy pattern mode. After changing any of these values,
+ // you can use the PenNormal procedure to return these initial values to the
+ // graphics pen.
+
+ // TODO: Consider creating a Pen/PenState class?
+ private int mPenVisibility = 0;
+ private Point2D mPenPosition = new Point();
+ private Pattern mPenPattern;
+ private Dimension2D mPenSize = new Dimension();
+ private int mPenMode;
+
+ QuickDrawContext(Graphics2D pGraphics) {
+ if (pGraphics == null) {
+ throw new IllegalArgumentException("graphics == null");
+ }
+ mGraphics = pGraphics;
+
+ setPenNormal();
+ }
+
+ protected void dispose() {
+ mGraphics.dispose();
+ }
+
+ // ClosePicture
+ public void closePicture() {
+ dispose();
+ }
+
+ // ClipRgn
+ public void setClipRegion(Shape pClip) {
+ mGraphics.setClip(pClip);
+ }
+
+ // Font number (sic), integer
+ void setTextFont(int fontFamily) {
+ // ..?
+ System.err.println("QuickDrawContext.setTextFont");
+ }
+
+ // Sets the text's font style (0..255)
+ void setTextFace(int face) {
+ // int?
+ System.err.println("QuickDrawContext.setTextFace");
+ }
+
+ void setTextMode(int pSourceMode) {
+ // ..?
+ System.err.println("QuickDrawContext.setTextMode");
+ }
+
+ public void setTextSize(int pSize) {
+ mGraphics.setFont(mGraphics.getFont().deriveFont((float) pSize));
+ }
+
+ // Numerator (Point), denominator (Point)
+ void setTextRatio() {
+ // TODO
+ System.err.println("QuickDrawContext.setTextRatio");
+ }
+
+ // TODO: spExtra added width for space characters
+ // TODO: chExtra added width for nonspace characters
+
+ public void setOrigin(Point2D pOrigin) {
+ mGraphics.translate(pOrigin.getX(), pOrigin.getY());
+ }
+
+ public void setForeground(Color pColor) {
+ // TODO: Is this really correct? Or does it depend on pattern mode?
+ mPenPattern = new BitMapPattern(pColor);
+ }
+
+ public void setBackground(Color pColor) {
+ mBackground = new BitMapPattern(pColor);
+ }
+
+ /*
+ // Pen management:
+ // NOTE: The HidePen procedure is called by the OpenRgn, OpenPicture, and OpenPoly routines so that you can create regions, pictures, and polygons without drawing on the screen.
+ // ShowPen is called by the procedures CloseRgn, ClosePoly, and ClosePicture
+ GetPenState // All pen state incl. position (PenState type?)
+ SetPenState
+ */
+
+ /**
+ * HidePen Visibility (decrements visibility by one!)
+ */
+ public void hidePen() {
+ mPenVisibility--;
+ }
+
+ /**
+ * ShowPen Visibility (increments visibility by one!)
+ */
+ public void showPen() {
+ mPenVisibility++;
+ }
+
+ /**
+ * Tells whether pen is visible.
+ *
+ * @return {@code true} if pen is visible
+ */
+ private boolean isPenVisible() {
+ return mPenVisibility >= 0;
+ }
+
+ /**
+ * Returns the pen position.
+ * GetPen
+ *
+ * @return the current pen position
+ */
+ public Point2D getPenPosition() {
+ return (Point2D) mPenPosition.clone();
+ }
+
+ /**
+ * Sets the pen size.
+ * PenSize
+ *
+ * @param pSize the new size
+ */
+ public void setPenSize(Dimension2D pSize) {
+ mPenSize.setSize(pSize);
+ mGraphics.setStroke(getStroke(mPenSize));
+ }
+
+ /**
+ * PenMode // Sets pen pattern mode
+ *
+ * @param pPenMode the new pen mode
+ */
+ public void setPenMode(int pPenMode) {
+ // TODO: Handle HILITE (+50)
+ // TODO: Handle DITHER_COPY (+64)
+ switch (pPenMode) {
+ // Boolean source transfer modes
+ case QuickDraw.SRC_COPY:
+ case QuickDraw.SRC_OR:
+ case QuickDraw.SRC_XOR:
+ case QuickDraw.SRC_BIC:
+ case QuickDraw.NOT_SRC_COPY:
+ case QuickDraw.NOT_SRC_OR:
+ case QuickDraw.NOT_SRC_XOR:
+ case QuickDraw.NOT_SRC_BIC:
+ // Boolean pattern transfer modes
+ case QuickDraw.PAT_COPY:
+ case QuickDraw.PAT_OR:
+ case QuickDraw.PAT_XOR:
+ case QuickDraw.PAT_BIC:
+ case QuickDraw.NOT_PAT_COPY:
+ case QuickDraw.NOT_PAT_OR:
+ case QuickDraw.NOT_PAT_XOR:
+ case QuickDraw.NOT_PAT_BIC:
+ // Aritmetic transfer modes
+ case QuickDraw.BLEND:
+ case QuickDraw.ADD_PIN:
+ case QuickDraw.ADD_OVER:
+ case QuickDraw.SUB_PIN:
+ case QuickDraw.TRANSPARENT:
+ case QuickDraw.AD_MAX:
+ case QuickDraw.SUB_OVER:
+ case QuickDraw.AD_MIN:
+ case QuickDraw.GRAYISH_TEXT_OR:
+ mPenMode = pPenMode;
+ break;
+
+ default:
+ throw new IllegalArgumentException("Undefined pen mode: " + pPenMode);
+ }
+ }
+
+ /**
+ * PenPat & PenPixPat // Sets pen bit pattern or pix pattern
+ *
+ * @param pPattern the new pattern
+ */
+ public void setPenPattern(final Pattern pPattern) {
+ mPenPattern = pPattern;
+ }
+
+ /**
+ * PenNormal // Reset (except posiotion)
+ */
+ public final void setPenNormal() {
+ // NOTE: Shold not change pen location
+ // TODO: What about visibility? Probably not touch
+ setPenPattern(QuickDraw.BLACK);
+ setPenSize(new Dimension(1, 1));
+ mPenMode = QuickDraw.SRC_COPY;
+ }
+
+ /*
+ // Background pattern:
+ BackPat // Used by the Erase* methods
+ *BackPixPat
+ */
+ public void setBackgroundPattern(Pattern pPaint) {
+ mBackground = pPaint;
+ }
+
+ private Composite getCompositeFor(final int pMode) {
+ switch (pMode & ~QuickDraw.DITHER_COPY) {
+ // Boolean source transfer modes
+ case QuickDraw.SRC_COPY:
+ return AlphaComposite.Src; // Or, SRC_OVER?
+ case QuickDraw.SRC_OR:
+ return AlphaComposite.SrcOver; // Or, DST_OUT?
+ case QuickDraw.SRC_XOR:
+ return AlphaComposite.Xor;
+ case QuickDraw.SRC_BIC:
+ return AlphaComposite.Clear;
+ case QuickDraw.NOT_SRC_COPY:
+ case QuickDraw.NOT_SRC_OR:
+ case QuickDraw.NOT_SRC_XOR:
+ case QuickDraw.NOT_SRC_BIC:
+ throw new UnsupportedOperationException("Not implemented for mode " + pMode);
+// return null;
+ // Boolean pattern transfer modes
+ case QuickDraw.PAT_COPY:
+ return AlphaComposite.Src; // Tested
+ case QuickDraw.PAT_OR:
+ return AlphaComposite.SrcOver; // Or, DST_OUT?
+ case QuickDraw.PAT_XOR:
+ return AlphaComposite.Xor;
+ case QuickDraw.PAT_BIC:
+ return AlphaComposite.Clear;
+ case QuickDraw.NOT_PAT_COPY:
+ case QuickDraw.NOT_PAT_OR:
+ case QuickDraw.NOT_PAT_XOR:
+ case QuickDraw.NOT_PAT_BIC:
+ throw new UnsupportedOperationException("Not implemented for mode " + pMode);
+// return null;
+ // TODO: Aritmetic transfer modes
+
+ default:
+ throw new IllegalArgumentException("Unknown pnMode: " + pMode);
+ }
+ }
+
+ /**
+ * Sets up context for line drawing/painting.
+ */
+ protected void setupForPaint() {
+ mGraphics.setPaint(mPenPattern);
+ mGraphics.setComposite(getCompositeFor(mPenMode));
+ //mGraphics.setStroke(getStroke(mPenSize));
+ }
+
+ private Stroke getStroke(final Dimension2D pPenSize) {
+ // TODO: OPTIMIZE: Only create stroke if changed!
+ if (pPenSize.getWidth() <= 1.0 && pPenSize.getWidth() == pPenSize.getHeight()) {
+ return new BasicStroke((float) pPenSize.getWidth());
+ }
+ return new RectangleStroke(new Rectangle2D.Double(0, 0, pPenSize.getWidth(), pPenSize.getHeight()));
+ }
+
+ /**
+ * Sets up paint context for fill.
+ *
+ * @param pPattern the pattern to use for filling.
+ */
+ protected void setupForFill(final Pattern pPattern) {
+ mGraphics.setPaint(pPattern);
+ mGraphics.setComposite(getCompositeFor(QuickDraw.PAT_COPY));
+ }
+
+ protected void setupForErase() {
+ mGraphics.setPaint(mBackground);
+ mGraphics.setComposite(getCompositeFor(QuickDraw.PAT_COPY)); // TODO: Check spec
+ }
+
+ protected void setupForInvert() {
+ // TODO: Setup for invert
+ mGraphics.setColor(Color.BLACK);
+ mGraphics.setXORMode(Color.WHITE);
+ }
+
+ /*
+
+ // Line drawing:
+ MoveTo // Moves to new pos
+ Move // distance (MoveTo(h+dh,v+dv))
+ LineTo // Draws from current pos to new pos, stores new pos
+ Line // dinstance (LineTo(h+dh,v+dv))
+ */
+
+ public void moveTo(final double pX, final double pY) {
+ mPenPosition.setLocation(pX, pY);
+ }
+
+ public final void moveTo(final Point2D pPosition) {
+ moveTo(pPosition.getX(), pPosition.getY());
+ }
+
+ public final void move(final double pDeltaX, final double pDeltaY) {
+ moveTo(mPenPosition.getX() + pDeltaX, mPenPosition.getY() + pDeltaY);
+ }
+
+ public void lineTo(final double pX, final double pY) {
+ Shape line = new Line2D.Double(mPenPosition.getX(), mPenPosition.getY(), pX, pY);
+
+ // TODO: Add line to current shape if recording...
+
+ if (isPenVisible()) {
+ // NOTE: Workaround for known Mac JDK bug: Paint, not frame
+ //mGraphics.setStroke(getStroke(mPenSize)); // Make sure we have correct stroke
+ paintShape(mGraphics.getStroke().createStrokedShape(line));
+
+ }
+
+ moveTo(pX, pY);
+ }
+
+ public final void lineTo(final Point2D pPosition) {
+ lineTo(pPosition.getX(), pPosition.getY());
+ }
+
+ public final void line(final double pDeltaX, final double pDeltaY) {
+ lineTo(mPenPosition.getX() + pDeltaX, mPenPosition.getY() + pDeltaY);
+ }
+
+ /*
+ // Drawing With Color QuickDraw Colors:
+ * RGBForeColor
+ * RGBBackColor
+ * SetCPixel <-- TODO
+ * FillCRect
+ * FillCRoundRect
+ * FillCOval
+ * FillCArc
+ * FillCPoly
+ * FillCRgn
+ * OpColor // sets the maximum color values for the addPin and subPin arithmetic transfer modes, and the weight color for the blend arithmetic transfer mode.
+ * HiliteColor // (SKIP?) See http://developer.apple.com/documentation/mac/quickdraw/QuickDraw-199.html#MARKER-9-151
+
+ // Creating and Managing Rectangles (SKIP? Rely on awt Rectangle):
+ SetRect
+ OffsetRect
+ InsetRect
+ SectRect
+ UnionRect
+ PtInRect
+ Pt2Rect
+ PtToAngle
+ EqualRect
+ EmptyRect // Reset
+ */
+
+ // Drawing Rectangles:
+
+ /**
+ * FrameRect(r) // outline rect with the size, pattern, and pattern mode of
+ * the graphics pen.
+ *
+ * @param pRectangle the rectangle to frame
+ */
+ public void frameRect(final Rectangle2D pRectangle) {
+ frameShape(pRectangle);
+ }
+
+ /**
+ * PaintRect(r) // fills a rectangle's interior with the pattern of the
+ * graphics pen, using the pattern mode of the graphics pen.
+ *
+ * @param pRectangle the rectangle to paint
+ */
+ public void paintRect(final Rectangle2D pRectangle) {
+ paintShape(pRectangle);
+ }
+
+ /**
+ * FillRect(r, pat) // fills a rectangle's interior with any pattern you
+ * specify. The procedure transfers the pattern with the patCopy pattern
+ * mode, which directly copies your requested pattern into the shape.
+ *
+ * @param pRectangle the rectangle to fill
+ * @param pPattern the pattern to use
+ */
+ public void fillRect(final Rectangle2D pRectangle, Pattern pPattern) {
+ fillShape(pRectangle, pPattern);
+ }
+
+ /**
+ * EraseRect(r) // fills the rectangle's interior with the background pattern
+ *
+ * @param pRectangle the rectangle to erase
+ */
+ public void eraseRect(final Rectangle2D pRectangle) {
+ eraseShape(pRectangle);
+ }
+
+ /**
+ * InvertRect(r) // reverses the color of all pixels in the rect
+ *
+ * @param pRectangle the rectangle to invert
+ */
+ public void invertRect(final Rectangle2D pRectangle) {
+ invertShape(pRectangle);
+ }
+
+ // http://developer.apple.com/documentation/mac/quickdraw/QuickDraw-102.html#HEADING102-0
+ // Drawing Rounded Rectangles:
+ private static RoundRectangle2D.Double toRoundRect(final Rectangle2D pRectangle, final int pArcW, final int pArcH) {
+ return new RoundRectangle2D.Double(
+ pRectangle.getX(), pRectangle.getY(),
+ pRectangle.getWidth(), pRectangle.getHeight(),
+ pArcW, pArcH);
+ }
+
+ /**
+ * FrameRoundRect(r,int,int) // outline round rect with the size, pattern, and pattern mode of
+ * the graphics pen.
+ *
+ * @param pRectangle the rectangle to frame
+ * @param pArcW width of the oval defining the rounded corner.
+ * @param pArcH height of the oval defining the rounded corner.
+ */
+ public void frameRoundRect(final Rectangle2D pRectangle, int pArcW, int pArcH) {
+ frameShape(toRoundRect(pRectangle, pArcW, pArcH));
+ }
+
+ /**
+ * PaintRooundRect(r,int,int) // fills a rectangle's interior with the pattern of the
+ * graphics pen, using the pattern mode of the graphics pen.
+ *
+ * @param pRectangle the rectangle to paint
+ * @param pArcW width of the oval defining the rounded corner.
+ * @param pArcH height of the oval defining the rounded corner.
+ */
+ public void paintRoundRect(final Rectangle2D pRectangle, int pArcW, int pArcH) {
+ paintShape(toRoundRect(pRectangle, pArcW, pArcH));
+ }
+
+ /**
+ * FillRoundRect(r,int,int,pat) // fills a rectangle's interior with any pattern you
+ * specify. The procedure transfers the pattern with the patCopy pattern
+ * mode, which directly copies your requested pattern into the shape.
+ *
+ * @param pRectangle the rectangle to fill
+ * @param pArcW width of the oval defining the rounded corner.
+ * @param pArcH height of the oval defining the rounded corner.
+ * @param pPattern the pattern to use
+ */
+ public void fillRoundRect(final Rectangle2D pRectangle, int pArcW, int pArcH, Pattern pPattern) {
+ fillShape(toRoundRect(pRectangle, pArcW, pArcH), pPattern);
+ }
+
+ /**
+ * EraseRoundRect(r,int,int) // fills the rectangle's interior with the background pattern
+ *
+ * @param pRectangle the rectangle to erase
+ * @param pArcW width of the oval defining the rounded corner.
+ * @param pArcH height of the oval defining the rounded corner.
+ */
+ public void eraseRoundRect(final Rectangle2D pRectangle, int pArcW, int pArcH) {
+ eraseShape(toRoundRect(pRectangle, pArcW, pArcH));
+ }
+
+ /**
+ * InvertRoundRect(r,int,int) // reverses the color of all pixels in the rect
+ *
+ * @param pRectangle the rectangle to invert
+ * @param pArcW width of the oval defining the rounded corner.
+ * @param pArcH height of the oval defining the rounded corner.
+ */
+ public void invertRoundRect(final Rectangle2D pRectangle, int pArcW, int pArcH) {
+ invertShape(toRoundRect(pRectangle, pArcW, pArcH));
+ }
+
+ // Drawing Ovals:
+ private static Ellipse2D.Double toOval(final Rectangle2D pRectangle) {
+ Ellipse2D.Double ellipse = new Ellipse2D.Double();
+ ellipse.setFrame(pRectangle);
+ return ellipse;
+ }
+
+ /**
+ * FrameOval(r) // outline oval with the size, pattern, and pattern mode of
+ * the graphics pen.
+ *
+ * @param pRectangle the rectangle to frame
+ */
+ public void frameOval(final Rectangle2D pRectangle) {
+ frameShape(toOval(pRectangle));
+ }
+
+ /**
+ * PaintOval(r) // fills an oval's interior with the pattern of the
+ * graphics pen, using the pattern mode of the graphics pen.
+ *
+ * @param pRectangle the rectangle to paint
+ */
+ public void paintOval(final Rectangle2D pRectangle) {
+ paintShape(toOval(pRectangle));
+ }
+
+ /**
+ * FillOval(r, pat) // fills an oval's interior with any pattern you
+ * specify. The procedure transfers the pattern with the patCopy pattern
+ * mode, which directly copies your requested pattern into the shape.
+ *
+ * @param pRectangle the rectangle to fill
+ * @param pPattern the pattern to use
+ */
+ public void fillOval(final Rectangle2D pRectangle, Pattern pPattern) {
+ fillShape(toOval(pRectangle), pPattern);
+ }
+
+ /**
+ * EraseOval(r) // fills the oval's interior with the background pattern
+ *
+ * @param pRectangle the rectangle to erase
+ */
+ public void eraseOval(final Rectangle2D pRectangle) {
+ eraseShape(toOval(pRectangle));
+ }
+
+ /**
+ * InvertOval(r) // reverses the color of all pixels in the oval
+ *
+ * @param pRectangle the rectangle to invert
+ */
+ public void invertOval(final Rectangle2D pRectangle) {
+ invertShape(toOval(pRectangle));
+ }
+
+ // http://developer.apple.com/documentation/mac/quickdraw/QuickDraw-114.html#HEADING114-0
+ // NOTE: Differs from Java 2D arcs, in start angle, and rotation
+ // Drawing Arcs and Wedges:
+
+ /**
+ * Converts a rectangle to an arc.
+ *
+ * @param pRectangle the framing rectangle
+ * @param pStartAngle start angle in degrees (starting from 12'o clock, this differs from Java)
+ * @param pArcAngle rotation angle in degrees (starting from {@code pStartAngle}, this differs from Java arcs)
+ * @param pClosed specifies if the arc should be closed
+ * @return the arc
+ */
+ private static Arc2D.Double toArc(final Rectangle2D pRectangle, int pStartAngle, int pArcAngle, final boolean pClosed) {
+ return new Arc2D.Double(pRectangle, 90 - pStartAngle, -pArcAngle, pClosed ? Arc2D.PIE : Arc2D.OPEN);
+ }
+
+ /**
+ * FrameArc(r,int,int) // outline arc with the size, pattern, and pattern mode of
+ * the graphics pen.
+ *
+ * @param pRectangle the rectangle to frame
+ * @param pStartAngle start angle in degrees (starting from 12'o clock, this differs from Java)
+ * @param pArcAngle rotation angle in degrees (starting from {@code pStartAngle}, this differs from Java arcs)
+ */
+ public void frameArc(final Rectangle2D pRectangle, int pStartAngle, int pArcAngle) {
+ frameShape(toArc(pRectangle, pStartAngle, pArcAngle, false));
+ }
+
+ /**
+ * PaintArc(r,int,int) // fills an arc's interior with the pattern of the
+ * graphics pen, using the pattern mode of the graphics pen.
+ *
+ * @param pRectangle the rectangle to paint
+ * @param pStartAngle start angle in degrees (starting from 12'o clock, this differs from Java)
+ * @param pArcAngle rotation angle in degrees (starting from {@code pStartAngle}, this differs from Java arcs)
+ */
+ public void paintArc(final Rectangle2D pRectangle, int pStartAngle, int pArcAngle) {
+ paintShape(toArc(pRectangle, pStartAngle, pArcAngle, true));
+ }
+
+ /**
+ * FillArc(r,int,int, pat) // fills an arc's interior with any pattern you
+ * specify. The procedure transfers the pattern with the patCopy pattern
+ * mode, which directly copies your requested pattern into the shape.
+ *
+ * @param pRectangle the rectangle to fill
+ * @param pStartAngle start angle in degrees (starting from 12'o clock, this differs from Java)
+ * @param pArcAngle rotation angle in degrees (starting from {@code pStartAngle}, this differs from Java arcs)
+ * @param pPattern the pattern to use
+ */
+ public void fillArc(final Rectangle2D pRectangle, int pStartAngle, int pArcAngle, Pattern pPattern) {
+ fillShape(toArc(pRectangle, pStartAngle, pArcAngle, true), pPattern);
+ }
+
+ /**
+ * EraseArc(r,int,int) // fills the arc's interior with the background pattern
+ *
+ * @param pRectangle the rectangle to erase
+ * @param pStartAngle start angle in degrees (starting from 12'o clock, this differs from Java)
+ * @param pArcAngle rotation angle in degrees (starting from {@code pStartAngle}, this differs from Java arcs)
+ */
+ public void eraseArc(final Rectangle2D pRectangle, int pStartAngle, int pArcAngle) {
+ eraseShape(toArc(pRectangle, pStartAngle, pArcAngle, true));
+ }
+
+ /**
+ * InvertArc(r,int,int) // reverses the color of all pixels in the arc
+ *
+ * @param pRectangle the rectangle to invert
+ * @param pStartAngle start angle in degrees (starting from 12'o clock, this differs from Java)
+ * @param pArcAngle rotation angle in degrees (starting from {@code pStartAngle}, this differs from Java arcs)
+ */
+ public void invertArc(final Rectangle2D pRectangle, int pStartAngle, int pArcAngle) {
+ invertShape(toArc(pRectangle, pStartAngle, pArcAngle, true));
+ }
+
+ /*
+ // http://developer.apple.com/documentation/mac/quickdraw/QuickDraw-120.html#HEADING120-0
+ // Creating and Managing Polygons:
+ // - Use Shape?
+ OpenPoly // Returns a reference to the polygon, used by the paint or other methods
+ ClosePoly // Close (use LineTo with invisible pen to create)
+ OffsetPoly
+ KillPoly // Set internal reference to null
+ */
+
+ // Drawing Polygons:
+ // TODO: What is the Xxx2D equivalent of Polygon!? GeneralPath?
+ // FramePoly
+ public void framePoly(final Polygon pPolygon) {
+ // TODO: The old PICTImageReader does not draw the last connection line,
+ // unless the start and end point is the same...
+ // Find out what the spec says.
+ //if (pPolygon.xpoints[0] == pPolygon.xpoints[pPolygon.npoints - 1] &&
+ // pPolygon.ypoints[0] == pPolygon.ypoints[pPolygon.npoints - 1]) {
+ //}
+ frameShape(pPolygon);
+ }
+
+ // From the source:
+ // "Four of these procedures--PaintPoly, ErasePoly, InvertPoly, and FillPoly--
+ // temporarily convert the polygon into a region to perform their operations"
+ // PaintPoly
+ public void paintPoly(final Polygon pPolygon) {
+ paintShape(pPolygon);
+ }
+
+ // FillPoly
+ public void fillPoly(final Polygon pPolygon, final Pattern pPattern) {
+ fillShape(pPolygon, pPattern);
+ }
+
+ // ErasePoly
+ public void erasePoly(final Polygon pPolygon) {
+ eraseShape(pPolygon);
+ }
+
+ // InvertPoly
+ public void invertPoly(final Polygon pPolygon) {
+ invertShape(pPolygon);
+ }
+
+ /*
+ // http://developer.apple.com/documentation/mac/quickdraw/QuickDraw-131.html#HEADING131-0
+ // Creating and Managing Regions:
+ // TODO: Java equiv? Area?
+ NewRgn // new Region?
+ OpenRgn // Start collecting region information
+ CloseRgn
+ DisposeRgn
+ CopyRgn
+ SetEmptyRgn
+ SetRectRgn
+ RectRgn
+ OffsetRgn
+ InsetRgn
+ SectRgn
+ UnionRgn
+ DiffRgn
+ XorRgn
+ PtInRgn
+ RectInRgn
+ EqualRgn
+ EmptyRgn
+ */
+
+ // Drawing Regions:
+ // FrameRgn
+ public void frameRegion(final Area pArea) {
+ frameShape(pArea);
+ }
+
+ // PaintRgn
+ public void paintRegion(final Area pArea) {
+ paintShape(pArea);
+ }
+
+ // FillRgn
+ public void fillRegion(final Area pArea, final Pattern pPattern) {
+ fillShape(pArea, pPattern);
+ }
+
+ // EraseRgn
+ public void eraseRegion(final Area pArea) {
+ eraseShape(pArea);
+ }
+
+ // InvertRgn
+ public void invertRegion(final Area pArea) {
+ invertShape(pArea);
+ }
+
+ // TODO: All other operations can delegate to these! :-)
+ private void frameShape(final Shape pShape) {
+ setupForPaint();
+ mGraphics.draw(pShape);
+ }
+
+ private void paintShape(final Shape pShape) {
+ setupForPaint();
+ mGraphics.fill(pShape);
+ }
+
+ private void fillShape(final Shape pShape, final Pattern pPattern) {
+ setupForFill(pPattern);
+ mGraphics.fill(pShape);
+ }
+
+ private void invertShape(final Shape pShape) {
+ setupForInvert();
+ mGraphics.fill(pShape);
+ }
+
+ private void eraseShape(final Shape pShape) {
+ setupForErase();
+ mGraphics.fill(pShape);
+ }
+
+ /*
+ // Scaling and Mapping Points, Rectangles, Polygons, and Regions:
+ ScalePt // Use the getXPtCoord/Y from the reader?
+ MapPt
+ MapRect
+ MapRgn
+ MapPoly
+
+ // Calculating Black-and-White Fills (SKIP?):
+ SeedFill // MacPaint paint-bucket tool
+ CalcMask // Calculates where paint would not flow (see above)
+
+ // Copying Images (SKIP?):
+ */
+ /**
+ * CopyBits.
+ *
+ * Note that the destination is always {@code this}.
+ *
+ * @param pSrcBitmap the source bitmap to copy pixels from
+ * @param pSrcRect the source rectangle
+ * @param pDstRect the destination rectangle
+ * @param pMode the blending mode
+ * @param pMaskRgn the mask region
+ */
+ public void copyBits(BufferedImage pSrcBitmap, Rectangle pSrcRect, Rectangle pDstRect, int pMode, Shape pMaskRgn) {
+ mGraphics.setComposite(getCompositeFor(pMode));
+ if (pMaskRgn != null) {
+ setClipRegion(pMaskRgn);
+ }
+
+ mGraphics.drawImage(
+ pSrcBitmap,
+ pDstRect.x,
+ pDstRect.y,
+ pDstRect.x + pDstRect.width,
+ pDstRect.y + pDstRect.height,
+ pSrcRect.x,
+ pSrcRect.y,
+ pSrcRect.x + pSrcRect.width,
+ pSrcRect.y + pSrcRect.height,
+ null
+ );
+ }
+
+ /**
+ * CopyMask
+ */
+ public void copyMask(BufferedImage pSrcBitmap, BufferedImage pMaskBitmap, Rectangle pSrcRect, Rectangle pMaskRect, Rectangle pDstRect, int pSrcCopy, Shape pMaskRgn) {
+ throw new UnsupportedOperationException("Method copyBits not implemented"); // TODO: Implement
+ }
+
+ /**
+ * CopyDeepMask -- available to basic QuickDraw only in System 7, combines the functionality of both CopyBits and CopyMask
+ */
+ public void copyDeepMask(BufferedImage pSrcBitmap, BufferedImage pMaskBitmap, Rectangle pSrcRect, Rectangle pMaskRect, Rectangle pDstRect, int pSrcCopy, Shape pMaskRgn) {
+ throw new UnsupportedOperationException("Method copyBits not implemented"); // TODO: Implement
+ }
+
+ /*
+ // Drawing With the Eight-Color System:
+ ForeColor // color of the "ink" used to frame, fill, and paint.
+ BackColor
+ ColorBit
+
+ // Getting Pattern Resources:
+ GetPattern
+ GetIndPattern
+
+ // http://developer.apple.com/documentation/mac/Text/Text-128.html#HEADING128-9
+ // Graphics Ports and Text Drawing
+
+ // Setting Text Characteristics:
+ TextFont // specifies the font to be used.
+ TextFace // specifies the glyph style.
+ TextMode // specifies the transfer mode.
+ TextSize // specifies the font size.
+ SpaceExtra // specifies the amount of pixels by which to widen or narrow each space character in a range of text.
+ CharExtra // specifies the amount of pixels by which to widen or narrow each glyph other than the space characters in a range of text (CharExtra).
+ GetFontInfo
+
+ // Drawing Text:
+ DrawChar // draws the glyph of a single 1-byte character.
+ */
+
+ /**
+ * DrawString - draws the text of a Pascal string.
+ *
+ * @param pString a Pascal string (a string of length less than or equal to 255 chars).
+ */
+ public void drawString(String pString) {
+ mGraphics.drawString(pString, (float) getPenPosition().getX(), (float) getPenPosition().getY());
+ }
+
+ /*
+ DrawText // draws the glyphs of a sequence of characters.
+ DrawJustified // draws a sequence of text that is widened or narrowed by a specified number of pixels.
+
+ // Measuring Text:
+ CharWidth // returns the horizontal extension of a single glyph.
+ StringWidth // returns the width of a Pascal string.
+ TextWidth // returns the width of the glyphs of a text segment.
+ MeasureText // fills an array with an entry for each character identifying the width of each character's glyph as measured from the left side of the entire text segment.
+ MeasureJustified // fills an array with an entry for each character in a style run identifying the width of each character's glyph as measured from the left side of the text segment.
+
+ // Laying Out a Line of Text:
+ GetFormatOrder // determines the display order of style runs for a line of text containing multiple style runs with mixed directions
+ VisibleLength // eliminates trailing spaces from the last style run on the line.
+ PortionLine // determines how to distribute the total slop value for a line among the style runs on that line.
+
+ // Determining the Caret Position, and Selecting and Highlighting Text:
+ PixelToChar // converts a pixel location associated with a glyph in a range of text to a byte offset within the style run.
+ CharToPixel // converts a byte offset to a pixel location. The pixel location is measured from the left edge of the style run.
+ HiliteText // returns three pairs of offsets marking the endpoints of ranges of text to be highlighted.
+
+
+// Color Constants
+ÝwhiteColor =Ý30;
+ÝblackColor = 33
+ÝyellowColor = 69;
+ magentaColor =Ý137;
+ÝredColor =Ý205;
+ÝcyanColor =Ý273;
+ÝgreenColor =Ý341;
+ÝblueColor =Ý409;
+ */
+
+ // TODO: Simplify! Extract to upper level class
+ static class RectangleStroke implements Stroke {
+
+ private Shape mShapes[];
+
+ private boolean repeat = true;
+
+ private AffineTransform mTransform = new AffineTransform();
+
+ private static final float FLATNESS = 1;
+
+ public RectangleStroke(Shape pShape) {
+ this(new Shape[]{pShape});
+ }
+
+ RectangleStroke(Shape pShapes[]) {
+ mShapes = new Shape[pShapes.length];
+
+ for (int i = 0; i < mShapes.length; i++) {
+ Rectangle2D bounds = pShapes[i].getBounds2D();
+ mTransform.setToTranslation(-bounds.getCenterX(), -bounds.getCenterY());
+ mShapes[i] = mTransform.createTransformedShape(pShapes[i]);
+ }
+ }
+
+ public Shape createStrokedShape(Shape shape) {
+ GeneralPath result = new GeneralPath();
+
+ PathIterator it = new FlatteningPathIterator(shape.getPathIterator(null), FLATNESS);
+ float points[] = new float[6];
+ float moveX = 0, moveY = 0;
+ float lastX = 0, lastY = 0;
+ float thisX = 0, thisY = 0;
+ int type = 0;
+ float next = 0;
+ int currentShape = 0;
+ int length = mShapes.length;
+
+ while (currentShape < length && !it.isDone()) {
+ type = it.currentSegment(points);
+ switch (type) {
+ case PathIterator.SEG_MOVETO:
+ moveX = lastX = points[0];
+ moveY = lastY = points[1];
+ result.moveTo(moveX, moveY);
+ next = 0;
+ break;
+
+ case PathIterator.SEG_CLOSE:
+ points[0] = moveX;
+ points[1] = moveY;
+ // Fall through
+
+ case PathIterator.SEG_LINETO:
+ thisX = points[0];
+ thisY = points[1];
+ float dx = thisX - lastX;
+ float dy = thisY - lastY;
+ float distance = (float) Math.sqrt(dx * dx + dy * dy);
+ if (distance >= next) {
+ float r = 1.0f / distance;
+ //float angle = (float) Math.atan2(dy, dx);
+ while (currentShape < length && distance >= next) {
+ float x = lastX + next * dx * r;
+ float y = lastY + next * dy * r;
+ mTransform.setToTranslation(x, y);
+ //mTransform.rotate(angle);
+ result.append(mTransform.createTransformedShape(mShapes[currentShape]), false);
+ next += 1;
+ currentShape++;
+ if (repeat) {
+ currentShape %= length;
+ }
+ }
+ }
+ next -= distance;
+ lastX = thisX;
+ lastY = thisY;
+ break;
+ }
+ it.next();
+ }
+
+ return result;
+ }
+
+ }
+}
diff --git a/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QuickTime.java b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QuickTime.java
new file mode 100755
index 00000000..02991e0b
--- /dev/null
+++ b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QuickTime.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (c) 2008, 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.pict;
+
+import com.twelvemonkeys.imageio.util.IIOUtil;
+
+import javax.imageio.IIOException;
+import javax.imageio.stream.ImageInputStream;
+import java.awt.image.BufferedImage;
+import java.io.DataInput;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * QuickTime
+ *
+ * @author Harald Kuhr
+ * @author Matthias Wiesmann (original embedded QuickTime parsing)
+ * @author last modified by $Author: haraldk$
+ * @version $Id: QT.java,v 1.0 Feb 16, 2009 7:20:59 PM haraldk Exp$
+ */
+final class QuickTime {
+ public static final String VENDOR_APPLE = "appl";
+
+ // TODO: Consider SPI for this in the future, however not very likely
+ private static final List sDecompressors = Arrays.asList(
+ new QTBMPDecompressor(),
+ new QTRAWDecompressor(),
+ // The GenericDecompressor must be the last in the list
+ new QTGenericDecompressor()
+ );
+
+ /*
+ Apple compressor id's (vendor 'appl'):
+
+ http://developer.apple.com/DOCUMENTATION/quicktime/Reference/QTRef_Constants/Reference/reference.html
+ kAnimationCodecType ='rle '
+ kAVRJPEGCodecType ='avr '
+ kBaseCodecType ='base'
+ kBMPCodecType ='WRLE' -> BMP without header, SUPPORTED
+ kCinepakCodecType ='cvid'
+ kCloudCodecType ='clou'
+ kCMYKCodecType ='cmyk' -> Is this raw CMYK data?
+ kComponentVideoCodecType ='yuv2'
+ kComponentVideoSigned ='yuvu'
+ kComponentVideoUnsigned ='yuvs'
+ kDVCNTSCCodecType ='dvc '
+ kDVCPALCodecType ='dvcp'
+ kDVCProNTSCCodecType ='dvpn'
+ kDVCProPALCodecType ='dvpp'
+ kFireCodecType ='fire'
+ kFLCCodecType ='flic'
+ k48RGBCodecType ='b48r' -> 48 bit (12 bpp) raw color data?
+ kGIFCodecType ='gif ' -> GIF, should work, but lacks test data
+ kGraphicsCodecType ='smc '
+ kH261CodecType ='h261'
+ kH263CodecType ='h263'
+ kIndeo4CodecType ='IV41'
+ kJPEGCodecType ='jpeg' -> JPEG, SUPPORTED
+ kMacPaintCodecType ='PNTG' -> Isn't this the PICT format itself? Does that make sense?! ;-)
+ kMicrosoftVideo1CodecType ='msvc'
+ kMotionJPEGACodecType ='mjpa'
+ kMotionJPEGBCodecType ='mjpb'
+ kMpegYUV420CodecType ='myuv'
+ kOpenDMLJPEGCodecType ='dmb1'
+ kPhotoCDCodecType ='kpcd' -> Could potentially use JMagick/JUI plugin
+ kPlanarRGBCodecType ='8BPS' -> Use (parts of) Photoshop plugin?
+ kPNGCodecType ='png ' -> PNG, SUPPORTED
+ kQuickDrawCodecType ='qdrw' -> QD?
+ kQuickDrawGXCodecType ='qdgx' -> QD?
+ kRawCodecType ='raw ' -> Raw (A)RGB pixel data
+ kSGICodecType ='.SGI'
+ k16GrayCodecType ='b16g' -> Raw 16 bit gray data?
+ k64ARGBCodecType ='b64a' -> Raw 64 bit (16 bpp) color data?
+ kSorensonCodecType ='SVQ1'
+ kSorensonYUV9CodecType ='syv9'
+ kTargaCodecType ='tga ' -> TGA, maybe create a plugin for that
+ k32AlphaGrayCodecType ='b32a' -> 16 bit gray + 16 bit alpha raw data?
+ kTIFFCodecType ='tiff' -> TIFF, SUPPORTED
+ kVectorCodecType ='path'
+ kVideoCodecType ='rpza'
+ kWaterRippleCodecType ='ripl'
+ kWindowsRawCodecType ='WRAW' -> Raw pixels with reverse byte order ((A)BGR vs (A)RGB)?
+ kYUV420CodecType ='y420'
+ */
+
+ /**
+ * Gets a decompressor that can decompress the described data.
+ *
+ * @param pDescription the image description ({@code 'idsc'} Atom).
+ * @return a decompressor that can decompress data decribed by the given {@link ImageDesc description},
+ * or {@code null} if no decompressor is found
+ */
+ private static QTDecompressor getDecompressor(final ImageDesc pDescription) {
+ for (QTDecompressor decompressor : sDecompressors) {
+ if (decompressor.canDecompress(pDescription)) {
+ return decompressor;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Decompresses the QuickTime image data from the given stream.
+ *
+ * @param pStream the image input stream
+ * @return a {@link BufferedImage} containing the image data, or {@code null} if no decompressor is capable of
+ * decompressing the image.
+ * @throws IOException if an I/O exception occurs during read
+ */
+ public static BufferedImage decompress(final ImageInputStream pStream) throws IOException {
+ ImageDesc description = ImageDesc.read(pStream);
+
+ if (PICTImageReader.DEBUG) {
+ System.out.println(description);
+ }
+
+ QTDecompressor decompressor = getDecompressor(description);
+
+ if (decompressor == null) {
+ return null;
+ }
+
+ InputStream streamAdapter = IIOUtil.createStreamAdapter(pStream, description.dataSize);
+ try {
+ return decompressor.decompress(description, streamAdapter);
+ }
+ finally {
+ streamAdapter.close();
+ }
+ }
+
+ /**
+ * Class representing the {@code 'idsc'} QuickTime Atom.
+ */
+ static final class ImageDesc /*extends QTAtom*/ {
+ private static final int SIZE = 86;
+
+ // 'idsc' Atom size
+ int size;
+
+ String compressorIdentifer;
+ short version;
+ short revision;
+ String compressorVendor;
+
+ int temporalQuality;
+ int spatialQuality;
+
+ int width;
+ int height;
+
+ double horizontalRes;
+ double verticalRes;
+
+ // Size of image data following 'idsc'
+ int dataSize;
+ int frameCount;
+
+ String compressorName;
+
+ short depth;
+ short colorLUTId;
+
+ byte[] extraDesc;
+
+ private ImageDesc() {}
+
+ public static ImageDesc read(final DataInput pStream) throws IOException {
+ // The following looks like the 'idsc' Atom (as described in the QuickTime File Format)
+ ImageDesc description = new ImageDesc();
+
+ description.size = pStream.readInt();
+ description.compressorIdentifer = PICTUtil.readIdString(pStream);
+
+ pStream.skipBytes(4); // Reserved, should be 0
+ pStream.skipBytes(2); // Reserved, should be 0
+ pStream.skipBytes(2); // Reserved, should be 0
+
+ description.version = pStream.readShort(); // Major version, 0 if not applicable
+ description.revision = pStream.readShort(); // Minor version, 0 if not applicable
+ description.compressorVendor = PICTUtil.readIdString(pStream);
+
+ description.temporalQuality = pStream.readInt(); // Temporal quality, 0 means "no temporal compression"
+ description.spatialQuality = pStream.readInt(); // Spatial quality, 0x0000 0200 is codecNormalQuality
+
+ description.width = pStream.readShort(); // Width (short)
+ description.height = pStream.readShort(); // Height (short)
+
+ description.horizontalRes = PICTUtil.readFixedPoint(pStream); // Horizontal resolution, FP, 0x0048 0000 means 72 DPI
+ description.verticalRes = PICTUtil.readFixedPoint(pStream); // Vertical resolution, FP, 0x0048 0000 means 72 DPI
+
+ // TODO: Handle 0 data size as unknown
+ description.dataSize = pStream.readInt(); // Data size, may be 0, if unknown
+ description.frameCount = pStream.readShort(); // Frame count
+
+ description.compressorName = PICTUtil.readStr31(pStream); // Compresor name, 32 byte null-terminated Pascal String
+ description.depth = pStream.readShort(); // Image bit depth
+ description.colorLUTId = pStream.readShort(); // Color Lookup Table Id, -1 means none
+
+ int extraDescSize = description.size - ImageDesc.SIZE;
+ if (extraDescSize < 0) {
+ throw new IIOException("Negative array size in 'idsc' Atom: " + extraDescSize);
+ }
+ description.extraDesc = new byte[extraDescSize];
+ pStream.readFully(description.extraDesc);
+
+ return description;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("'idsc', size: %s, id: '%s', ver: %s, rev: %s, vendor: '%s', " +
+// "tempQ: %s, spatQ: %s, " +
+ "w: %d, h: %d, " +
+// "horiz: %s, vert: %s, " +
+ "data-size: %d, frame-count: %s, " +
+ "name: '%s', depth: %d, lut: %s, extra: %d",
+ size, compressorIdentifer, version, revision, compressorVendor,
+// temporalQuality, spatialQuality,
+ width, height,
+// horizontalRes, verticalRes,
+ dataSize, frameCount,
+ compressorName, depth,
+ colorLUTId, extraDesc != null ? extraDesc.length : 0
+ );
+ }
+ }
+}
diff --git a/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/RGBColor.java b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/RGBColor.java
new file mode 100755
index 00000000..8ee599e3
--- /dev/null
+++ b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/RGBColor.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2008, 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.pict;
+
+import java.awt.*;
+
+/**
+ * RGBColor
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: RGBColor.java,v 1.0 Mar 2, 2009 10:39:24 AM haraldk Exp$
+ */
+class RGBColor extends Color {
+ // TODO: Is the 16 bit resolution ever signigicant?
+ public RGBColor(short r, short g, short b) {
+ super((r & 0xffff) / (float) 0xffff, (g & 0xffff) / (float) 0xffff, (b & 0xffff) / (float) 0xffff);
+ }
+}
diff --git a/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/TestPICTClippingApp.java b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/TestPICTClippingApp.java
new file mode 100755
index 00000000..a614d20c
--- /dev/null
+++ b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/TestPICTClippingApp.java
@@ -0,0 +1,191 @@
+package com.twelvemonkeys.imageio.plugins.pict;
+
+import com.twelvemonkeys.image.BufferedImageIcon;
+import com.twelvemonkeys.image.ImageUtil;
+import com.twelvemonkeys.net.MIMEUtil;
+
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReader;
+import javax.imageio.ImageReadParam;
+import javax.imageio.stream.ImageInputStream;
+import javax.swing.*;
+import java.awt.*;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * TestPICTClippingApp
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: TestPICTClippingApp.java,v 1.0 Feb 16, 2009 3:05:16 PM haraldk Exp$
+ */
+public class TestPICTClippingApp {
+ public static void main(final String[] pArgs) {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ try {
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ }
+ catch (Exception ignore) {
+ }
+
+ JFrame frame = new JFrame("PICTClipping test");
+ frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ JLabel dropZone = new JLabel("Drop images here", JLabel.CENTER) {
+ @Override
+ public Dimension getPreferredSize() {
+ return new Dimension(320, 200);
+ }
+ };
+
+ dropZone.setTransferHandler(new ImageDropHandler(dropZone));
+ frame.add(dropZone);
+ frame.pack();
+ frame.setLocationRelativeTo(null);
+ frame.setVisible(true);
+ }
+ });
+ }
+
+ private static class ImageDropHandler extends TransferHandler {
+ private final JLabel mLabel;
+
+ public ImageDropHandler(JLabel pLabel) {
+ super(null);
+ mLabel = pLabel;
+ }
+
+ private DataFlavor getSupportedFlavor(final DataFlavor[] transferFlavors) {
+ for (DataFlavor flavor : transferFlavors) {
+ String type = MIMEUtil.bareMIME(flavor.getMimeType());
+ if (InputStream.class.isAssignableFrom(flavor.getDefaultRepresentationClass()) && ImageIO.getImageReadersByMIMEType(type).hasNext()) {
+ return flavor;
+ }
+ else if (flavor.equals(DataFlavor.javaFileListFlavor)) {
+ return flavor;
+ }
+ }
+
+ for (DataFlavor flavor : transferFlavors) {
+ System.err.printf("flavor: %s%n", flavor);
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean canImport(final JComponent comp, final DataFlavor[] transferFlavors) {
+ return getSupportedFlavor(transferFlavors) != null;
+ }
+
+ @Override
+ public boolean importData(JComponent comp, Transferable t) {
+ DataFlavor[] flavors = t.getTransferDataFlavors();
+ DataFlavor flavor = getSupportedFlavor(flavors);
+ if (flavor != null) {
+ try {
+ InputStream input;
+ if (DataFlavor.javaFileListFlavor.equals(flavor)) {
+ List files = (List) t.getTransferData(flavor);
+ if (files.isEmpty()) {
+ return false;
+ }
+ else {
+ input = new FileInputStream((File) files.get(0));
+ }
+ }
+ else {
+ Object data = t.getTransferData(flavor);
+ input = (InputStream) data;
+ }
+
+ ImageInputStream stream = ImageIO.createImageInputStream(input);
+ Iterator readers = ImageIO.getImageReaders(stream);
+
+ if (!readers.hasNext()) {
+ String mimeType = MIMEUtil.bareMIME(flavor.getMimeType());
+ System.out.printf("Getting reader by MIME type (%s)...%n", mimeType);
+ readers = ImageIO.getImageReadersByMIMEType(mimeType);
+ }
+
+ if (readers.hasNext()) {
+ ImageReader reader = readers.next();
+
+ reader.setInput(stream);
+
+ final int maxDimension = 200;
+ int w = reader.getWidth(0);
+ int h = reader.getHeight(0);
+
+ ImageReadParam param = null;
+ if (w > maxDimension && h > maxDimension) {
+ int sub = (int) Math.ceil(Math.min(w, h) / (double) maxDimension) / 3;
+ if (sub > 1) {
+ param = reader.getDefaultReadParam();
+ param.setSourceSubsampling(sub, sub, 0, 0);
+ }
+ }
+
+ System.out.printf("Reading %s format%s... ", reader.getFormatName(), (param != null ? ", sampling every " + param.getSourceXSubsampling() + "th pixel" : ""));
+ final BufferedImage image = reader.read(0, param);
+ System.out.printf("Done (%dx%d).%n", image.getWidth(), image.getHeight());
+
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ System.out.print("Scaling image... ");
+ BufferedImage scaled = box(image, maxDimension);
+ System.out.printf("Done (%dx%d).%n", scaled.getWidth(), scaled.getHeight());
+ mLabel.setIcon(new BufferedImageIcon(scaled));
+ }
+ });
+
+ return true;
+ }
+ else {
+ System.err.println("No reader found!");
+ }
+
+ }
+ catch (UnsupportedFlavorException ignore) {
+ ignore.printStackTrace();
+ }
+ catch (IOException ignore) {
+ ignore.printStackTrace();
+ }
+ catch (RuntimeException e) {
+ e.printStackTrace();
+ throw e;
+ }
+ }
+
+ return false;
+ }
+
+ private BufferedImage box(final BufferedImage pImage, final int pMaxDimension) {
+ if (pImage.getWidth() > pMaxDimension || pImage.getHeight() > pMaxDimension) {
+ int w, h;
+
+ if (pImage.getWidth() > pImage.getHeight()) {
+ w = pMaxDimension;
+ h = (int) Math.round(w / (pImage.getWidth() / (double) pImage.getHeight()));
+ }
+ else {
+ h = pMaxDimension;
+ w = (int) Math.round(h * (pImage.getWidth() / (double) pImage.getHeight()));
+ }
+
+ return ImageUtil.createResampled(pImage, w, h, Image.SCALE_DEFAULT);
+ }
+ return pImage;
+ }
+ }
+}
\ No newline at end of file
diff --git a/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/TestPICTEagerDetect.java b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/TestPICTEagerDetect.java
new file mode 100755
index 00000000..0a1630c6
--- /dev/null
+++ b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/TestPICTEagerDetect.java
@@ -0,0 +1,26 @@
+package com.twelvemonkeys.imageio.plugins.pict;
+
+import javax.imageio.ImageIO;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * TestPICTEagerDetect
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: TestPICTEagerDetect.java,v 1.0 Aug 6, 2009 2:59:41 PM haraldk Exp$
+ */
+public class TestPICTEagerDetect {
+ public static void main(final String[] pArgs) throws IOException {
+ PICTImageReaderSpi provider = new PICTImageReaderSpi();
+
+ if (pArgs.length == 0) {
+ System.exit(1);
+ }
+ for (String arg : pArgs) {
+ boolean canDecode = provider.canDecodeInput(ImageIO.createImageInputStream(new File(arg)));
+ System.err.printf("canDecode %s: %s%n", arg, canDecode);
+ }
+ }
+}
diff --git a/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/plpictdec.cpp b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/plpictdec.cpp
new file mode 100755
index 00000000..5169f71c
--- /dev/null
+++ b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/plpictdec.cpp
@@ -0,0 +1,1272 @@
+/*
+http://www.koders.com/cpp/fidEBFA7988680615CF80EF7DA28116F3E423B56E7A.aspx
+http://www.paintlib.de/paintlib/copyright.html
+/--------------------------------------------------------------------
+|
+| $Id: plpictdec.cpp,v 1.14 2004/10/02 22:23:12 uzadow Exp $
+| Macintosh pict Decoder Class
+|
+| This class decodes macintosh PICT files with 1,2,4,8,16 and 32
+| bits per pixel as well as PICT/JPEG. If an alpha channel is
+| present in a 32-bit-PICT, it is decoded as well.
+| The PICT format is a general picture file format and can
+| contain a lot of other elements besides bitmaps. These elements
+| are ignored.
+|
+| Copyright (c) 1996-2002 Ulrich von Zadow
+|
+\--------------------------------------------------------------------
+*/
+
+#include "config.h"
+#include "plpictdec.h"
+#ifdef PL_SUPPORT_JPEG
+#include "pljpegdec.h"
+#endif
+#include "ploptable.h"
+#include "plexcept.h"
+
+#include
+
+PLPictDecoder::PLPictDecoder
+ ( PLJPEGDecoder * pJPEGDecoder
+ )
+ : PLPicDecoder(),
+ m_pJPEGDecoder (pJPEGDecoder)
+ // Creates a decoder
+{
+}
+
+
+PLPictDecoder::~PLPictDecoder
+ ()
+{
+}
+
+
+void PLPictDecoder::Open (PLDataSource * pDataSrc)
+{
+ Trace (2, "Decoding mac pict.\n");
+
+ // Skip empty 512 byte header.
+ pDataSrc->Skip (512);
+
+ // Read PICT header
+ int Version;
+ readHeader (pDataSrc, Version);
+ interpretOpcodes (pDataSrc, Version);
+
+}
+
+
+//! Fills the bitmap with the image.
+void PLPictDecoder::GetImage (PLBmpBase & Bmp)
+{
+ switch (m_PictType)
+ {
+ case op9a:
+ DecodeOp9a (&Bmp, m_pDataSrc);
+ break;
+ case jpeg:
+ DecodeJPEG (&Bmp, m_pDataSrc);
+ break;
+ case pixmap:
+ DecodePixmap (&Bmp, m_pDataSrc);
+ break;
+ case bitmap:
+ DecodeBitmap (&Bmp, m_pDataSrc);
+ break;
+ default:
+ PLASSERT (false);
+ }
+ m_PictType = none;
+}
+
+
+void PLPictDecoder::readHeader
+ ( PLDataSource * pDataSrc,
+ int& Version
+ )
+ // Decodes header and version information.
+ // Performs checks to make sure the data is really a pict file.
+{
+ PLBYTE ch;
+ PLWORD PicSize; // Version 1 picture size. Ignored in version 2.
+ char sz[256];
+ MacRect Frame;
+
+ PicSize = ReadMWord (pDataSrc);
+
+ readRect (&Frame, pDataSrc);
+
+ while ((ch = ReadByte(pDataSrc)) == 0);
+ if (ch != 0x11)
+ raiseError (PL_ERRWRONG_SIGNATURE,
+ "Error decoding pict: Version number missing.");
+
+ switch (ReadByte(pDataSrc))
+ {
+ case 1:
+ Version = 1;
+ break;
+ case 2:
+ if (ReadByte(pDataSrc) != 0xff)
+ raiseError (PL_ERRWRONG_SIGNATURE,
+ "Illegal version number.");
+ Version = 2;
+ break;
+ default:
+ raiseError (PL_ERRWRONG_SIGNATURE,
+ "Illegal version number.");
+ }
+
+ sprintf (sz, "PICT version %d found.\n", Version);
+ Trace (2, sz);
+}
+
+
+void PLPictDecoder::interpretOpcodes
+ ( PLDataSource * pDataSrc,
+ int& Version
+ )
+ // This is the main decoder loop. The functions reads opcodes,
+ // skips some, and hands the rest to opcode-specific functions.
+ // It stops decoding after the first opcode containing bitmapped
+ // data.
+{
+ PLWORD Opcode;
+ char sz[256];
+
+ bool bDone = false;
+
+ while (!bDone)
+ {
+ Opcode = readOpcode(Version, pDataSrc);
+
+ if (Opcode == 0xFF || Opcode == 0xFFFF)
+ {
+ bDone = true;
+ Trace (2, "Opcode: End of pict.\n");
+ raiseError (PL_ERRFORMAT_NOT_SUPPORTED,
+ "PICT contained only vector data!\n");
+ }
+ else if (Opcode < 0xa2)
+ {
+ if (!strcmp(optable[Opcode].name, "reserved"))
+ sprintf (sz, "Opcode: reserved=0x%x\n", Opcode);
+ else
+ sprintf (sz, "Opcode: %s\n", optable[Opcode].name);
+ Trace (2, sz);
+
+ switch (Opcode)
+ {
+ case 0x01: // Clip
+ clip (pDataSrc);
+ break;
+ case 0x12:
+ case 0x13:
+ case 0x14:
+ pixPat (pDataSrc);
+ break;
+ case 0x70:
+ case 0x71:
+ case 0x72:
+ case 0x73:
+ case 0x74:
+ case 0x75:
+ case 0x76:
+ case 0x77:
+ skipPolyOrRegion (pDataSrc);
+ break;
+ case 0x90:
+ case 0x98:
+ bitsRect (pDataSrc);
+ bDone = true;
+ break;
+ case 0x91:
+ case 0x99:
+ bitsRegion (pDataSrc);
+ bDone = true;
+ break;
+ case 0x9a:
+ opcode9a (pDataSrc);
+ bDone = true;
+ break;
+ case 0xa1:
+ longComment (pDataSrc);
+ break;
+ default:
+ // No function => skip to next Opcode
+ if (optable[Opcode].len == WORD_LEN)
+ pDataSrc->Skip(ReadMWord(pDataSrc));
+ else
+ pDataSrc->Skip(optable[Opcode].len);
+ }
+ }
+ else if (Opcode == 0xc00)
+ {
+ Trace (2, "Opcode: Header.\n");
+ headerOp (pDataSrc);
+ }
+ else if (Opcode == 0x8200)
+ {
+ Trace (2, "Opcode: JPEG.\n");
+ jpegOp (pDataSrc);
+ bDone = true;
+ }
+ else if (Opcode >= 0xa2 && Opcode <= 0xaf)
+ {
+ sprintf (sz, "Opcode: reserved 0x%x.\n", Opcode);
+ Trace (2, sz);
+ pDataSrc->Skip(ReadMWord(pDataSrc));
+ }
+ else if ((Opcode >= 0xb0 && Opcode <= 0xcf) ||
+ (Opcode >= 0x8000 && Opcode <= 0x80ff))
+ {
+ // just a reserved Opcode, no data
+ sprintf (sz, "Opcode: reserved 0x%x.\n", Opcode);
+ Trace (2, sz);
+ }
+ else if ((Opcode >= 0xd0 && Opcode <= 0xfe) ||
+ Opcode >= 8100)
+ {
+ sprintf (sz, "Opcode: reserved 0x%x.\n", Opcode);
+ Trace (2, sz);
+ pDataSrc->Skip(ReadMLong(pDataSrc));
+ }
+ else if (Opcode >= 0x100 && Opcode <= 0x7fff)
+ {
+ sprintf (sz, "Opcode: reserved 0x%x.\n", Opcode);
+ Trace (2, sz);
+ pDataSrc->Skip((Opcode >> 7) & 255);
+ }
+ else
+ {
+ char sz[256];
+ sprintf (sz, "Can't handle Opcode %x.\n", Opcode);
+ raiseError (PL_ERRFORMAT_UNKNOWN, sz);
+ }
+ }
+}
+
+
+PLWORD PLPictDecoder::readOpcode
+ ( int Version,
+ PLDataSource * pDataSrc
+ )
+ // moves to an even byte position in the file and returns the
+ // opcode found.
+{
+ if (Version == 2)
+ pDataSrc->AlignToWord();
+
+ if (Version == 1)
+ return ReadByte (pDataSrc);
+ else
+ return ReadMWord (pDataSrc);
+}
+
+
+/////////////////////////////////////////////////////////////////////
+// Opcode functions
+
+void PLPictDecoder::clip
+ ( PLDataSource * pDataSrc
+ )
+ // skips clipping rectangle or region.
+{
+ MacRect ClipRect;
+
+ PLWORD len = ReadMWord(pDataSrc);
+
+ if (len == 0x000a)
+ { /* null rgn */
+ readRect(&ClipRect, pDataSrc);
+ }
+ else
+ pDataSrc->Skip(len - 2);
+}
+
+void PLPictDecoder::pixPat
+ ( PLDataSource * pDataSrc
+ )
+ // skips pattern definition.
+{
+ PLWORD PatType;
+ PLWORD rowBytes;
+ MacpixMap p;
+ PLWORD NumColors;
+
+ PatType = ReadMWord(pDataSrc);
+
+ switch (PatType)
+ {
+ case 2:
+ pDataSrc->Skip(8);
+ pDataSrc->Skip(5);
+ break;
+ case 1:
+ {
+ pDataSrc->Skip(8);
+ rowBytes = ReadMWord(pDataSrc);
+ readRect(&p.Bounds, pDataSrc);
+ readPixmap(&p, pDataSrc);
+
+ PLPixel32 CT[256];
+ readColourTable(&NumColors, pDataSrc, CT);
+ skipBits(&p.Bounds, rowBytes, p.pixelSize, pDataSrc);
+ }
+ break;
+ default:
+ raiseError (PL_ERRFORMAT_UNKNOWN,
+ "Unknown pattern type in pixPat.");
+ }
+}
+
+void PLPictDecoder::skipPolyOrRegion
+ ( PLDataSource * pDataSrc
+ )
+{
+ Trace (3, "Skipping polygon or region.\n");
+ pDataSrc->Skip (ReadMWord (pDataSrc) - 2);
+}
+
+void PLPictDecoder::bitsRect
+ ( PLDataSource * pDataSrc
+ )
+ // Bitmap/pixmap data clipped by a rectangle.
+{
+ m_rowBytes = ReadMWord(pDataSrc); // Bytes per row in source when
+ // uncompressed.
+
+ m_bIsRegion = false;
+ if (m_rowBytes & 0x8000)
+ doPixmap (pDataSrc);
+ else
+ doBitmap (pDataSrc);
+}
+
+void PLPictDecoder::bitsRegion
+ ( PLDataSource * pDataSrc
+ )
+ // Bitmap/pixmap data clipped by a region.
+{
+ m_rowBytes = ReadMWord(pDataSrc); // Bytes per row in source when
+ // uncompressed.
+ m_bIsRegion = true;
+ if (m_rowBytes & 0x8000)
+ doPixmap (pDataSrc);
+ else
+ doBitmap (pDataSrc);
+}
+
+void PLPictDecoder::opcode9a
+ ( PLDataSource * pDataSrc
+ )
+ // DirectBitsRect.
+{
+ pDataSrc->Skip(4); // Skip fake len and fake EOF.
+ ReadMWord(pDataSrc); // bogus row bytes.
+
+ // Read in the PixMap fields.
+ readRect(&m_PixMap.Bounds, pDataSrc);
+ readPixmap (&m_PixMap, pDataSrc);
+
+ // Ignore source & destination rectangle as well as transfer mode.
+ MacRect TempRect;
+ readRect (&TempRect, pDataSrc);
+ readRect (&TempRect, pDataSrc);
+ PLWORD mode = ReadMWord(pDataSrc);
+
+ // Create empty DIB
+ setBmpInfo (m_PixMap);
+ m_PictType=op9a;
+}
+
+void PLPictDecoder::DecodeOp9a
+ ( PLBmpBase * pBmp,
+ PLDataSource * pDataSrc
+ )
+{
+ // Do the actual unpacking.
+ switch (m_PixMap.pixelSize)
+ {
+ case 32:
+ unpack32bits (&m_PixMap.Bounds, 0, m_PixMap.cmpCount, pBmp, pDataSrc);
+ break;
+ case 8:
+ unpack8bits (&m_PixMap.Bounds, 0, pBmp, pDataSrc);
+ break;
+ default:
+ unpackbits (&m_PixMap.Bounds, 0, m_PixMap.pixelSize, pBmp, pDataSrc);
+ }
+}
+
+void PLPictDecoder::longComment
+ ( PLDataSource * pDataSrc
+ )
+{
+ PLWORD type;
+ PLWORD len;
+
+ type = ReadMWord(pDataSrc);
+ len = ReadMWord(pDataSrc);
+ if (len > 0)
+ pDataSrc->Skip (len);
+}
+
+void PLPictDecoder::headerOp
+ ( PLDataSource * pDataSrc
+ )
+{
+ int Version = ReadMWord (pDataSrc);
+ m_Resolution.x = ReadMLong(pDataSrc);
+ m_Resolution.y = ReadMLong(pDataSrc);
+ MacRect Dummy;
+ readRect (&Dummy, pDataSrc);
+ ReadMWord (pDataSrc);
+}
+
+void PLPictDecoder::jpegOp
+ ( PLDataSource * pDataSrc
+ )
+ // Invoke the JPEG decoder for this PICT.
+{
+ long OpLen = ReadMLong(pDataSrc);
+ bool bFound = false;
+ int i = 0;
+
+ // skip to JPEG header.
+ while (!bFound && i < OpLen)
+ {
+ PLBYTE * pData = pDataSrc->GetBufferPtr (3);
+ if (pData[0] == 0xFF && pData[1] == 0xD8 && pData[2] == 0xFF)
+ bFound = true;
+ else
+ {
+ ReadByte(pDataSrc);
+ i++;
+ }
+ }
+ if (bFound)
+ // Pass the data to the JPEG decoder.
+ if (m_pJPEGDecoder)
+#ifdef PL_SUPPORT_JPEG
+ {
+ m_pJPEGDecoder->Open (pDataSrc);
+ SetBmpInfo (*m_pJPEGDecoder);
+ m_PictType = jpeg;
+ }
+#else
+ raiseError (PL_ERRFORMAT_NOT_SUPPORTED,
+ "Library not compiled for PICT/JPEG.");
+#endif
+ else
+ raiseError (PL_ERRFORMAT_NOT_SUPPORTED,
+ "Library not compiled for PICT/JPEG.");
+ else
+ raiseError (PL_ERRFORMAT_NOT_SUPPORTED,
+ "PICT file contains unrecognized quicktime data.\n");
+}
+
+void PLPictDecoder::DecodeJPEG
+ ( PLBmpBase * pBmp,
+ PLDataSource * pDataSrc
+ )
+{
+#ifdef PL_SUPPORT_JPEG
+ m_pJPEGDecoder->GetImage(*pBmp);
+#else
+ raiseError (PL_ERRFORMAT_NOT_SUPPORTED,
+ "Library not compiled for PICT/JPEG.");
+#endif
+}
+
+
+/////////////////////////////////////////////////////////////////////
+// Bitmap & Pixmap functions
+
+void PLPictDecoder::setBmpInfo
+ ( MacpixMap PixMap
+ )
+{
+
+ PLPixelFormat pf;
+ if (PixMap.pixelSize > 8)
+ if (PixMap.cmpCount == 4)
+ pf = PLPixelFormat::A8R8G8B8;
+ else
+ pf = PLPixelFormat::X8R8G8B8;
+ else
+ pf = PLPixelFormat::I8;
+
+ SetBmpInfo (PLPoint (PixMap.Bounds.right - PixMap.Bounds.left,
+ PixMap.Bounds.bottom - PixMap.Bounds.top),
+ PLPoint (PixMap.hRes, PixMap.vRes), pf);
+}
+
+
+void PLPictDecoder::doBitmap
+ ( PLDataSource * pDataSrc
+ )
+ // Decode version 1 bitmap: 1 bpp.
+{
+ MacRect SrcRect;
+ MacRect DstRect;
+ PLWORD width; // Width in pixels
+ PLWORD height; // Height in pixels
+
+ Trace (2, "Reading version 1 bitmap.\n");
+
+ readRect(&m_Bounds, pDataSrc);
+ dumpRect (" Bounds", &m_Bounds);
+ readRect(&SrcRect, pDataSrc);
+ readRect(&DstRect, pDataSrc);
+
+ width = m_Bounds.right - m_Bounds.left;
+ height = m_Bounds.bottom - m_Bounds.top;
+
+ SetBmpInfo (PLPoint (width, height), PLPoint (0,0), PLPixelFormat::I8);
+ m_PictType = bitmap;
+}
+
+void PLPictDecoder::DecodeBitmap
+ ( PLBmpBase * pBmp,
+ PLDataSource * pDataSrc
+ )
+{
+ PLWORD mode = ReadMWord(pDataSrc);
+
+ if (m_bIsRegion)
+ skipPolyOrRegion (pDataSrc);
+
+ pBmp->SetPaletteEntry (0, 0, 0, 0, 255);
+ pBmp->SetPaletteEntry (1, 255, 255, 255, 255);
+ unpackbits (&m_Bounds, m_rowBytes, 1, pBmp, pDataSrc);
+}
+
+
+void PLPictDecoder::doPixmap
+ ( PLDataSource * pDataSrc
+ )
+ // Decode version 2 pixmap
+{
+ readRect(&m_PixMap.Bounds, pDataSrc);
+ readPixmap(&m_PixMap, pDataSrc);
+
+ setBmpInfo (m_PixMap);
+ m_PictType = pixmap;
+}
+
+void PLPictDecoder::DecodePixmap
+ ( PLBmpBase * pBmp,
+ PLDataSource * pDataSrc
+ )
+{
+ // Read mac colour table into windows palette.
+ PLWORD NumColors; // Palette size.
+ PLPixel32 Pal[256];
+
+ readColourTable (&NumColors, pDataSrc, Pal);
+ if (pBmp->GetBitsPerPixel() == 8)
+ pBmp->SetPalette (Pal);
+
+ // Ignore source & destination rectangle as well as transfer mode.
+ MacRect TempRect;
+ readRect (&TempRect, pDataSrc);
+ readRect (&TempRect, pDataSrc);
+ PLWORD mode = ReadMWord(pDataSrc);
+
+ if (m_bIsRegion)
+ skipPolyOrRegion (pDataSrc);
+
+ switch (m_PixMap.pixelSize)
+ {
+ case 32:
+ unpack32bits (&m_PixMap.Bounds, m_rowBytes, m_PixMap.cmpCount, pBmp, pDataSrc);
+ break;
+ case 8:
+ unpack8bits (&m_PixMap.Bounds, m_rowBytes, pBmp, pDataSrc);
+ break;
+ default:
+ unpackbits (&m_PixMap.Bounds, m_rowBytes, m_PixMap.pixelSize,
+ pBmp, pDataSrc);
+ }
+}
+
+void PLPictDecoder::unpack32bits
+ ( MacRect* pBounds,
+ PLWORD rowBytes,
+ int NumBitPlanes, // 3 if RGB, 4 if RGBA
+ PLBmpBase * pBmp,
+ PLDataSource * pDataSrc
+ )
+ // This routine decompresses BitsRects with a packType of 4 (and
+ // 32 bits per pixel). In this format, each line is separated
+ // into 8-bit-bitplanes and then compressed via RLE. To decode,
+ // the routine decompresses each line & then juggles the bytes
+ // around to get pixel-oriented data.
+{
+ int i,j;
+ PLWORD BytesPerRow; // bytes per row when uncompressed.
+ PLPixel32 * pDestLine;
+
+ PLBYTE * pLinebuf; // Temporary buffer for line data. In
+ // this buffer, pixels are uncompressed
+ // but still plane-oriented.
+ PLPixel32 ** pLineArray = pBmp->GetLineArray32();
+
+
+ int Height = pBounds->bottom - pBounds->top;
+ int Width = pBounds->right - pBounds->left;
+
+ BytesPerRow = Width*NumBitPlanes;
+
+ if (rowBytes == 0)
+ rowBytes = Width*4;
+
+ // Allocate temporary line buffer.
+ pLinebuf = new PLBYTE [BytesPerRow];
+
+ try
+ {
+ for (i = 0; i < Height; i++)
+ { // for each line do...
+ int linelen; // length of source line in bytes.
+ if (rowBytes > 250)
+ linelen = ReadMWord(pDataSrc);
+ else
+ linelen = ReadByte(pDataSrc);
+
+ PLBYTE * pBuf = unpackPictRow (pLinebuf, pDataSrc, Width, rowBytes, linelen);
+
+ // Convert plane-oriented data into pixel-oriented data &
+ // copy into destination bitmap.
+ pDestLine = pLineArray[i];
+
+ if (NumBitPlanes == 3)
+ for (j = 0; j < Width; j++)
+ { // For each pixel in line...
+ pDestLine->SetB (*(pBuf+Width*2)); // Blue
+ pDestLine->SetG (*(pBuf+Width)); // Green
+ pDestLine->SetR (*pBuf); // Red
+ pDestLine->SetA (0xFF);
+ pDestLine++;
+ pBuf++;
+ }
+ else
+ for (j = 0; j < Width; j++)
+ { // For each pixel in line...
+ pDestLine->SetB (*(pBuf+Width*3)); // Blue
+ pDestLine->SetG (*(pBuf+Width*2)); // Green
+ pDestLine->SetR (*(pBuf+Width)); // Red
+ pDestLine->SetA (*pBuf);
+ pDestLine++;
+ pBuf++;
+ }
+ }
+ }
+ catch (PLTextException)
+ {
+ delete [] pLinebuf;
+ throw;
+ }
+ catch(...)
+ {
+ delete [] pLinebuf;
+ throw;
+ }
+ delete [] pLinebuf;
+}
+
+
+void PLPictDecoder::unpack8bits
+ ( MacRect* pBounds,
+ PLWORD rowBytes,
+ PLBmpBase * pBmp,
+ PLDataSource * pDataSrc
+ )
+ // Decompression routine for 8 bpp. rowBytes is the number of
+ // bytes each source row would take if it were uncompressed.
+ // This _isn't_ equal to the number of pixels in the row - it
+ // seems apple pads the data to a word boundary and then
+ // compresses it. Of course, we have to decompress the excess
+ // data and then throw it away.
+{
+ int i;
+ PLBYTE ** pLineArray = pBmp->GetLineArray();
+
+ int Height = pBounds->bottom - pBounds->top;
+ int Width = pBounds->right - pBounds->left;
+
+ // High bit of rowBytes is flag.
+ rowBytes &= 0x7fff;
+
+ if (rowBytes == 0)
+ rowBytes = Width;
+
+ PLBYTE * pLineBuf = new PLBYTE [rowBytes];
+
+ try
+ {
+ for (i = 0; i < Height; i++)
+ {
+ int linelen; // length of source line in bytes.
+ if (rowBytes > 250)
+ linelen = ReadMWord(pDataSrc);
+ else
+ linelen = ReadByte(pDataSrc);
+ PLBYTE * pRawLine = unpackPictRow (pLineBuf, pDataSrc, Width, rowBytes, linelen);
+ memcpy (pLineArray[i], pRawLine, Width);
+ }
+ }
+ catch (PLTextException)
+ {
+ delete [] pLineBuf;
+ throw;
+ }
+ delete [] pLineBuf;
+}
+
+
+void PLPictDecoder::unpackbits
+ ( MacRect* pBounds,
+ PLWORD rowBytes,
+ int pixelSize, // Source bits per pixel.
+ PLBmpBase * pBmp,
+ PLDataSource * pDataSrc
+ )
+ // Decompression routine for everything but 8 & 32 bpp. This
+ // routine is slower than the two routines above since it has to
+ // deal with a lot of special cases :-(.
+ // It's also a bit chaotic because of these special cases...
+ // unpack8bits is basically a dumber version of unpackbits.
+{
+ PLBYTE * pSrcLine; // Pointer to source line in file.
+ int i,j,k;
+ PLWORD pixwidth; // bytes per row when uncompressed.
+ int linelen; // length of source line in bytes.
+ int pkpixsize;
+ PLBYTE * pDestLine;
+ PLBYTE FlagCounter;
+ int len;
+ int PixelPerRLEUnit;
+ PLBYTE * pLineBuf;
+ PLBYTE * pBuf;
+ PLBYTE ** pLineArray = pBmp->GetLineArray();
+
+ int Height = pBounds->bottom - pBounds->top;
+ int Width = pBounds->right - pBounds->left;
+
+ // High bit of rowBytes is flag.
+ if (pixelSize <= 8)
+ rowBytes &= 0x7fff;
+
+ pixwidth = Width;
+ pkpixsize = 1; // RLE unit: one byte for everything...
+ if (pixelSize == 16) // ...except 16 bpp.
+ {
+ pkpixsize = 2;
+ pixwidth *= 2;
+ }
+
+ if (rowBytes == 0)
+ rowBytes = pixwidth;
+
+ try
+ {
+ // I allocate the temporary line buffer here. I allocate too
+ // much memory to compensate for sloppy (& hence fast)
+ // decompression.
+ switch (pixelSize)
+ {
+ case 1:
+ PixelPerRLEUnit = 8;
+ pLineBuf = new PLBYTE [(rowBytes+1) * 32];
+ break;
+ case 2:
+ PixelPerRLEUnit = 4;
+ pLineBuf = new PLBYTE [(rowBytes+1) * 16];
+ break;
+ case 4:
+ PixelPerRLEUnit = 2;
+ pLineBuf = new PLBYTE [(rowBytes+1) * 8];
+ break;
+ case 8:
+ PixelPerRLEUnit = 1;
+ pLineBuf = new PLBYTE [rowBytes * 4];
+ break;
+ case 16:
+ PixelPerRLEUnit = 1;
+ pLineBuf = new PLBYTE [rowBytes * 2 + 4];
+ break;
+ default:
+ char sz[256];
+ sprintf (sz,
+ "Illegal bpp value in unpackbits: %d\n",
+ pixelSize);
+ raiseError (PL_ERRFORMAT_UNKNOWN, sz);
+ }
+
+ if (rowBytes < 8)
+ { // ah-ha! The bits aren't actually packed. This will be easy.
+ for (i = 0; i < Height; i++)
+ {
+ pDestLine = pLineArray[i];
+ pSrcLine = pDataSrc->ReadNBytes (rowBytes);
+ if (pixelSize == 16)
+ expandBuf(pDestLine, pSrcLine, Width, pixelSize);
+ else
+ expandBuf8(pDestLine, pSrcLine, Width, pixelSize);
+ }
+ }
+ else
+ {
+ for (i = 0; i < Height; i++)
+ { // For each line do...
+ if (rowBytes > 250)
+ linelen = ReadMWord(pDataSrc);
+ else
+ linelen = ReadByte(pDataSrc);
+
+ pSrcLine = pDataSrc->ReadNBytes(linelen);
+ pBuf = pLineBuf;
+
+ // Unpack RLE. The data is packed bytewise - except for
+ // 16 bpp data, which is packed per pixel :-(.
+ for (j = 0; j < linelen; )
+ {
+ FlagCounter = pSrcLine[j];
+ if (FlagCounter & 0x80)
+ {
+ if (FlagCounter == 0x80)
+ // Special case: repeat value of 0.
+ // Apple sais ignore.
+ j++;
+ else
+ { // Packed data.
+ len = ((FlagCounter ^ 255) & 255) + 2;
+
+ // This is slow for some formats...
+ if (pixelSize == 16)
+ {
+ expandBuf (pBuf, pSrcLine+j+1, 1, pixelSize);
+ for (k = 1; k < len; k++)
+ { // Repeat the pixel len times.
+ memcpy (pBuf+(k*4*PixelPerRLEUnit), pBuf,
+ 4*PixelPerRLEUnit);
+ }
+ pBuf += len*4*PixelPerRLEUnit;
+ }
+ else
+ {
+ expandBuf8 (pBuf, pSrcLine+j+1, 1, pixelSize);
+ for (k = 1; k < len; k++)
+ { // Repeat the expanded byte len times.
+ memcpy (pBuf+(k*PixelPerRLEUnit), pBuf,
+ PixelPerRLEUnit);
+ }
+ pBuf += len*PixelPerRLEUnit;
+ }
+ j += 1 + pkpixsize;
+ }
+ }
+ else
+ { // Unpacked data
+ len = (FlagCounter & 255) + 1;
+ if (pixelSize == 16)
+ {
+ expandBuf (pBuf, pSrcLine+j+1, len, pixelSize);
+ pBuf += len*4*PixelPerRLEUnit;
+ }
+ else
+ {
+ expandBuf8 (pBuf, pSrcLine+j+1, len, pixelSize);
+ pBuf += len*PixelPerRLEUnit;
+ }
+ j += len * pkpixsize + 1;
+ }
+ }
+ pDestLine = pLineArray[i];
+ if (pixelSize == 16)
+ memcpy (pDestLine, pLineBuf, 4*Width);
+ else
+ memcpy (pDestLine, pLineBuf, Width);
+ }
+ }
+ }
+ catch (PLTextException)
+ {
+ delete [] pLineBuf;
+ throw;
+ }
+
+ delete [] pLineBuf;
+}
+
+void PLPictDecoder::skipBits
+ ( MacRect* pBounds,
+ PLWORD rowBytes,
+ int pixelSize, // Source bits per pixel.
+ PLDataSource * pDataSrc
+ )
+ // skips unneeded packbits.
+{
+ int i;
+ PLWORD pixwidth; // bytes per row when uncompressed.
+ int linelen; // length of source line in bytes.
+
+ int Height = pBounds->bottom - pBounds->top;
+ int Width = pBounds->right - pBounds->left;
+
+ // High bit of rowBytes is flag.
+ if (pixelSize <= 8)
+ rowBytes &= 0x7fff;
+
+ pixwidth = Width;
+
+ if (pixelSize == 16)
+ pixwidth *= 2;
+
+ if (rowBytes == 0)
+ rowBytes = pixwidth;
+
+ if (rowBytes < 8)
+ {
+ pDataSrc->Skip (rowBytes*Height);
+ }
+ else
+ {
+ for (i = 0; i < Height; i++)
+ {
+ if (rowBytes > 250)
+ linelen = ReadMWord(pDataSrc);
+ else
+ linelen = ReadByte(pDataSrc);
+ pDataSrc->Skip (linelen);
+ }
+ }
+}
+
+
+void PLPictDecoder::expandBuf
+ ( PLBYTE * pDestBuf,
+ PLBYTE * pSrcBuf,
+ int Width, // Width in bytes for 8 bpp or less.
+ // Width in pixels for 16 bpp.
+ int bpp // bits per pixel
+ )
+ // Expands Width units to 32-bit pixel data.
+{
+ PLBYTE * pSrc;
+ PLBYTE * pDest;
+ int i;
+
+ pSrc = pSrcBuf;
+ pDest = pDestBuf;
+
+ switch (bpp)
+ {
+ case 16:
+ for (i=0; i> 5) & 31)*8; // Green
+ *(pDestBuf+PL_RGBA_RED) = ((Src >> 10) & 31)*8; // Red
+ *(pDestBuf+PL_RGBA_ALPHA) = 0xFF; // Alpha
+ pSrcBuf += 2;
+ pDestBuf += 4;
+ }
+ break;
+ default:
+ raiseError (PL_ERRFORMAT_UNKNOWN,
+ "Bad bits per pixel in expandBuf.");
+ }
+ return;
+}
+
+
+void PLPictDecoder::expandBuf8
+ ( PLBYTE * pDestBuf,
+ PLBYTE * pSrcBuf,
+ int Width, // Width in bytes.
+ int bpp // bits per pixel.
+ )
+ // Expands Width units to 8-bit pixel data.
+ // Max. 8 bpp source format.
+{
+ PLBYTE * pSrc;
+ PLBYTE * pDest;
+ int i;
+
+ pSrc = pSrcBuf;
+ pDest = pDestBuf;
+
+ switch (bpp)
+ {
+ case 8:
+ memcpy (pDestBuf, pSrcBuf, Width);
+ break;
+ case 4:
+ for (i=0; i> 4) & 15;
+ *(pDest+1) = (*pSrc & 15);
+ pSrc++;
+ pDest += 2;
+ }
+ if (Width & 1) // Odd Width?
+ {
+ *pDest = (*pSrc >> 4) & 15;
+ pDest++;
+ }
+ break;
+ case 2:
+ for (i=0; i> 6) & 3;
+ *(pDest+1) = (*pSrc >> 4) & 3;
+ *(pDest+2) = (*pSrc >> 2) & 3;
+ *(pDest+3) = (*pSrc & 3);
+ pSrc++;
+ pDest += 4;
+ }
+ if (Width & 3) // Check for leftover pixels
+ for (i=6; i>8-(Width & 3)*2; i-=2)
+ {
+ *pDest = (*pSrc >> i) & 3;
+ pDest++;
+ }
+ break;
+ case 1:
+ for (i=0; i> 7) & 1;
+ *(pDest+1) = (*pSrc >> 6) & 1;
+ *(pDest+2) = (*pSrc >> 5) & 1;
+ *(pDest+3) = (*pSrc >> 4) & 1;
+ *(pDest+4) = (*pSrc >> 3) & 1;
+ *(pDest+5) = (*pSrc >> 2) & 1;
+ *(pDest+6) = (*pSrc >> 1) & 1;
+ *(pDest+7) = (*pSrc & 1);
+ pSrc++;
+ pDest += 8;
+ }
+ if (Width & 7) // Check for leftover pixels
+ for (i=7; i>(8-Width & 7); i--)
+ {
+ *pDest = (*pSrc >> i) & 1;
+ pDest++;
+ }
+ break;
+ default:
+ raiseError (PL_ERRFORMAT_UNKNOWN,
+ "Bad bits per pixel in expandBuf8.");
+ }
+ return;
+}
+
+
+/////////////////////////////////////////////////////////////////////
+// Auxillary functions
+
+void PLPictDecoder::readPixmap
+ ( MacpixMap * pPixMap,
+ PLDataSource * pDataSrc
+ )
+{
+ pPixMap->version = ReadMWord(pDataSrc);
+ pPixMap->packType = ReadMWord(pDataSrc);
+ pPixMap->packSize = ReadMLong(pDataSrc);
+ pPixMap->hRes = ReadMWord(pDataSrc);
+ ReadMWord(pDataSrc);
+ pPixMap->vRes = ReadMWord(pDataSrc);
+ ReadMWord(pDataSrc);
+ pPixMap->pixelType = ReadMWord(pDataSrc);
+ pPixMap->pixelSize = ReadMWord(pDataSrc);
+ pPixMap->cmpCount = ReadMWord(pDataSrc);
+ pPixMap->cmpSize = ReadMWord(pDataSrc);
+ pPixMap->planeBytes = ReadMLong(pDataSrc);
+ pPixMap->pmTable = ReadMLong(pDataSrc);
+ pPixMap->pmReserved = ReadMLong(pDataSrc);
+
+ tracePixMapHeader (2, pPixMap);
+}
+
+void PLPictDecoder::readColourTable
+ ( PLWORD * pNumColors,
+ PLDataSource * pDataSrc,
+ PLPixel32 * pPal
+ )
+ // Reads a mac colour table into a bitmap palette.
+{
+ PLLONG ctSeed;
+ PLWORD ctFlags;
+ PLWORD val;
+ int i;
+
+ Trace (3, "Getting color table info.\n");
+
+ ctSeed = ReadMLong(pDataSrc);
+ ctFlags = ReadMWord(pDataSrc);
+ *pNumColors = ReadMWord(pDataSrc)+1;
+
+ char sz[256];
+ sprintf (sz, "Palette Size: %d\n", *pNumColors);
+ Trace (2, sz);
+ Trace (3, "Reading Palette.\n");
+
+ for (i = 0; i < *pNumColors; i++)
+ {
+ val = ReadMWord(pDataSrc);
+ if (ctFlags & 0x8000)
+ // The indicies in a device colour table are bogus and
+ // usually == 0, so I assume we allocate up the list of
+ // colours in order.
+ val = i;
+ if (val >= *pNumColors)
+ {
+ raiseError (PL_ERRFORMAT_UNKNOWN,
+ "pixel value greater than colour table size.");
+ }
+ // Mac colour tables contain 16-bit values for R, G, and B...
+ pPal[val].SetR ((PLBYTE) (((PLWORD) (ReadMWord(pDataSrc)) >> 8) & 0xFF));
+ pPal[val].SetG ((PLBYTE) (((PLWORD) (ReadMWord(pDataSrc)) >> 8) & 0xFF));
+ pPal[val].SetB ((PLBYTE) (((PLWORD) (ReadMWord(pDataSrc)) >> 8) & 0xFF));
+ }
+
+}
+
+void PLPictDecoder::readRect
+ ( MacRect * pr,
+ PLDataSource * pDataSrc
+ )
+{
+ pr->top = ReadMWord(pDataSrc);
+ pr->left = ReadMWord(pDataSrc);
+ pr->bottom = ReadMWord(pDataSrc);
+ pr->right = ReadMWord(pDataSrc);
+}
+
+
+void PLPictDecoder::dumpRect
+ ( char * psz,
+ MacRect * pr
+ )
+{
+ char sz[256];
+ sprintf (sz, "%s (%d,%d) (%d,%d).\n",
+ psz, pr->left, pr->top, pr->right, pr->bottom);
+ Trace (2, sz);
+}
+
+
+void PLPictDecoder::tracePixMapHeader
+ ( int Level,
+ MacpixMap * pPixMap
+ )
+{
+ char sz[256];
+ Trace (Level, "PixMap header info:\n");
+ dumpRect (" Bounds:", &(pPixMap->Bounds));
+
+ sprintf (sz, " version: 0x%x\n", pPixMap->version);
+ Trace (Level, sz);
+ sprintf (sz, " packType: %d\n", pPixMap->packType);
+ Trace (Level, sz);
+ sprintf (sz, " packSize: %ld\n", pPixMap->packSize);
+ Trace (Level, sz);
+ sprintf (sz, " hRes: %ld\n", pPixMap->hRes);
+ Trace (Level, sz);
+ sprintf (sz, " vRes: %ld\n", pPixMap->vRes);
+ Trace (Level, sz);
+ sprintf (sz, " pixelSize: %d\n", pPixMap->pixelSize);
+ Trace (Level, sz);
+ sprintf (sz, " cmpCount: %d\n", pPixMap->cmpCount);
+ Trace (Level, sz);
+ sprintf (sz, " cmpSize: %d.\n", pPixMap->cmpSize);
+ Trace (Level, sz);
+ sprintf (sz, " planeBytes: %ld.\n", pPixMap->planeBytes);
+ Trace (Level, sz);
+}
+
+/*
+/--------------------------------------------------------------------
+|
+| $Log: plpictdec.cpp,v $
+| Revision 1.14 2004/10/02 22:23:12 uzadow
+| - configure and Makefile cleanups\n- Pixelformat enhancements for several filters\n- Added PLBmpBase:Dump\n- Added PLBmpBase::GetPixelNn()-methods\n- Changed default unix byte order to BGR
+|
+| Revision 1.13 2004/09/11 12:41:35 uzadow
+| removed plstdpch.h
+|
+| Revision 1.12 2004/09/09 16:52:49 artcom
+| refactored PixelFormat
+|
+| Revision 1.11 2004/06/19 16:49:07 uzadow
+| Changed GetImage so it works with PLBmpBase
+|
+| Revision 1.10 2002/11/04 21:03:12 uzadow
+| Fixed error when PL_SUPPORT_JPEG was turned off.
+|
+| Revision 1.9 2002/08/04 21:20:41 uzadow
+| no message
+|
+| Revision 1.8 2002/08/04 20:08:01 uzadow
+| Added PLBmpInfo class, ability to extract metainformation from images without loading the whole image and proper greyscale support.
+|
+| Revision 1.7 2002/03/31 13:36:42 uzadow
+| Updated copyright.
+|
+| Revision 1.6 2001/10/21 17:12:40 uzadow
+| Added PSD decoder beta, removed BPPWanted from all decoders, added PLFilterPixel.
+|
+| Revision 1.5 2001/10/16 17:12:26 uzadow
+| Added support for resolution information (Luca Piergentili)
+|
+| Revision 1.4 2001/10/06 22:03:26 uzadow
+| Added PL prefix to basic data types.
+|
+| Revision 1.3 2001/10/06 15:32:22 uzadow
+| Removed types LPBYTE, DWORD, UCHAR, VOID and INT from the code.
+|
+| Revision 1.2 2001/09/16 20:57:17 uzadow
+| Linux version name prefix changes
+|
+| Revision 1.1 2001/09/16 19:03:22 uzadow
+| Added global name prefix PL, changed most filenames.
+|
+| Revision 1.12 2001/02/04 14:31:52 uzadow
+| Member initialization list cleanup (Erik Hoffmann).
+|
+| Revision 1.11 2001/01/21 14:28:21 uzadow
+| Changed array cleanup from delete to delete[].
+|
+| Revision 1.10 2000/12/18 22:42:52 uzadow
+| Replaced RGBAPIXEL with PLPixel32.
+|
+| Revision 1.9 2000/10/24 23:01:42 uzadow
+| Fixed bug in ver.1 bitmap decoder
+|
+| Revision 1.8 2000/08/13 12:11:43 Administrator
+| Added experimental DirectDraw-Support
+|
+| Revision 1.7 2000/05/27 16:28:28 Ulrich von Zadow
+| Really fixed bug decoding pixmaps with < 8 bpp.
+|
+| Revision 1.6 2000/03/15 17:23:20 Ulrich von Zadow
+| Fixed bug decoding pixmaps with < 8 bpp.
+|
+| Revision 1.5 2000/01/16 20:43:14 anonymous
+| Removed MFC dependencies
+|
+| Revision 1.4 1999/11/22 15:00:27 Ulrich von Zadow
+| Fixed bug decoding small 24 bpp pict files.
+|
+| Revision 1.3 1999/10/03 18:50:51 Ulrich von Zadow
+| Added automatic logging of changes.
+|
+|
+\--------------------------------------------------------------------
+*/
diff --git a/twelvemonkeys-imageio/pict/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi b/twelvemonkeys-imageio/pict/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi
new file mode 100755
index 00000000..1f4a92e9
--- /dev/null
+++ b/twelvemonkeys-imageio/pict/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi
@@ -0,0 +1 @@
+com.twelvemonkeys.imageio.plugins.pict.PICTImageReaderSpi
\ No newline at end of file
diff --git a/twelvemonkeys-imageio/pict/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi b/twelvemonkeys-imageio/pict/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi
new file mode 100755
index 00000000..4a4007f8
--- /dev/null
+++ b/twelvemonkeys-imageio/pict/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi
@@ -0,0 +1 @@
+com.twelvemonkeys.imageio.plugins.pict.PICTImageWriterSpi
\ No newline at end of file
diff --git a/twelvemonkeys-imageio/pict/src/test/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderTestCase.java b/twelvemonkeys-imageio/pict/src/test/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderTestCase.java
new file mode 100755
index 00000000..ec2ad5f7
--- /dev/null
+++ b/twelvemonkeys-imageio/pict/src/test/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderTestCase.java
@@ -0,0 +1,60 @@
+package com.twelvemonkeys.imageio.plugins.pict;
+
+import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
+
+import javax.imageio.spi.ImageReaderSpi;
+import java.awt.*;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * ICOImageReaderTestCase
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: ICOImageReaderTestCase.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
+ */
+public class PICTImageReaderTestCase extends ImageReaderAbstractTestCase {
+
+ static ImageReaderSpi sProvider = new PICTImageReaderSpi();
+
+ // TODO: Should also test the cliboard format (without 512 byte header)
+ protected List getTestData() {
+ return Arrays.asList(
+ new TestData(getClassLoaderResource("/pict/test.pct"), new Dimension(300, 200)),
+ new TestData(getClassLoaderResource("/pict/food.pct"), new Dimension(146, 194)),
+ new TestData(getClassLoaderResource("/pict/carte.pict"), new Dimension(782, 598)),
+ // Embedded QuickTime image... Should at least include the embedded fallback text
+ new TestData(getClassLoaderResource("/pict/u2.pict"), new Dimension(160, 159)),
+ // Obsolete V2 format with weird header
+ new TestData(getClassLoaderResource("/pict/FLAG_B24.PCT"), new Dimension(124, 124)),
+ // 1000 DPI with bounding box not matching DPI
+ new TestData(getClassLoaderResource("/pict/oom.pict"), new Dimension(1713, 1263))
+ );
+ }
+
+ protected ImageReaderSpi createProvider() {
+ return sProvider;
+ }
+
+ @Override
+ protected PICTImageReader createReader() {
+ return new PICTImageReader(sProvider);
+ }
+
+ protected Class getReaderClass() {
+ return PICTImageReader.class;
+ }
+
+ protected List getFormatNames() {
+ return Arrays.asList("pict");
+ }
+
+ protected List getSuffixes() {
+ return Arrays.asList("pct", "pict");
+ }
+
+ protected List getMIMETypes() {
+ return Arrays.asList("image/pict", "image/x-pict");
+ }
+}
\ No newline at end of file
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/1.pict b/twelvemonkeys-imageio/pict/src/test/resources/pict/1.pict
new file mode 100755
index 00000000..3b4a8611
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/1.pict differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/16bit.pict b/twelvemonkeys-imageio/pict/src/test/resources/pict/16bit.pict
new file mode 100755
index 00000000..830d2e34
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/16bit.pict differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/2.pict b/twelvemonkeys-imageio/pict/src/test/resources/pict/2.pict
new file mode 100755
index 00000000..cccdbac0
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/2.pict differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/32bit.pict b/twelvemonkeys-imageio/pict/src/test/resources/pict/32bit.pict
new file mode 100755
index 00000000..d17c16fe
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/32bit.pict differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/4.pict b/twelvemonkeys-imageio/pict/src/test/resources/pict/4.pict
new file mode 100755
index 00000000..80eb774a
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/4.pict differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/5.pict b/twelvemonkeys-imageio/pict/src/test/resources/pict/5.pict
new file mode 100755
index 00000000..7d62beeb
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/5.pict differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/6.pict b/twelvemonkeys-imageio/pict/src/test/resources/pict/6.pict
new file mode 100755
index 00000000..3bc0c581
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/6.pict differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/7.pict b/twelvemonkeys-imageio/pict/src/test/resources/pict/7.pict
new file mode 100755
index 00000000..9d68894c
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/7.pict differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/8.pict b/twelvemonkeys-imageio/pict/src/test/resources/pict/8.pict
new file mode 100755
index 00000000..3386cc7c
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/8.pict differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/9.pict b/twelvemonkeys-imageio/pict/src/test/resources/pict/9.pict
new file mode 100755
index 00000000..c34290af
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/9.pict differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/BLK.PCT b/twelvemonkeys-imageio/pict/src/test/resources/pict/BLK.PCT
new file mode 100755
index 00000000..03819136
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/BLK.PCT differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/BLU.PCT b/twelvemonkeys-imageio/pict/src/test/resources/pict/BLU.PCT
new file mode 100755
index 00000000..667c62ae
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/BLU.PCT differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/EDUC0052.pict b/twelvemonkeys-imageio/pict/src/test/resources/pict/EDUC0052.pict
new file mode 100755
index 00000000..21038982
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/EDUC0052.pict differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/FLAG_B24.PCT b/twelvemonkeys-imageio/pict/src/test/resources/pict/FLAG_B24.PCT
new file mode 100755
index 00000000..d9bc2d53
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/FLAG_B24.PCT differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/GRN.PCT b/twelvemonkeys-imageio/pict/src/test/resources/pict/GRN.PCT
new file mode 100755
index 00000000..3ac4ec7d
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/GRN.PCT differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/MARBLES.PCT b/twelvemonkeys-imageio/pict/src/test/resources/pict/MARBLES.PCT
new file mode 100755
index 00000000..47ffe741
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/MARBLES.PCT differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/RED.PCT b/twelvemonkeys-imageio/pict/src/test/resources/pict/RED.PCT
new file mode 100755
index 00000000..543d5361
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/RED.PCT differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/TRU256.PCT b/twelvemonkeys-imageio/pict/src/test/resources/pict/TRU256.PCT
new file mode 100755
index 00000000..e1c2a9a1
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/TRU256.PCT differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/VENUS.PCT b/twelvemonkeys-imageio/pict/src/test/resources/pict/VENUS.PCT
new file mode 100755
index 00000000..a50f5829
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/VENUS.PCT differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/WHT.PCT b/twelvemonkeys-imageio/pict/src/test/resources/pict/WHT.PCT
new file mode 100755
index 00000000..0cf13ceb
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/WHT.PCT differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/YEL.PCT b/twelvemonkeys-imageio/pict/src/test/resources/pict/YEL.PCT
new file mode 100755
index 00000000..2f9d44ec
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/YEL.PCT differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/carte.pict b/twelvemonkeys-imageio/pict/src/test/resources/pict/carte.pict
new file mode 100755
index 00000000..0cfc22ae
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/carte.pict differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/demo.pict b/twelvemonkeys-imageio/pict/src/test/resources/pict/demo.pict
new file mode 100755
index 00000000..4446f430
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/demo.pict differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/foo.pict b/twelvemonkeys-imageio/pict/src/test/resources/pict/foo.pict
new file mode 100755
index 00000000..f5879194
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/foo.pict differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/food.pct b/twelvemonkeys-imageio/pict/src/test/resources/pict/food.pct
new file mode 100755
index 00000000..c4a4c673
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/food.pct differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/mire16.pict b/twelvemonkeys-imageio/pict/src/test/resources/pict/mire16.pict
new file mode 100755
index 00000000..e7a328f6
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/mire16.pict differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/mire32.pict b/twelvemonkeys-imageio/pict/src/test/resources/pict/mire32.pict
new file mode 100755
index 00000000..2d029ef1
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/mire32.pict differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/oom.pict b/twelvemonkeys-imageio/pict/src/test/resources/pict/oom.pict
new file mode 100755
index 00000000..eec7427a
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/oom.pict differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/qt_bmp.pict b/twelvemonkeys-imageio/pict/src/test/resources/pict/qt_bmp.pict
new file mode 100755
index 00000000..08b28056
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/qt_bmp.pict differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/qt_jpeg.pict b/twelvemonkeys-imageio/pict/src/test/resources/pict/qt_jpeg.pict
new file mode 100755
index 00000000..263cfa73
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/qt_jpeg.pict differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/qt_mire.pict b/twelvemonkeys-imageio/pict/src/test/resources/pict/qt_mire.pict
new file mode 100755
index 00000000..c0f30b52
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/qt_mire.pict differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/qt_png.pict b/twelvemonkeys-imageio/pict/src/test/resources/pict/qt_png.pict
new file mode 100755
index 00000000..67e2fe68
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/qt_png.pict differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/qt_tiff.pict b/twelvemonkeys-imageio/pict/src/test/resources/pict/qt_tiff.pict
new file mode 100755
index 00000000..8b48695d
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/qt_tiff.pict differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/test.pct b/twelvemonkeys-imageio/pict/src/test/resources/pict/test.pct
new file mode 100755
index 00000000..b8f7bc2f
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/test.pct differ
diff --git a/twelvemonkeys-imageio/pict/src/test/resources/pict/u2.pict b/twelvemonkeys-imageio/pict/src/test/resources/pict/u2.pict
new file mode 100755
index 00000000..f9b6a27c
Binary files /dev/null and b/twelvemonkeys-imageio/pict/src/test/resources/pict/u2.pict differ
diff --git a/twelvemonkeys-imageio/pom.xml b/twelvemonkeys-imageio/pom.xml
new file mode 100644
index 00000000..c2c1e98a
--- /dev/null
+++ b/twelvemonkeys-imageio/pom.xml
@@ -0,0 +1,115 @@
+
+
+ 4.0.0
+ com.twelvemonkeys
+ twelvemonkeys-imageio
+ 2.2
+ pom
+ TwelveMonkeys ImageIO
+
+
+ com.twelvemonkeys
+ twelvemonkeys-parent
+ 2.0
+
+
+
+
+ Harald Kuhr
+ harald.kuhr@gmail.com
+
+ owner
+ developer
+
+
+
+
+
+
+ core
+
+
+ ico
+ iff
+ pdf
+ pict
+ psd
+ thumbsdb
+
+
+ batik
+ jmagick
+
+
+
+ 2.2-SNAPSHOT
+ 2.2-SNAPSHOT
+
+
+
+
+ com.twelvemonkeys
+ twelvemonkeys-core
+ ${core.version}
+ compile
+
+
+ com.twelvemonkeys
+ twelvemonkeys-core
+ ${core.version}
+ tests
+ test
+
+
+
+ junit
+ junit
+ 4.3.1
+ test
+
+
+
+ jmock
+ jmock-cglib
+ 1.0.1
+ test
+
+
+
+
+
+
+ com.twelvemonkeys.imageio
+ twelvemonkeys-imageio-core
+ ${imageio.core.version}
+ compile
+
+
+
+ com.twelvemonkeys.imageio
+ twelvemonkeys-imageio-core
+ ${imageio.core.version}
+ tests
+ test
+
+
+
+
+
+
+
+ maven-source-plugin
+
+
+
+ maven-resources-plugin
+
+ UTF-8
+
+
+
+
+
+
diff --git a/twelvemonkeys-imageio/psd/license.txt b/twelvemonkeys-imageio/psd/license.txt
new file mode 100755
index 00000000..2d8ee79c
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/license.txt
@@ -0,0 +1,25 @@
+Copyright (c) 2009, 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.
\ No newline at end of file
diff --git a/twelvemonkeys-imageio/psd/pom.xml b/twelvemonkeys-imageio/psd/pom.xml
new file mode 100755
index 00000000..81abcf08
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/pom.xml
@@ -0,0 +1,31 @@
+
+
+ 4.0.0
+ com.twelvemonkeys.imageio
+ twelvemonkeys-imageio-psd
+ 2.2-SNAPSHOT
+ TwelveMonkeys ImageIO PSD plugin
+
+ ImageIO plugin for Adobe Photoshop Document (PSD).
+
+
+
+ twelvemonkeys-imageio
+ com.twelvemonkeys
+ 2.2
+
+
+
+
+ com.twelvemonkeys.imageio
+ twelvemonkeys-imageio-core
+
+
+ com.twelvemonkeys.imageio
+ twelvemonkeys-imageio-core
+ tests
+
+
+
\ No newline at end of file
diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/CMYKColorSpace.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/CMYKColorSpace.java
new file mode 100755
index 00000000..11c01e89
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/CMYKColorSpace.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2008, 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.psd;
+
+import java.awt.color.ColorSpace;
+
+/**
+ * CMYKColorSpace
+*
+* @author Harald Kuhr
+* @author last modified by $Author: haraldk$
+* @version $Id: CMYKColorSpace.java,v 1.0 Apr 30, 2008 1:38:13 PM haraldk Exp$
+*/
+// TODO: Move to com.twlevemonkeys.image?
+// TODO: Read a ICC CMYK profile from classpath resource (from ECI)? ISO coated?
+final class CMYKColorSpace extends ColorSpace {
+
+ static final ColorSpace INSTANCE = new CMYKColorSpace();
+ final ColorSpace sRGB = getInstance(CS_sRGB);
+
+ CMYKColorSpace() {
+ super(ColorSpace.TYPE_CMYK, 4);
+ }
+
+ public static ColorSpace getInstance() {
+ return INSTANCE;
+ }
+
+ public float[] toRGB(float[] colorvalue) {
+ return new float[] {
+ (1 - colorvalue[0]) * (1 - colorvalue[3]),
+ (1 - colorvalue[1]) * (1 - colorvalue[3]),
+ (1 - colorvalue[2]) * (1 - colorvalue[3])
+ };
+ // TODO: Convert via CIEXYZ space using sRGB space, as suggested in docs
+ // return sRGB.fromCIEXYZ(toCIEXYZ(colorvalue));
+ }
+
+ public float[] fromRGB(float[] rgbvalue) {
+ // Compute CMY
+ float c = 1 - rgbvalue[0];
+ float m = 1 - rgbvalue[1];
+ float y = 1 - rgbvalue[2];
+
+ // Find K
+ float k = Math.min(c, Math.min(m, y));
+
+ // Convert to CMYK values
+ return new float[] {(c - k), (m - k), (y - k), k};
+/*
+http://www.velocityreviews.com/forums/t127265-rgb-to-cmyk.html
+
+(Step 0: Normalize R,G, and B values to fit into range [0.0 ... 1.0], or
+adapt the following matrix.)
+
+Step 1: RGB to CMY
+
+| C | | 1 | | R |
+| M | = | 1 | - | G |
+| Y | | 1 | | B |
+
+Step 2: CMY to CMYK
+
+| C' | | C | | min(C,M,Y) |
+| M' | | M | | min(C,M,Y) |
+| Y' | = | Y | - | min(C,M,Y) |
+| K' | | min(C,M,Y) | | 0 |
+
+Easier to calculate if K' is calculated first, because K' = min(C,M,Y):
+
+| C' | | C | | K' |
+| M' | | M | | K' |
+| Y' | = | Y | - | K' |
+| K' | | K'| | 0 |
+ */
+// return fromCIEXYZ(sRGB.toCIEXYZ(rgbvalue));
+ }
+
+ public float[] toCIEXYZ(float[] colorvalue) {
+ throw new UnsupportedOperationException("Method toCIEXYZ not implemented"); // TODO: Implement
+ }
+
+ public float[] fromCIEXYZ(float[] colorvalue) {
+ throw new UnsupportedOperationException("Method fromCIEXYZ not implemented"); // TODO: Implement
+ }
+}
diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/ICCProfile.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/ICCProfile.java
new file mode 100755
index 00000000..88474984
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/ICCProfile.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2008, 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.psd;
+
+import com.twelvemonkeys.imageio.util.IIOUtil;
+
+import javax.imageio.stream.ImageInputStream;
+import java.awt.color.ICC_Profile;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * ICCProfile
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: ICCProfile.java,v 1.0 May 20, 2008 6:24:10 PM haraldk Exp$
+ */
+class ICCProfile extends PSDImageResource {
+ private ICC_Profile mProfile;
+
+ ICCProfile(final short pId, final ImageInputStream pInput) throws IOException {
+ super(pId, pInput);
+ }
+
+ @Override
+ protected void readData(ImageInputStream pInput) throws IOException {
+ InputStream stream = IIOUtil.createStreamAdapter(pInput, mSize);
+ try {
+ mProfile = ICC_Profile.getInstance(stream);
+ }
+ finally {
+ // Make sure stream has correct position after read
+ stream.close();
+ }
+ }
+
+ public ICC_Profile getProfile() {
+ return mProfile;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = toStringBuilder();
+
+ builder.append(", profile: ").append(mProfile);
+ builder.append("]");
+
+ return builder.toString();
+ }
+
+}
diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSD.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSD.java
new file mode 100755
index 00000000..30c77218
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSD.java
@@ -0,0 +1,544 @@
+/*
+ * Copyright (c) 2008, 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.psd;
+
+/**
+ * PSD format constants.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: PSD.java,v 1.0 Apr 29, 2008 4:47:47 PM haraldk Exp$
+ *
+ * @see http://www.fileformat.info/format/psd/egff.htm
+ */
+interface PSD {
+ /** PSD 2+ Native format (.PSD) identifier "8BPS" */
+ int SIGNATURE_8BPS = ('8' << 24) + ('B' << 16) + ('P' << 8) + 'S';
+
+ /** PSD 5+ Large Document Format (.PSB) identifier "8BPB" */
+ int SIGNATURE_8BPB = ('8' << 24) + ('B' << 16) + ('P' << 8) + 'B';
+
+ /** PSD Resource type identifier "8BIM" */
+ int RESOURCE_TYPE = ('8' << 24) + ('B' << 16) + ('I' << 8) + 'M';
+
+ // Blending modes
+ /** Normal blending mode "norm"*/
+ int BLEND_NORM = ('n' << 24) + ('o' << 16) + ('r' << 8) + 'm';
+
+ /** Darken blending mode "dark" */
+ int BLEND_DARK = ('d' << 24) + ('a' << 16) + ('r' << 8) + 'k';
+
+ /** Lighten blending mode "lite" */
+ int BLEND_LITE = ('l' << 24) + ('i' << 16) + ('t' << 8) + 'e';
+
+ /** Hue blending mode "hue " */
+ int BLEND_HUE = ('h' << 24) + ('u' << 16) + ('e' << 8) + ' ';
+
+ /** Saturation blending mode "sat " */
+ int BLEND_SAT = ('s' << 24) + ('a' << 16) + ('t' << 8) + ' ';
+
+ /** Color blending mode "colr" */
+ int BLEND_COLR = ('c' << 24) + ('o' << 16) + ('l' << 8) + 'r';
+
+ /** Luminosity blending mode "lum " */
+ int BLEND_LUM = ('l' << 24) + ('u' << 16) + ('m' << 8) + ' ';
+
+ /** Multiply blending mode "mul " */
+ int BELND_MUL = ('m' << 24) + ('u' << 16) + ('l' << 8) + ' ';
+
+ /** Screen blending mode "scrn" */
+ int BLEND_SCRN = ('s' << 24) + ('c' << 16) + ('r' << 8) + 'n';
+
+ /** Dissolve blending mode "diss" */
+ int BLEND_DISS = ('d' << 24) + ('i' << 16) + ('s' << 8) + 's';
+
+ /** Overlay blending mode "over" */
+ int BLEND_OVER = ('o' << 24) + ('v' << 16) + ('e' << 8) + 'r';
+
+ /** Hard light blending mode "hLit" */
+ int BLEND_HLIT = ('h' << 24) + ('L' << 16) + ('i' << 8) + 't';
+
+ /** Soft light blending mode "sLit" */
+ int BLEND_SLIT = ('s' << 24) + ('L' << 16) + ('i' << 8) + 't';
+
+ /** Difference blending mode "diff" */
+ int BLEND_DIFF = ('d' << 24) + ('i' << 16) + ('f' << 8) + 'f';
+
+ // Compression modes
+ /** No compression */
+ int COMPRESSION_NONE = 0;
+
+ /** PacBits RLE compression */
+ int COMPRESSION_RLE = 1;
+
+ /** ZIP compression */
+ int COMPRESSION_ZIP = 2;
+
+ /** ZIP compression with prediction */
+ int COMPRESSION_ZIP_PREDICTON = 3;
+
+ // Color Modes
+ /** Bitmap (monochrome) */
+ short COLOR_MODE_MONOCHROME = 0;
+
+ /** Gray-scale */
+ short COLOR_MODE_GRAYSCALE = 1;
+
+ /** Indexed color (palette color) */
+ short COLOR_MODE_INDEXED = 2;
+
+ /** RGB color */
+ short COLOR_MODE_RGB = 3;
+
+ /** CMYK color */
+ short COLOR_MODE_CMYK = 4;
+
+ /** Multichannel color */
+ short COLOR_MODE_MULTICHANNEL = 7;
+
+ /** Duotone (halftone) */
+ short COLOR_MODE_DUOTONE = 8;
+
+ /** Lab color */
+ short COLOR_MODE_LAB = 9;
+
+ // TODO: Consider moving these constants to PSDImageResource
+ // ID values 03e8, 03eb, 03ff, and 0403 are considered obsolete. Values 03e8 and 03eb are associated with
+ // Photoshop v2.0. The data format for values 03f2, 03f4-03fa, 03fc, 03fd, 0405-0bb7 is intentionally not
+ // documented by Adobe, or the data is missing.
+ // Please refer to the Adobe Photoshop SDK for information on obtaining the IPTC-NAA record 2 structure definition.
+ // WORD[5]
+ /** Channels, rows, columns, depth, and mode (Obsolete?Photoshop 2.0 only). */
+ int RES_CHANNELS_ROWS_COLUMNS_DEPTH_MODE = 0x03e8;
+
+ /** Optional Macintosh print manager information. */
+ int RES_MAC_PRINT_MANAGER_INFO = 0x03e9;
+
+ /** Indexed color table (Obsolete?Photoshop 2.0 only). */
+ int RES_INDEXED_COLOR_TABLE = 0x03eb;
+
+ /** Resolution information
+ // ID value 03ed indicates that the data is in the form of a ResolutionInfo structure:
+ //
+ // typedef struct _ResolutionInfo
+ // {
+ // LONG hRes; // Fixed-point number: pixels per inch
+ // WORD hResUnit; // 1=pixels per inch, 2=pixels per centimeter
+ // WORD WidthUnit; // 1=in, 2=cm, 3=pt, 4=picas, 5=columns
+ // LONG vRes; // Fixed-point number: pixels per inch/
+ // WORD vResUnit; // 1=pixels per inch, 2=pixels per centimeter
+ // WORD HeightUnit; // 1=in, 2=cm, 3=pt, 4=picas, 5=columns
+ // } RESOLUTIONINFO;
+ */
+ int RES_RESOLUTION_INFO = 0x3ed;
+
+ /** Alpha channel names (Pascal-format strings) */
+ int RES_ALPHA_CHANNEL_INFO = 0x3ee;
+
+ /** Display information for each channel
+ // ID value 03ef indicates that the data is stored as a DisplayInfo structure, which contains display information
+ // associated with each channel:
+ //
+ // typedef _DisplayInfo
+ // {
+ // WORD ColorSpace;
+ // WORD Color[4];
+ // WORD Opacity; // 0-100
+ // BYTE Kind; // 0=selected, 1=protected
+ // BYTE Padding; // Always zero
+ // } DISPLAYINFO;
+ //
+ */
+ int RES_DISPLAY_INFO = 0x3ef;
+
+ // 03f0
+ // BYTE[]
+ /** Optional Pascal-format caption string */
+ int RES_CAPTION = 0x03f0;
+
+ // 03f1
+ // LONG, WORD
+ /** Fixed-point border width, border units */
+ int RES_BORDER_WIDTH = 0x03f1;
+
+ // 03f2
+ /** Background color */
+ // 2 byte Color space: 0 = RGB (unsigned 16 bit), 1 = HSB (unsigned 16 bit), 2 = CMYK (unsigned 16 bit),
+ // 3 = Pantone matching system (undocumented), 4 = Focoltone colour system (undocumented),
+ // 5 = Truematch color (undocumented), 6 = Toyo 88 colorfinder 1050 (undocumented),
+ // 7 = Lab (lighntess 0...10000, chrominance -12800..127000, 8 = Grayscale 0...10000,
+ // 10 = HKS colors
+ // 8 byte Color data: 6 first bytes used for RGB, HSB and Lab, all 8 for CMYK and only first two for grayscale
+ int RES_BACKGROUND_COLOR = 0x03f2;
+
+ // 03f3
+ // BYTE[8]
+ /**
+ * Print flags.
+ * ID value 03f3 indicates that the data is a series of eight flags, indicating the enabled state of labels,
+ * crop marks, color bars, registration marks, negative, flip, interpolate, and caption items in the
+ * Photoshop Page Setup dialog box.
+ */
+ int RES_PRINT_FLAGS = 0x03f3;
+
+ // 03f4
+ /** Gray-scale and halftoning information */
+ int RES_GRAYSCALE_HALFTONE_INFO = 0x03f4;
+
+ // 03f5
+ /** Color halftoning information */
+ int RES_COLOR_HALFTONE_INFO = 0x03f5;
+
+ // 03f6
+ /** Duotone halftoning information */
+ int RES_DUOTONE_HALFTONE_INFO = 0x03f6;
+
+ // 03f7
+ /** Gray-scale and multichannel transfer function */
+ int RES_GRAYSCALE_MULTICHANNEL_TRANSFER_FUNCTION = 0x03f7;
+
+ // 03f8
+ /** Color transfer functions */
+ int RES_COLOR_TRANSFER_FUNCITON = 0x03f8;
+
+ // 03f9
+ /** Duotone transfer functions */
+ int RES_DUOTONE_TRANSFER_FUNCITON = 0x03f9;
+
+ // 03fa
+ /** Duotone image information */
+ int RES_DUOTONE_IMAGE_INFO = 0x03fa;
+
+ // 03fb
+ // BYTE[2]
+ /** Effective black and white value for dot range */
+ int RES_EFFECTIVE_BLACK_WHITE = 0x03fb;
+
+ // 03fc
+ /** Obsolete undocumented resource. */
+ int RES_03FC = 0x03fc;
+
+ // 03fd
+ /** EPS options */
+ int RES_EPS_OPTIONS = 0x03fd;
+
+ // 03fe
+ // WORD, BYTE
+ /** Quick Mask channel ID, flag for mask initially empty */
+ int RES_QUICK_MASK_CHANNEL_ID = 0x03fe;
+
+ // 03ff
+ /** Obsolete undocumented resource. */
+ int RES_03ff = 0x03ff;
+
+ // 0400
+ // WORD
+ /** Index of target layer (0=bottom)*/
+ int RES_INDEX_OF_TARGET_LAYER = 0x0400;
+
+ // 0401
+ /** Working path */
+ int RES_WORKING_PATH = 0x0401;
+
+ // 0402
+ // WORD[]
+ /** Layers group info, group ID for dragging groups */
+ int RES_LAYERS_GROUP_INFO = 0x0402;
+
+ //
+ // 0403
+ /** Obsolete undocumented resource. */
+ int RES_0403 = 0x0403;
+
+ // 0404
+ /** IPTC-NAA record */
+ int RES_IPTC_NAA = 0x0404;
+
+ // 0405
+ /** Image mode for raw-format files */
+ int RES_RAW_IMAGE_MODE = 0x0405;
+
+ // 0406
+ /** JPEG quality (Adobe internal) */
+ int RES_JPEG_QUALITY = 0x0406;
+
+ // 1032
+ /** (Photoshop 4.0) Grid and guides information */
+ int RES_GRID_AND_GUIDES_INFO = 0x0408;
+
+ // 1033
+ /**
+ * (Photoshop 4.0) Thumbnail resource for Photoshop 4.0 only. BGR layout. Obsolete.
+ * @see #RES_THUMBNAIL
+ */
+ int RES_THUMBNAIL_PS4 = 0x0409;
+
+ // 1034
+ /**
+ * (Photoshop 4.0) Copyright flag
+ * Boolean indicating whether image is copyrighted. Can be set via
+ * Property suite or by user in File Info...
+ */
+ int RES_COPYRIGHT_FLAG = 0x040A;
+
+ // 1035
+ /**
+ * (Photoshop 4.0) URL
+ * Handle of a text string with uniform resource locator. Can be set via
+ * Property suite or by user in File Info...
+ */
+ int RES_URL = 0x040B;
+
+ // 1036
+ /** (Photoshop 5.0) Thumbnail resource (supersedes resource 1033) */
+ int RES_THUMBNAIL = 0x040C;
+
+ // 1037
+ /**
+ * (Photoshop 5.0) Global Angle
+ * 4 bytes that contain an integer between 0 and 359, which is the global
+ * lighting angle for effects layer. If not present, assumed to be 30.
+ */
+ int RES_GLOBAL_ANGLE = 0x040D;
+
+ // 1038
+ /**
+ * (Photoshop 5.0) Color samplers resource
+ * See "Color samplers resource format" on page20.
+ */
+ int RES_COLOR_SAMPLERS = 0x040E;
+
+ /**
+ * (Photoshop 5.0) ICC Profile
+ * The raw bytes of an ICC (International Color Consortium) format profile.
+ */
+ int RES_ICC_PROFILE = 0x040f;
+
+ // 1040
+ /**
+ * (Photoshop 5.0) Watermark
+ * One byte.
+ */
+ int RES_WATERMARK = 0x0410;
+
+ // 1041
+ /**
+ * (Photoshop 5.0) ICC Untagged Profile
+ * 1 byte that disables any assumed profile handling when opening the file.
+ * 1 = intentionally untagged.
+ */
+ int RES_ICC_UNTAGGED_PROFILE = 0x0411;
+
+ // 1042
+ /**
+ * (Photoshop 5.0) Effects visible
+ * 1-byte global flag to show/hide all the effects layer. Only present when
+ * they are hidden.
+ */
+ int RES_EFFECTS_VISIBLE = 0x0412;
+
+ // 1043
+ /**
+ * (Photoshop 5.0) Spot Halftone
+ * 4 bytes for version, 4 bytes for length, and the variable length data.
+ */
+ int RES_SPOT_HALFTONE = 0x0413;
+
+ // 1044
+ /**
+ * (Photoshop 5.0) Document-specific IDs seed number
+ * 4 bytes: Base value, starting at which layer IDs will be generated (or a
+ * greater value if existing IDs already exceed it). Its purpose is to avoid the
+ * case where we add layers, flatten, save, open, and then add more layers
+ * that end up with the same IDs as the first set.
+ */
+ int RES_DOC_ID_SEED = 0x0414;
+
+ // 1045
+ /**
+ * (Photoshop 5.0) Unicode Alpha Names
+ * Unicode string (4 bytes length followed by string).
+ */
+ int RES_UNICODE_ALPHA_NAME = 0x0415;
+
+ // 1046
+ /**
+ * (Photoshop 6.0) Indexed Color Table Count
+ * 2 bytes for the number of colors in table that are actually defined
+ */
+ int RES_INDEXED_COLOR_TABLE_COUNT = 0x0416;
+
+ //1047
+ /**
+ * (Photoshop 6.0) Transparency Index.
+ * 2 bytes for the index of transparent color, if any.
+ */
+ int RES_TRANSPARENCY_INDEX = 0x0417;
+
+ //1049
+ /**
+ * (Photoshop 6.0) Global Altitude
+ * 4 byte entry for altitude
+ */
+ int RES_GLOBAL_ALTITUDE = 0x0419;
+
+ //1050
+ /**
+ * (Photoshop 6.0) Slices
+ */
+ int RES_SLICES = 0x041A;
+
+ //1051
+ /**
+ * (Photoshop 6.0) Workflow URL
+ * Unicode string
+ */
+ int RES_WORKFLOW_URL = 0x041B;
+
+ // 1052
+ /**
+ * (Photoshop 6.0) Jump To XPEP
+ * 2 bytes major version, 2 bytes minor version, 4 bytes count. Following is
+ * repeated for count: 4 bytes block size, 4 bytes key, if key = 'jtDd', then
+ * next is a Boolean for the dirty flag; otherwise its a 4 byte entry for the
+ * mod date.
+ */
+ int RES_JUMP_TO_XPEP = 0x041C;
+
+ // 1053
+ /**
+ * (Photoshop 6.0) Alpha Identifiers
+ * 4 bytes of length, followed by 4 bytes each for every alpha identifier.
+ */
+ int RES_ALPHA_IDENTIFIERS = 0x041D;
+
+ // 1054
+ /**
+ * (Photoshop 6.0) URL List
+ * 4 byte count of URLs, followed by 4 byte long, 4 byte ID, and Unicode
+ * string for each count.
+ */
+ int RES_URL_LIST = 0x041E;
+
+ // 1057
+ /**
+ * (Photoshop 6.0) Version Info
+ * 4 bytes version, 1 byte hasRealMergedData, Unicode string: writer
+ * name, Unicode string: reader name, 4 bytes file version.
+ */
+ int RES_VERSION_INFO = 0x0421;
+
+ // 1058
+ /**
+ * (Photoshop 7.0) EXIF data 1
+ *
+ * @see EXIF standard
+ */
+ int RES_EXIF_DATA_1 = 0x0422;
+
+ //1059
+ /**
+ * (Photoshop 7.0) EXIF data 3
+ *
+ * @see EXIF standard
+ */
+ int RES_EXIF_DATA_3 = 0x0423;
+
+ //1060
+ /**
+ * (Photoshop 7.0) XMP metadata
+ * File info as XML description.
+ *
+ * @see XMP standard
+ */
+ int RES_XMP_DATA = 0x0424;
+
+ // 1061
+ /**
+ * (Photoshop 7.0) Caption digest
+ * 16 bytes: RSA Data Security, MD5 message-digest algorithm
+ */
+ int RES_CAPTION_DIGEST = 0x0425;
+
+ // 1062
+ /**
+ * (Photoshop 7.0) Print scale
+ * 2 bytes style (0 = centered, 1 = size to fit, 2 = user defined). 4 bytes x
+ * location (floating point). 4 bytes y location (floating point). 4 bytes scale
+ * (floating point)
+ */
+ int RES_PRINT_SCALE = 0x0426;
+
+ // 1064
+ /**
+ * (Photoshop CS) Pixel Aspect Ratio
+ * 4 bytes (version = 1), 8 bytes double, x / y of a pixel
+ * 0x0429 1065 (Photoshop CS) Layer Comps
+ * 4 bytes (descriptor version = 16), Descriptor (see ?Descriptor structure?
+ * on page57)
+ */
+ int RES_PIXEL_ASPECT_RATIO = 0x0428;
+
+ // 1066
+ /**
+ * (Photoshop CS) Alternate Duotone Colors
+ * 2 bytes (version = 1), 2 bytes count, following is repeated for each count:
+ * [ Color: 2 bytes for space followed by 4 * 2 byte color component ],
+ * following this is another 2 byte count, usually 256, followed by Lab colors
+ * one byte each for L, a, b
+ * This resource is not read or used by Photoshop.
+ */
+ int RES_ALTERNATE_DUOTONE_COLORS = 0x042A;
+
+ // 1067
+ /**
+ * (Photoshop CS) Alternate Spot Colors
+ * 2 bytes (version = 1), 2 bytes channel count, following is repeated for
+ * each count: 4 bytes channel ID, Color: 2 bytes for space followed by 4 * 2
+ * byte color component
+ * This resource is not read or used by Photoshop.
+ */
+ int RES_ALTERNATE_SPOT_COLORS = 0x042B;
+
+ // 07d0-0bb6
+ /* Saved path information */
+
+ // 0bb7
+ /** Clipping path name */
+ int RES_CLIPPING_PATH_NAME = 0x0bb7;
+
+ // 2710
+ /** Print flags information
+ * ID value 2710 signals that the Data section contains a WORD-length version number (should be 1),
+ * a BYTE-length flag indicating crop marks, a BYTE-length field (should be 0), a LONG-length bleed width value, and a
+ * WORD indicating the bleed width scale.
+ */
+ int RES_PRINT_FLAGS_INFORMATION = 0x2710;
+
+}
diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDAlphaChannelInfo.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDAlphaChannelInfo.java
new file mode 100755
index 00000000..7d3ae079
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDAlphaChannelInfo.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2008, 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.psd;
+
+import javax.imageio.stream.ImageInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * PSDAlhpaChannelInfo
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: PSDAlhpaChannelInfo.java,v 1.0 May 2, 2008 5:33:40 PM haraldk Exp$
+ */
+class PSDAlphaChannelInfo extends PSDImageResource {
+ List mNames;
+
+ public PSDAlphaChannelInfo(short pId, final ImageInputStream pInput) throws IOException {
+ super(pId, pInput);
+ }
+
+ @Override
+ protected void readData(ImageInputStream pInput) throws IOException {
+ mNames = new ArrayList();
+ long left = mSize;
+ while (left > 0) {
+ String name = PSDUtil.readPascalStringByte(pInput);
+ mNames.add(name);
+ left -= name.length() + 1;
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = toStringBuilder();
+ builder.append(", alpha channels: ").append(mNames).append("]");
+ return builder.toString();
+ }
+}
diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDChannelInfo.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDChannelInfo.java
new file mode 100755
index 00000000..0267dffc
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDChannelInfo.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2008, 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.psd;
+
+/**
+ * PSDChannelInfo
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: PSDChannelInfo.java,v 1.0 May 6, 2008 2:46:23 PM haraldk Exp$
+ */
+class PSDChannelInfo {
+ private short mChannelId;
+ long mLength;
+
+ // typedef struct _CLI
+ // {
+ // WORD ChannelID; /* Channel Length Info field one */
+ // LONG LengthOfChannelData; /* Channel Length Info field two */
+ // } CLI;
+ public PSDChannelInfo(short pChannelId, long pLength) {
+ mChannelId = pChannelId;
+ mLength = pLength;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder(getClass().getSimpleName());
+ builder.append("[");
+ builder.append("channelId: ").append(mChannelId);
+ builder.append(", length: ").append(mLength);
+ builder.append("]");
+ return builder.toString();
+ }
+}
diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDChannelSourceDestinationRange.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDChannelSourceDestinationRange.java
new file mode 100755
index 00000000..65735285
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDChannelSourceDestinationRange.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2008, 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.psd;
+
+import javax.imageio.stream.ImageInputStream;
+import java.io.IOException;
+
+/**
+ * PSDChannelSourceDestinationRange
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: PSDChannelSourceDestinationRange.java,v 1.0 May 6, 2008 5:14:13 PM haraldk Exp$
+ */
+class PSDChannelSourceDestinationRange {
+ private String mChannel;
+ private short mSourceBlack;
+ private short mSourceWhite;
+ private short mDestBlack;
+ private short mDestWhite;
+
+ public PSDChannelSourceDestinationRange(ImageInputStream pInput, String pChannel) throws IOException {
+ mChannel = pChannel;
+ mSourceBlack = pInput.readShort();
+ mSourceWhite = pInput.readShort();
+ mDestBlack = pInput.readShort();
+ mDestWhite = pInput.readShort();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder(getClass().getSimpleName());
+
+ builder.append("[(").append(mChannel);
+ builder.append("): sourceBlack: ").append(Integer.toHexString(mSourceBlack & 0xffff));
+ builder.append(", sourceWhite: ").append(Integer.toHexString(mSourceWhite & 0xffff));
+ builder.append(", destBlack: ").append(Integer.toHexString(mDestBlack & 0xffff));
+ builder.append(", destWhite: ").append(Integer.toHexString(mDestWhite & 0xffff));
+ builder.append("]");
+
+ return builder.toString();
+ }
+}
diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDColorData.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDColorData.java
new file mode 100755
index 00000000..ecf1b00a
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDColorData.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2008, 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.psd;
+
+import com.twelvemonkeys.image.InverseColorMapIndexColorModel;
+
+import javax.imageio.IIOException;
+import javax.imageio.stream.ImageInputStream;
+import java.awt.image.DataBuffer;
+import java.awt.image.IndexColorModel;
+import java.io.IOException;
+
+/**
+ * PSDColorData
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: PSDColorData.java,v 1.0 Apr 29, 2008 5:33:01 PM haraldk Exp$
+ */
+class PSDColorData {
+ final byte[] mColors;
+ private IndexColorModel mColorModel;
+
+ PSDColorData(ImageInputStream pInput) throws IOException {
+ int length = pInput.readInt();
+ if (length == 0) {
+ throw new IIOException("No palette information in PSD");
+ }
+ else if (length % 3 != 0) {
+ throw new IIOException("Wrong palette information in PSD");
+ }
+
+ // NOTE: Spec says length may only be 768 bytes (256 RGB triplets)
+ mColors = new byte[length];
+ pInput.readFully(mColors);
+
+ // NOTE: Could be a padding byte here, if not even..
+ }
+
+ IndexColorModel getIndexColorModel() {
+ if (mColorModel == null) {
+ int[] rgb = toInterleavedRGB(mColors);
+ mColorModel = new InverseColorMapIndexColorModel(8, rgb.length, rgb, 0, false, -1, DataBuffer.TYPE_BYTE);
+ }
+ return mColorModel;
+ }
+
+ private int[] toInterleavedRGB(byte[] pColors) {
+ int[] rgb = new int[pColors.length / 3];
+ for (int i = 0; i < rgb.length; i++) {
+ // Pack the non-interleaved samples into interleaved form
+ int r = pColors[ i] & 0xff;
+ int g = pColors[ rgb.length + i] & 0xff;
+ int b = pColors[2 * rgb.length + i] & 0xff;
+
+ rgb[i] = (r << 16) | (g << 8) | b;
+ }
+ return rgb;
+ }
+}
diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDDisplayInfo.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDDisplayInfo.java
new file mode 100755
index 00000000..6b04cf12
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDDisplayInfo.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2008, 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.psd;
+
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.IIOException;
+import java.io.IOException;
+
+/**
+ * PSDResolutionInfo
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: PSDResolutionInfo.java,v 1.0 May 2, 2008 3:58:19 PM haraldk Exp$
+ */
+class PSDDisplayInfo extends PSDImageResource {
+ // TODO: Size of this struct should be 14.. Does not compute...
+ //typedef _DisplayInfo
+ //{
+ // WORD ColorSpace;
+ // WORD Color[4];
+ // WORD Opacity; /* 0-100 */
+ // BYTE Kind; /* 0=selected, 1=protected */
+ // BYTE Padding; /* Always zero */
+ //} DISPLAYINFO;
+
+ private int mColorSpace;
+ private short[] mColors;
+ private short mOpacity;
+ private byte mKind;
+
+ PSDDisplayInfo(final short pId, final ImageInputStream pInput) throws IOException {
+ super(pId, pInput);
+ }
+
+ @Override
+ protected void readData(ImageInputStream pInput) throws IOException {
+ if (mSize % 14 != 0) {
+ throw new IIOException("Display info length expected to be mod 14: " + mSize);
+ }
+
+// long left = mSize;
+// while (left > 0) {
+ mColorSpace = pInput.readShort();
+
+ // Color[4]...?
+ mColors = new short[4];
+ mColors[0] = pInput.readShort();
+ mColors[1] = pInput.readShort();
+ mColors[2] = pInput.readShort();
+ mColors[3] = pInput.readShort();
+
+ mOpacity = pInput.readShort();
+
+ mKind = pInput.readByte();
+
+ pInput.readByte(); // Pad
+// left -= 14;
+// }
+ pInput.skipBytes(mSize - 14);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = toStringBuilder();
+
+ builder.append(", ColorSpace: ").append(mColorSpace);
+ builder.append(", Colors: {");
+ builder.append(mColors[0]);
+ builder.append(", ");
+ builder.append(mColors[1]);
+ builder.append(", ");
+ builder.append(mColors[2]);
+ builder.append(", ");
+ builder.append(mColors[3]);
+ builder.append("}, Opacity: ").append(mOpacity);
+ builder.append(", Kind: ").append(kind(mKind));
+
+ builder.append("]");
+
+ return builder.toString();
+ }
+
+ private String kind(final byte pKind) {
+ switch (pKind) {
+ case 0:
+ return "selected";
+ case 1:
+ return "protected";
+ default:
+ return "unknown kind: " + Integer.toHexString(pKind & 0xff);
+ }
+ }
+}
\ No newline at end of file
diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java
new file mode 100755
index 00000000..84ebb4e4
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java
@@ -0,0 +1,327 @@
+package com.twelvemonkeys.imageio.plugins.psd;
+
+import com.twelvemonkeys.imageio.util.IIOUtil;
+import com.twelvemonkeys.lang.StringUtil;
+
+import javax.imageio.IIOException;
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.stream.MemoryCacheImageInputStream;
+import java.io.IOException;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * EXIF metadata.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: XMPData.java,v 1.0 Jul 28, 2009 5:50:34 PM haraldk Exp$
+ *
+ * @see Wikipedia
+ * @see Aware systems TIFF tag reference
+ * @see Adobe TIFF developer resources
+ */
+public final class PSDEXIF1Data extends PSDImageResource {
+// protected byte[] mData;
+ protected Directory mDirectory;
+
+ PSDEXIF1Data(final short pId, final ImageInputStream pInput) throws IOException {
+ super(pId, pInput);
+ }
+
+ @Override
+ protected void readData(final ImageInputStream pInput) throws IOException {
+ // This is in essence an embedded TIFF file.
+ // TODO: Extract TIFF parsing to more general purpose package
+ // TODO: Instead, read the byte data, store for later parsing
+ MemoryCacheImageInputStream stream = new MemoryCacheImageInputStream(IIOUtil.createStreamAdapter(pInput, mSize));
+
+ byte[] bom = new byte[2];
+ stream.readFully(bom);
+ if (bom[0] == 'I' && bom[1] == 'I') {
+ stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+ }
+ else if (!(bom[0] == 'M' && bom[1] == 'M')) {
+ throw new IIOException(String.format("Invalid byte order marker '%s'", StringUtil.decode(bom, 0, bom.length, "ASCII")));
+ }
+
+ if (stream.readUnsignedShort() != 42) {
+ throw new IIOException("Wrong TIFF magic in EXIF data.");
+ }
+
+ long directoryOffset = stream.readUnsignedInt();
+ mDirectory = Directory.read(stream, directoryOffset);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = toStringBuilder();
+
+ builder.append(", ").append(mDirectory);
+
+ builder.append("]");
+
+ return builder.toString();
+ }
+
+ // TIFF Image file directory (IFD)
+ private static class Directory {
+ List mEntries = new ArrayList();
+
+ private Directory() {}
+
+ public static Directory read(final ImageInputStream pInput, final long pOffset) throws IOException {
+ Directory directory = new Directory();
+
+ pInput.seek(pOffset);
+ int entryCount = pInput.readUnsignedShort();
+ for (int i = 0; i < entryCount; i++) {
+ directory.mEntries.add(Entry.read(pInput));
+ }
+
+ long nextOffset = pInput.readUnsignedInt();
+ if (nextOffset != 0) {
+ Directory next = Directory.read(pInput, nextOffset);
+ directory.mEntries.addAll(next.mEntries);
+ }
+
+ return directory;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Directory%s", mEntries);
+ }
+ }
+
+ // TIFF IFD Entry
+ private static class Entry {
+ private static final int EXIF_IFD = 0x8769;
+
+ private final static String[] TYPE_NAMES = {
+ "BYTE", "ASCII", "SHORT", "LONG", "RATIONAL",
+
+ "SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL", "FLOAT", "DOUBLE",
+ };
+
+ private final static int[] TYPE_LENGTHS = {
+ 1, 1, 2, 4, 8,
+
+ 1, 1, 2, 4, 8, 4, 8,
+ };
+
+ private int mTag;
+ /*
+ 1 = BYTE 8-bit unsigned integer.
+ 2 = ASCII 8-bit byte that contains a 7-bit ASCII code; the last byte
+ must be NUL (binary zero).
+ 3 = SHORT 16-bit (2-byte) unsigned integer.
+ 4 = LONG 32-bit (4-byte) unsigned integer.
+ 5 = RATIONAL Two LONGs: the first represents the numerator of a
+ fraction; the second, the denominator.
+
+ TIFF 6.0 and above:
+ 6 = SBYTE An 8-bit signed (twos-complement) integer.
+ 7 = UNDEFINED An 8-bit byte that may contain anything, depending on
+ the definition of the field.
+ 8 = SSHORT A 16-bit (2-byte) signed (twos-complement) integer.
+ 9 = SLONG A 32-bit (4-byte) signed (twos-complement) integer.
+ 10 = SRATIONAL Two SLONGs: the first represents the numerator of a
+ fraction, the second the denominator.
+ 11 = FLOAT Single precision (4-byte) IEEE format.
+ 12 = DOUBLE Double precision (8-byte) IEEE format.
+ */
+ private short mType;
+ private int mCount;
+ private long mValueOffset;
+ private Object mValue;
+
+ private Entry() {}
+
+ public static Entry read(final ImageInputStream pInput) throws IOException {
+ Entry entry = new Entry();
+
+ entry.mTag = pInput.readUnsignedShort();
+ entry.mType = pInput.readShort();
+ entry.mCount = pInput.readInt(); // Number of values
+
+ // TODO: Handle other sub-IFDs
+ if (entry.mTag == EXIF_IFD) {
+ long offset = pInput.readUnsignedInt();
+ pInput.mark();
+ try {
+ entry.mValue = Directory.read(pInput, offset);
+ }
+ finally {
+ pInput.reset();
+ }
+ }
+ else {
+ int valueLength = entry.getValueLength();
+ if (valueLength > 0 && valueLength <= 4) {
+ entry.readValueInLine(pInput);
+ pInput.skipBytes(4 - valueLength);
+ }
+ else {
+ entry.mValueOffset = pInput.readUnsignedInt(); // This is the *value* iff the value size is <= 4 bytes
+ entry.readValue(pInput);
+ }
+ }
+
+ return entry;
+ }
+
+ private void readValue(final ImageInputStream pInput) throws IOException {
+ long pos = pInput.getStreamPosition();
+ try {
+ pInput.seek(mValueOffset);
+ readValueInLine(pInput);
+ }
+ finally {
+ pInput.seek(pos);
+ }
+ }
+
+ private void readValueInLine(ImageInputStream pInput) throws IOException {
+ mValue = readValueDirect(pInput, mType, mCount);
+ }
+
+ private static Object readValueDirect(final ImageInputStream pInput, final short pType, final int pCount) throws IOException {
+ switch (pType) {
+ case 2:
+ // TODO: This might be UTF-8 or ISO-8859-1, even though against the spec
+ byte[] ascii = new byte[pCount];
+ pInput.readFully(ascii);
+ return StringUtil.decode(ascii, 0, ascii.length, "ASCII");
+ case 1:
+ if (pCount == 1) {
+ return pInput.readUnsignedByte();
+ }
+ case 6:
+ if (pCount == 1) {
+ return pInput.readByte();
+ }
+ case 7:
+ byte[] bytes = new byte[pCount];
+ pInput.readFully(bytes);
+ return bytes;
+ case 3:
+ if (pCount == 1) {
+ return pInput.readUnsignedShort();
+ }
+ case 8:
+ if (pCount == 1) {
+ return pInput.readShort();
+ }
+
+ short[] shorts = new short[pCount];
+ pInput.readFully(shorts, 0, shorts.length);
+ return shorts;
+ case 4:
+ if (pCount == 1) {
+ return pInput.readUnsignedInt();
+ }
+ case 9:
+ if (pCount == 1) {
+ return pInput.readInt();
+ }
+
+ int[] ints = new int[pCount];
+ pInput.readFully(ints, 0, ints.length);
+ return ints;
+ case 11:
+ if (pCount == 1) {
+ return pInput.readFloat();
+ }
+
+ float[] floats = new float[pCount];
+ pInput.readFully(floats, 0, floats.length);
+ return floats;
+ case 12:
+ if (pCount == 1) {
+ return pInput.readDouble();
+ }
+
+ double[] doubles = new double[pCount];
+ pInput.readFully(doubles, 0, doubles.length);
+ return doubles;
+
+ // TODO: Consider using a Rational class
+ case 5:
+ if (pCount == 1) {
+ return pInput.readUnsignedInt() / (double) pInput.readUnsignedInt();
+ }
+
+ double[] rationals = new double[pCount];
+ for (int i = 0; i < rationals.length; i++) {
+ rationals[i] = pInput.readUnsignedInt() / (double) pInput.readUnsignedInt();
+ }
+
+ return rationals;
+ case 10:
+ if (pCount == 1) {
+ return pInput.readInt() / (double) pInput.readInt();
+ }
+
+ double[] srationals = new double[pCount];
+ for (int i = 0; i < srationals.length; i++) {
+ srationals[i] = pInput.readInt() / (double) pInput.readInt();
+ }
+
+ return srationals;
+
+ default:
+ throw new IIOException(String.format("Unknown EXIF type '%s'", pType));
+ }
+ }
+
+ private int getValueLength() {
+ if (mType > 0 && mType <= TYPE_LENGTHS.length) {
+ return TYPE_LENGTHS[mType - 1] * mCount;
+ }
+ return -1;
+ }
+
+ private String getTypeName() {
+ if (mType > 0 && mType <= TYPE_NAMES.length) {
+ return TYPE_NAMES[mType - 1];
+ }
+ return "Unknown type";
+ }
+
+ // TODO: Tag names!
+ @Override
+ public String toString() {
+ return String.format("0x%04x: %s (%s, %d)", mTag, getValueAsString(), getTypeName(), mCount);
+ }
+
+ public String getValueAsString() {
+ if (mValue instanceof String) {
+ return String.format("\"%s\"", mValue);
+ }
+
+ if (mValue != null && mValue.getClass().isArray()) {
+ Class> type = mValue.getClass().getComponentType();
+ if (byte.class == type) {
+ return Arrays.toString((byte[]) mValue);
+ }
+ if (short.class == type) {
+ return Arrays.toString((short[]) mValue);
+ }
+ if (int.class == type) {
+ return Arrays.toString((int[]) mValue);
+ }
+ if (float.class == type) {
+ return Arrays.toString((float[]) mValue);
+ }
+ if (double.class == type) {
+ return Arrays.toString((double[]) mValue);
+ }
+ }
+
+ return String.valueOf(mValue);
+ }
+ }
+}
diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDGlobalLayerMask.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDGlobalLayerMask.java
new file mode 100755
index 00000000..3e919563
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDGlobalLayerMask.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2008, 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.psd;
+
+import javax.imageio.stream.ImageInputStream;
+import java.io.IOException;
+
+/**
+ * PSDGlobalLayerMask
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: PSDGlobalLayerMask.java,v 1.0 May 8, 2008 5:33:48 PM haraldk Exp$
+ */
+class PSDGlobalLayerMask {
+ private int mColorSpace;
+ private int mColor1;
+ private int mColor2;
+ private int mColor3;
+ private int mColor4;
+ private int mOpacity;
+ private int mKind;
+
+ PSDGlobalLayerMask(ImageInputStream pInput) throws IOException {
+ mColorSpace = pInput.readUnsignedShort();
+
+ mColor1 = pInput.readUnsignedShort();
+ mColor2 = pInput.readUnsignedShort();
+ mColor3 = pInput.readUnsignedShort();
+ mColor4 = pInput.readUnsignedShort();
+
+ mOpacity = pInput.readUnsignedShort();
+
+ mKind = pInput.readUnsignedByte();
+
+ pInput.readByte(); // Pad
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder(getClass().getSimpleName());
+ builder.append("[");
+ builder.append("color space: 0x").append(Integer.toHexString(mColorSpace));
+ builder.append(", colors: [0x").append(Integer.toHexString(mColor1));
+ builder.append(", 0x").append(Integer.toHexString(mColor2));
+ builder.append(", 0x").append(Integer.toHexString(mColor3));
+ builder.append(", 0x").append(Integer.toHexString(mColor4));
+ builder.append("], opacity: ").append(mOpacity);
+ builder.append(", kind: ").append(mKind);
+ builder.append("]");
+ return builder.toString();
+ }
+}
diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDHeader.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDHeader.java
new file mode 100755
index 00000000..ca37acfd
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDHeader.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2008, 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.psd;
+
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.IIOException;
+import java.io.IOException;
+
+/**
+ * PSDHeader
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: PSDHeader.java,v 1.0 Apr 29, 2008 5:18:22 PM haraldk Exp$
+ */
+class PSDHeader {
+// The header is 26 bytes in length and is structured as follows:
+//
+// typedef struct _PSD_HEADER
+// {
+// BYTE Signature[4]; /* File ID "8BPS" */
+// WORD Version; /* Version number, always 1 */
+// BYTE Reserved[6]; /* Reserved, must be zeroed */
+// WORD Channels; /* Number of color channels (1-24) including alpha
+// channels */
+// LONG Rows; /* Height of image in pixels (1-30000) */
+// LONG Columns; /* Width of image in pixels (1-30000) */
+// WORD Depth; /* Number of bits per channel (1, 8, and 16) */
+// WORD Mode; /* Color mode */
+// } PSD_HEADER;
+
+ final short mChannels;
+ final int mWidth;
+ final int mHeight;
+ final short mBits;
+ final short mMode;
+
+ PSDHeader(ImageInputStream pInput) throws IOException {
+ int signature = pInput.readInt();
+ if (signature != PSD.SIGNATURE_8BPS) {
+ throw new IIOException("Not a PSD document, expected signature \"8BPS\": \"" + PSDUtil.intToStr(signature) + "\" (0x" + Integer.toHexString(signature) + ")");
+ }
+
+ int version = pInput.readUnsignedShort();
+ if (version != 1) {
+ if (version == 2) {
+ throw new IIOException("Large Document Format (PSB) not supported yet.");
+ }
+ throw new IIOException("Unknown PSD version, expected 1 or 2: 0x" + Integer.toHexString(version));
+ }
+
+ byte[] reserved = new byte[6];
+ pInput.readFully(reserved);
+
+ mChannels = pInput.readShort();
+ mHeight = pInput.readInt(); // Rows
+ mWidth = pInput.readInt(); // Coloumns
+ mBits = pInput.readShort();
+ mMode = pInput.readShort();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder(getClass().getSimpleName());
+ builder.append("[Channels: ");
+ builder.append(mChannels);
+ builder.append(", width: ");
+ builder.append(mWidth);
+ builder.append(", height: ");
+ builder.append(mHeight);
+ builder.append(", depth: ");
+ builder.append(mBits);
+ builder.append(", mode: ");
+ builder.append(mMode);
+ switch (mMode) {
+ case PSD.COLOR_MODE_MONOCHROME:
+ builder.append(" (Monochrome)");
+ break;
+ case PSD.COLOR_MODE_GRAYSCALE:
+ builder.append(" (Grayscale)");
+ break;
+ case PSD.COLOR_MODE_INDEXED:
+ builder.append(" (Indexed)");
+ break;
+ case PSD.COLOR_MODE_RGB:
+ builder.append(" (RGB)");
+ break;
+ case PSD.COLOR_MODE_CMYK:
+ builder.append(" (CMYK)");
+ break;
+ case PSD.COLOR_MODE_MULTICHANNEL:
+ builder.append(" (Multi channel)");
+ break;
+ case PSD.COLOR_MODE_DUOTONE:
+ builder.append(" (Duotone)");
+ break;
+ case PSD.COLOR_MODE_LAB:
+ builder.append(" (Lab color)");
+ break;
+ default:
+ builder.append(" (Unkown mode)");
+ }
+ builder.append("]");
+
+ return builder.toString();
+ }
+}
diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java
new file mode 100755
index 00000000..7f5d1792
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java
@@ -0,0 +1,575 @@
+/*
+ * Copyright (c) 2008, 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.psd;
+
+import com.twelvemonkeys.image.ImageUtil;
+import com.twelvemonkeys.imageio.ImageReaderBase;
+import com.twelvemonkeys.imageio.util.IndexedImageTypeSpecifier;
+
+import javax.imageio.IIOException;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReadParam;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.stream.ImageInputStream;
+import java.awt.color.ColorSpace;
+import java.awt.color.ICC_ColorSpace;
+import java.awt.color.ICC_Profile;
+import java.awt.image.*;
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * ImageReader for Adobe Photoshop Document format.
+ *
+ * @see Adobe Photoshop File Format Summary
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: PSDImageReader.java,v 1.0 Apr 29, 2008 4:45:52 PM haraldk Exp$
+ */
+// TODO: Implement AOI and subsampling
+// TODO: Implement meta data reading
+// TODO: Implement layer reading
+// TODO: Allow reading separate (or some?) layers
+// TODO: Consider Romain Guy's Java 2D implementation of PS filters for the blending modes in layers
+// http://www.curious-creature.org/2006/09/20/new-blendings-modes-for-java2d/
+// See http://www.codeproject.com/KB/graphics/PSDParser.aspx
+// See http://www.adobeforums.com/webx?14@@.3bc381dc/0
+public class PSDImageReader extends ImageReaderBase {
+ private PSDHeader mHeader;
+ private PSDColorData mColorData;
+ private List mImageResources;
+ private PSDGlobalLayerMask mGlobalLayerMask;
+ private List mLayerInfo;
+ private ICC_ColorSpace mColorSpace;
+
+ protected PSDImageReader(final ImageReaderSpi pOriginatingProvider) {
+ super(pOriginatingProvider);
+ }
+
+ protected void resetMembers() {
+ mHeader = null;
+ mColorData = null;
+ mImageResources = null;
+ mColorSpace = null;
+ }
+
+ public int getWidth(int pIndex) throws IOException {
+ checkBounds(pIndex);
+ readHeader();
+ return mHeader.mWidth;
+ }
+
+ public int getHeight(int pIndex) throws IOException {
+ checkBounds(pIndex);
+ readHeader();
+ return mHeader.mHeight;
+ }
+
+ public Iterator getImageTypes(int pIndex) throws IOException {
+ checkBounds(pIndex);
+ readHeader();
+
+ ColorSpace cs;
+ List types = new ArrayList();
+
+ switch (mHeader.mMode) {
+ case PSD.COLOR_MODE_INDEXED:
+ if (mHeader.mChannels == 1 && mHeader.mBits == 8) {
+ types.add(IndexedImageTypeSpecifier.createFromIndexColorModel(mColorData.getIndexColorModel()));
+ }
+ else {
+ throw new IIOException("Unsupported channel count/bit depth for Indexed Color PSD: " + mHeader.mChannels + " channels/" + mHeader.mBits + " bits");
+ }
+ break;
+ case PSD.COLOR_MODE_DUOTONE:
+ // NOTE: Duotone (whatever that is) should be treated as grayscale, so fall-through
+ case PSD.COLOR_MODE_GRAYSCALE:
+ if (mHeader.mChannels == 1 && mHeader.mBits == 8) {
+ types.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY));
+ }
+ else if (mHeader.mChannels == 1 && mHeader.mBits == 16) {
+ types.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_USHORT_GRAY));
+ }
+ else {
+ throw new IIOException("Unsupported channel count/bit depth for Gray Scale PSD: " + mHeader.mChannels + " channels/" + mHeader.mBits + " bits");
+ }
+ break;
+ case PSD.COLOR_MODE_RGB:
+ cs = getEmbeddedColorSpace();
+ if (cs == null) {
+ cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
+ }
+
+ if (mHeader.mChannels == 3 && mHeader.mBits == 8) {
+// types.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR));
+ types.add(ImageTypeSpecifier.createInterleaved(cs, new int[] {2, 1, 0}, DataBuffer.TYPE_BYTE, false, false));
+ }
+ else if (mHeader.mChannels >= 4 && mHeader.mBits == 8) {
+// types.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR));
+ types.add(ImageTypeSpecifier.createInterleaved(cs, new int[] {3, 2, 1, 0}, DataBuffer.TYPE_BYTE, true, false));
+ }
+ else {
+ throw new IIOException("Unsupported channel count/bit depth for RGB PSD: " + mHeader.mChannels + " channels/" + mHeader.mBits + " bits");
+ }
+ break;
+ case PSD.COLOR_MODE_CMYK:
+ // TODO: We should convert these to their RGB equivalents while reading for the common-case,
+ // as Java2D is extremely slow displaying custom images.
+ // Converting to RGB is also correct behaviour, according to the docs.
+ // The code below is, however, correct for raw type.
+ cs = getEmbeddedColorSpace();
+ if (cs == null) {
+ cs = CMYKColorSpace.getInstance();
+ }
+
+ if (mHeader.mChannels == 4 && mHeader.mBits == 8) {
+// types.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR));
+ types.add(ImageTypeSpecifier.createInterleaved(cs, new int[]{3, 2, 1, 0}, DataBuffer.TYPE_BYTE, false, false));
+ }
+ else if (mHeader.mChannels == 5 && mHeader.mBits == 8) {
+// types.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR));
+ types.add(ImageTypeSpecifier.createInterleaved(cs, new int[]{4, 3, 2, 1, 0}, DataBuffer.TYPE_BYTE, true, false));
+ }
+ else {
+ throw new IIOException("Unsupported channel count/bit depth for CMYK PSD: " + mHeader.mChannels + " channels/" + mHeader.mBits + " bits");
+ }
+ break;
+ default:
+ throw new IIOException("Unsupported PSD MODE: " + mHeader.mMode);
+ }
+
+ return types.iterator();
+ }
+
+ private ColorSpace getEmbeddedColorSpace() throws IOException {
+ readImageResources(true);
+ // TODO: Skip this, requires storing some stream offsets
+ readLayerAndMaskInfo(false);
+
+ if (mColorSpace == null) {
+ ICC_Profile profile = null;
+ for (PSDImageResource resource : mImageResources) {
+ if (resource instanceof ICCProfile) {
+ profile = ((ICCProfile) resource).getProfile();
+ break;
+ }
+ }
+
+ mColorSpace = profile == null ? null : new ICC_ColorSpace(profile);
+ }
+
+ return mColorSpace;
+ }
+
+ // TODO: Implement param handling
+ public BufferedImage read(int pIndex, ImageReadParam pParam) throws IOException {
+ checkBounds(pIndex);
+
+ readHeader();
+
+ processImageStarted(pIndex);
+
+ readImageResources(false);
+ readLayerAndMaskInfo(false);
+
+ BufferedImage image = getDestination(pParam, getImageTypes(pIndex), mHeader.mWidth, mHeader.mHeight);
+ // TODO: Should do color convert op for CMYK -> RGB
+ ColorModel cm = image.getColorModel();
+ final boolean isCMYK = cm.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
+ final int numColorComponents = cm.getColorSpace().getNumComponents();
+
+ WritableRaster raster = image.getRaster();
+ if (!(raster.getDataBuffer() instanceof DataBufferByte)) {
+ throw new IIOException("Unsupported raster type: " + raster);
+ }
+ byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
+
+ // TODO: Maybe a banded raster would be easier than interleaved?
+ final int channels = raster.getNumBands();
+
+// System.out.println("channels: " + channels);
+// System.out.println("numColorComponents: " + numColorComponents);
+// System.out.println("isCMYK: " + isCMYK);
+
+ short compression = mImageInput.readShort();
+
+ // TODO: Bitmap (depth = 1) and 16 bit (depth = 16) must be read differently, obviously...
+ // This code works fine for images with channel depth = 8
+ switch (compression) {
+ case PSD.COMPRESSION_NONE:
+ // TODO: This entire reading block is duplicated and should be replaced with the one for RLE!
+// System.out.println("Uncompressed");
+ for (int c = 0; c < mHeader.mChannels; c++) {
+ for (int y = 0; y < mHeader.mHeight; y++) {
+ for (int x = 0; x < mHeader.mWidth; x++) {
+ int offset = (x + y * mHeader.mWidth) * channels;
+
+ byte value = mImageInput.readByte();
+
+ // CMYK values are stored inverted, but alpha is not
+ if (isCMYK && c < numColorComponents) {
+ value = (byte) (255 - value & 0xff);
+ }
+
+// System.out.println("b: " + Integer.toHexString(b & 0xff));
+ data[offset + (channels - 1 - c)] = value;
+ }
+
+ if (abortRequested()) {
+ break;
+ }
+ processImageProgress((c * y * 100) / mHeader.mChannels * mHeader.mHeight);
+ }
+ if (abortRequested()) {
+ break;
+ }
+ }
+ break;
+ case PSD.COMPRESSION_RLE:
+// System.out.println("PackBits compressed");
+ // NOTE: Offsets will allow us to easily skip rows before AOI
+ int[] offsets = new int[mHeader.mChannels * mHeader.mHeight];
+ for (int i = 0; i < offsets.length; i++) {
+ offsets[i] = mImageInput.readUnsignedShort();
+ }
+
+ int x = 0, y = 0, c = 0;
+ try {
+ for (c = 0; c < channels; c++) {
+ for (y = 0; y < mHeader.mHeight; y++) {
+ int length = offsets[c * mHeader.mHeight + y];
+// System.out.println("channel: " + c + " line: " + y + " length: " + length);
+ DataInputStream input = PSDUtil.createPackBitsStream(mImageInput, length);
+ for (x = 0; x < mHeader.mWidth; x++) {
+ int offset = (x + y * mHeader.mWidth) * channels;
+
+ byte value = input.readByte();
+
+// if (c < numColorComponents) {
+// continue;
+// }
+
+ // CMYK values are stored inverted, but alpha is not
+ if (isCMYK && c < numColorComponents) {
+ value = (byte) (255 - value & 0xff);
+ }
+
+// System.out.println("b: " + Integer.toHexString(b & 0xff));
+ data[offset + (channels - 1 - c)] = value;
+ }
+ input.close();
+
+ if (abortRequested()) {
+ break;
+ }
+ processImageProgress((c * y * 100) / mHeader.mChannels * mHeader.mHeight);
+ }
+ if (abortRequested()) {
+ break;
+ }
+ }
+ }
+ catch (IOException e) {
+ System.err.println("c: " + c);
+ System.err.println("y: " + y);
+ System.err.println("x: " + x);
+ throw e;
+ }
+ catch (IndexOutOfBoundsException e) {
+ e.printStackTrace();
+ System.out.println("data.length: " + data.length);
+ System.err.println("c: " + c);
+ System.err.println("y: " + y);
+ System.err.println("x: " + x);
+ throw e;
+ }
+ break;
+ case PSD.COMPRESSION_ZIP:
+ // TODO: Could probably use the ZIPDecoder (DeflateDecoder) here..
+ case PSD.COMPRESSION_ZIP_PREDICTON:
+ // TODO: Need to find out if the normal java.util.zip can handle this...
+ // Could be same as PNG prediction? Read up...
+ throw new IIOException("ZIP compression not supported yet");
+ default:
+ throw new IIOException("Unknown compression type: " + compression);
+ }
+
+ // Compose out the background of the semi-transparent pixels, as PS somehow has the background composed in
+ decomposeAlpha(image);
+
+ if (abortRequested()) {
+ processReadAborted();
+ }
+ else {
+ processImageComplete();
+ }
+
+ return image;
+ }
+
+ private void decomposeAlpha(final BufferedImage pImage) throws IOException {
+ ColorModel cm = pImage.getColorModel();
+
+ // TODO: What about CMYK + alpha?
+ if (cm.hasAlpha() && cm.getColorSpace().getType() == ColorSpace.TYPE_RGB) {
+ WritableRaster raster = pImage.getRaster();
+
+ // TODO: Probably faster to do this inline..
+ // TODO: This is not so good, as it might break acceleration...
+ byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
+
+ final int w = pImage.getWidth();
+ final int channels = raster.getNumBands();
+ for (int y = 0; y < pImage.getHeight(); y++) {
+ for (int x = 0; x < w; x++) {
+ int offset = (x + y * w) * channels;
+
+ // TODO: Is the document background always white!?
+ // ABGR format
+ int alpha = data[offset] & 0xff;
+ if (alpha != 0) {
+ double normalizedAlpha = alpha / 255.0;
+ for (int i = 1; i < channels; i++) {
+ data[offset + i] = decompose(data[offset + i] & 0xff, normalizedAlpha);
+ }
+ }
+ else {
+ for (int i = 1; i < channels; i++) {
+ data[offset + i] = 0;
+ }
+ }
+ }
+ }
+
+ }
+// System.out.println("PSDImageReader.coerceData: " + cm.getClass());
+// System.out.println("other.equals(cm): " + (other == cm));
+ }
+
+ private static byte decompose(final int pColor, final double pAlpha) {
+ // Adapted from Computer Graphics: Principles and Practice (Foley et al.), p. 837
+ double color = pColor / 255.0;
+ return (byte) ((color / pAlpha - ((1 - pAlpha) / pAlpha)) * 255);
+ }
+
+ private void readHeader() throws IOException {
+ assertInput();
+ if (mHeader == null) {
+ mHeader = new PSDHeader(mImageInput);
+
+ /*
+ Contains the required data to define the color mode.
+
+ For indexed color images, the count will be equal to 768, and the mode data
+ will contain the color table for the image, in non-interleaved order.
+
+ For duotone images, the mode data will contain the duotone specification,
+ the format of which is not documented. Non-Photoshop readers can treat
+ the duotone image as a grayscale image, and keep the duotone specification
+ around as a black box for use when saving the file.
+ */
+ if (mHeader.mMode == PSD.COLOR_MODE_INDEXED) {
+ mColorData = new PSDColorData(mImageInput);
+ }
+ else {
+ // Skip color mode data for other modes
+ long length = mImageInput.readUnsignedInt();
+ mImageInput.skipBytes(length);
+ }
+
+ // Don't need the header again
+ mImageInput.flushBefore(mImageInput.getStreamPosition());
+ }
+ }
+
+ private void readImageResources(boolean pParseData) throws IOException {
+ // TODO: Avoid unnecessary stream repositioning
+ long pos = mImageInput.getFlushedPosition();
+ mImageInput.seek(pos);
+
+ long length = mImageInput.readUnsignedInt();
+
+ if (pParseData && length > 0) {
+ if (mImageResources == null) {
+ mImageResources = new ArrayList();
+ long expectedEnd = mImageInput.getStreamPosition() + length;
+ while (mImageInput.getStreamPosition() < expectedEnd) {
+ PSDImageResource resource = PSDImageResource.read(mImageInput);
+ mImageResources.add(resource);
+ }
+
+ if (mImageInput.getStreamPosition() != expectedEnd) {
+ throw new IIOException("Corrupt PSD document");
+ }
+ }
+ }
+
+ mImageInput.seek(pos + length + 4);
+ }
+
+ private void readLayerAndMaskInfo(boolean pParseData) throws IOException {
+ // TODO: Make sure we are positioned correctly
+ long length = mImageInput.readUnsignedInt();
+ if (pParseData && length > 0) {
+ long pos = mImageInput.getStreamPosition();
+
+ long layerInfoLength = mImageInput.readUnsignedInt();
+
+ /*
+ "Layer count. If it is a negative number, its absolute value is the number of
+ layers and the first alpha channel contains the transparency data for the
+ merged result."
+ */
+ // TODO: Figure out what the last part of that sentence means in practice...
+ int layers = mImageInput.readShort();
+// System.out.println("layers: " + layers);
+
+ PSDLayerInfo[] layerInfo = new PSDLayerInfo[Math.abs(layers)];
+ for (int i = 0; i < layerInfo.length; i++) {
+ layerInfo[i] = new PSDLayerInfo(mImageInput);
+// System.out.println("layerInfo[" + i + "]: " + layerInfo[i]);
+ }
+ mLayerInfo = Arrays.asList(layerInfo);
+
+ for (PSDLayerInfo info : layerInfo) {
+ for (PSDChannelInfo channelInfo : info.mChannelInfo) {
+ int compression = mImageInput.readUnsignedShort();
+ // 0: None, 1: PackBits RLE, 2: Zip, 3: Zip w/prediction
+ switch (compression) {
+ case PSD.COMPRESSION_NONE:
+// System.out.println("Compression: None");
+ break;
+ case PSD.COMPRESSION_RLE:
+// System.out.println("Compression: PackBits RLE");
+ break;
+ case PSD.COMPRESSION_ZIP:
+// System.out.println("Compression: ZIP");
+ break;
+ case PSD.COMPRESSION_ZIP_PREDICTON:
+// System.out.println("Compression: ZIP with prediction");
+ break;
+ default:
+ // TODO: Do we care, as we can just skip the data?
+ // We could issue a warning to the warning listener
+ throw new IIOException(String.format(
+ "Unknown PSD compression: %d. Expected 0 (none), 1 (RLE), 2 (ZIP) or 3 (ZIP w/prediction).",
+ compression
+ ));
+ }
+
+ // TODO: If RLE, the the image data starts with the byte counts
+ // for all the scan lines in the channel (LayerBottom*LayerTop), with
+ // each count stored as a two*byte value.
+ // if (compression == 1) {
+ // mImageInput.skipBytes(channelInfo.mLength);
+ // }
+
+ // TODO: Read channel image data (same format as composite image channel data)
+ mImageInput.skipBytes(channelInfo.mLength - 2);
+ // if (channelInfo.mLength % 2 != 0) {
+ // mImageInput.readByte();
+ // }
+ }
+ }
+
+ // TODO: We seem to have some alignment issues here...
+ // I'm always reading two bytes off..
+
+ long read = mImageInput.getStreamPosition() - pos;
+// System.out.println("layerInfoLength: " + layerInfoLength);
+// System.out.println("layer info read: " + (read - 4)); // - 4 for the layerInfoLength field itself
+ long diff = layerInfoLength - (read - 4);
+// System.out.println("diff: " + diff);
+ mImageInput.skipBytes(diff);
+
+ // TODO: Global LayerMaskInfo (18 bytes or more..?)
+ // 4 (length), 2 (colorSpace), 8 (4 * 2 byte color components), 2 (opacity %), 1 (kind), variable (pad)
+ long layerMaskInfoLength = mImageInput.readUnsignedInt();
+// System.out.println("GlobalLayerMaskInfo length: " + layerMaskInfoLength);
+ if (layerMaskInfoLength > 0) {
+ mGlobalLayerMask = new PSDGlobalLayerMask(mImageInput);
+ }
+
+ read = mImageInput.getStreamPosition() - pos;
+
+ long toSkip = length - read;
+// System.out.println("toSkip: " + toSkip);
+ mImageInput.skipBytes(toSkip);
+ }
+ else {
+ mImageInput.skipBytes(length);
+ }
+ }
+
+ public static void main(String[] pArgs) throws IOException {
+ PSDImageReader imageReader = new PSDImageReader(null);
+
+ File file = new File(pArgs[0]);
+ ImageInputStream stream = ImageIO.createImageInputStream(file);
+ imageReader.setInput(stream);
+ imageReader.readHeader();
+ System.out.println("imageReader.mHeader: " + imageReader.mHeader);
+
+ imageReader.readImageResources(true);
+ System.out.println("imageReader.mImageResources: " + imageReader.mImageResources);
+
+ imageReader.readLayerAndMaskInfo(true);
+ System.out.println("imageReader.mLayerInfo: " + imageReader.mLayerInfo);
+ System.out.println("imageReader.mGlobalLayerMask: " + imageReader.mGlobalLayerMask);
+
+ long start = System.currentTimeMillis();
+ ImageReadParam param = new ImageReadParam();
+// param.setSourceRegion(new Rectangle(100, 100, 300, 200));
+ BufferedImage image = imageReader.read(0, param);
+ System.out.println("time: " + (System.currentTimeMillis() - start));
+ System.out.println("image: " + image);
+
+ if (image.getColorModel().getColorSpace().getType() == ColorSpace.TYPE_CMYK) {
+ try {
+ ColorConvertOp op = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_sRGB), null);
+ image = op.filter(image, new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_4BYTE_ABGR_PRE));
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ image = ImageUtil.accelerate(image);
+ }
+ System.out.println("time: " + (System.currentTimeMillis() - start));
+ System.out.println("image: " + image);
+ }
+
+ showIt(image, file.getName());
+ }
+
+}
diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderSpi.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderSpi.java
new file mode 100755
index 00000000..79117fe1
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderSpi.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2008, 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.psd;
+
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.ImageReader;
+import java.io.IOException;
+import java.util.Locale;
+
+/**
+ * PSDImageReaderSpi
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: PSDImageReaderSpi.java,v 1.0 Apr 29, 2008 4:49:03 PM haraldk Exp$
+ */
+public class PSDImageReaderSpi extends ImageReaderSpi {
+
+ /**
+ * Creates an PSDImageReaderSpi
+ */
+ public PSDImageReaderSpi() {
+ super(
+ "TwelveMonkeys",
+ "2.0",
+ new String[]{"psd", "PSD"},
+ new String[]{"psd"},
+ new String[]{
+ "application/vnd.adobe.photoshop", // This one seems official, used in XMP
+ "image/x-psd", "application/x-photoshop", "image/x-photoshop"
+ },
+ "com.twelvemkonkeys.imageio.plugins.psd.PSDImageReader",
+ STANDARD_INPUT_TYPE,
+// new String[]{"com.twelvemkonkeys.imageio.plugins.psd.PSDImageWriterSpi"},
+ null,
+ true, null, null, null, null,
+ true, null, null, null, null
+ );
+ }
+
+ public boolean canDecodeInput(Object pSource) throws IOException {
+ if (!(pSource instanceof ImageInputStream)) {
+ return false;
+ }
+
+ ImageInputStream stream = (ImageInputStream) pSource;
+
+ stream.mark();
+ try {
+ return stream.readInt() == PSD.SIGNATURE_8BPS;
+ // TODO: Test more of the header, see PSDImageReader#readHeader
+ }
+ finally {
+ stream.reset();
+ }
+ }
+
+ public ImageReader createReaderInstance(Object pExtension) throws IOException {
+ return new PSDImageReader(this);
+ }
+
+ public String getDescription(Locale pLocale) {
+ return "Adobe Photoshop Document (PSD) image reader";
+ }
+}
diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageResource.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageResource.java
new file mode 100755
index 00000000..c5484460
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageResource.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2008, 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.psd;
+
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.IIOException;
+import java.io.IOException;
+import java.lang.reflect.Field;
+
+/**
+ * PSDImageResource
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: PSDImageResource.java,v 1.0 Apr 29, 2008 5:49:06 PM haraldk Exp$
+ */
+class PSDImageResource {
+ final short mId;
+ final String mName;
+ final long mSize;
+
+ PSDImageResource(final short pId, final ImageInputStream pInput) throws IOException {
+ mId = pId;
+
+ mName = PSDUtil.readPascalString(pInput);
+
+ mSize = pInput.readUnsignedInt();
+ readData(pInput);
+ // Data is even-padded
+ if (mSize % 2 != 0) {
+ pInput.read();
+ }
+ }
+
+ /**
+ * This default implementation simply skips the data.
+ *
+ * @param pInput the input
+ * @throws IOException if an I/O exception occurs
+ */
+ protected void readData(final ImageInputStream pInput) throws IOException {
+ // TODO: This design is ugly, as subclasses readData is invoked BEFORE their respective constructor...
+ pInput.skipBytes(mSize);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = toStringBuilder();
+
+ builder.append(", data length: ");
+ builder.append(mSize);
+ builder.append("]");
+
+ return builder.toString();
+ }
+
+ protected StringBuilder toStringBuilder() {
+ StringBuilder builder = new StringBuilder(getClass().getSimpleName());
+
+ builder.append(resourceTypeForId(mId));
+
+ builder.append("[ID: 0x");
+ builder.append(Integer.toHexString(mId));
+ if (mName != null && mName.trim().length() != 0) {
+ builder.append(", name: \"");
+ builder.append(mName);
+ builder.append("\"");
+ }
+
+ return builder;
+ }
+
+ static String resourceTypeForId(final short pId) {
+ switch (pId) {
+ case PSD.RES_RESOLUTION_INFO:
+ case PSD.RES_ALPHA_CHANNEL_INFO:
+ case PSD.RES_DISPLAY_INFO:
+ case PSD.RES_PRINT_FLAGS:
+ case PSD.RES_THUMBNAIL_PS4:
+ case PSD.RES_THUMBNAIL:
+ case PSD.RES_ICC_PROFILE:
+ case PSD.RES_EXIF_DATA_1:
+// case PSD.RES_EXIF_DATA_3:
+ case PSD.RES_XMP_DATA:
+ case PSD.RES_PRINT_FLAGS_INFORMATION:
+ return "";
+ default:
+ try {
+ for (Field field : PSD.class.getDeclaredFields()) {
+ if (field.getName().startsWith("RES_") && field.getInt(null) == pId) {
+ return "(" + field.getName().substring(4) + ")";
+ }
+ }
+ }
+ catch (IllegalAccessException ignore) {
+ }
+
+ return "(unknown resource)";
+ }
+ }
+
+ public static PSDImageResource read(final ImageInputStream pInput) throws IOException {
+ int type = pInput.readInt();
+ if (type != PSD.RESOURCE_TYPE) {
+ throw new IIOException("Wrong image resource type, expected 8BIM: " + PSDUtil.intToStr(type));
+ }
+
+ // TODO: Process more of the resource stuff, most important are IPTC, EXIF and XMP data,
+ // version info, and thumbnail for thumbnail-support.
+ short id = pInput.readShort();
+ switch (id) {
+ case PSD.RES_RESOLUTION_INFO:
+ return new PSDResolutionInfo(id, pInput);
+ case PSD.RES_ALPHA_CHANNEL_INFO:
+ return new PSDAlphaChannelInfo(id, pInput);
+ case PSD.RES_DISPLAY_INFO:
+ return new PSDDisplayInfo(id, pInput);
+ case PSD.RES_PRINT_FLAGS:
+ return new PSDPrintFlags(id, pInput);
+ case PSD.RES_THUMBNAIL_PS4:
+ case PSD.RES_THUMBNAIL:
+ return new PSDThumbnail(id, pInput);
+ case PSD.RES_ICC_PROFILE:
+ return new ICCProfile(id, pInput);
+ case PSD.RES_EXIF_DATA_1:
+ return new PSDEXIF1Data(id, pInput);
+ case PSD.RES_XMP_DATA:
+ return new PSDXMPData(id, pInput);
+ case PSD.RES_PRINT_FLAGS_INFORMATION:
+ return new PSDPrintFlagsInformation(id, pInput);
+ default:
+ if (id >= 0x07d0 && id <= 0x0bb6) {
+ // TODO: Parse saved path information
+ return new PSDImageResource(id, pInput);
+ }
+ else {
+ return new PSDImageResource(id, pInput);
+ }
+ }
+
+ }
+}
diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerBlendMode.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerBlendMode.java
new file mode 100755
index 00000000..926be1c9
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerBlendMode.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2008, 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.psd;
+
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.IIOException;
+import java.io.IOException;
+
+/**
+ * PSDLayerBlendMode
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: PSDLayerBlendMode.java,v 1.0 May 8, 2008 4:34:35 PM haraldk Exp$
+ */
+class PSDLayerBlendMode {
+ final int mBlendMode;
+ final int mOpacity; // 0-255
+ final int mClipping; // 0: base, 1: non-base
+ final int mFlags;
+
+ public PSDLayerBlendMode(ImageInputStream pInput) throws IOException {
+ int blendModeSig = pInput.readInt();
+ if (blendModeSig != PSD.RESOURCE_TYPE) { // TODO: Is this really just a resource?
+ throw new IIOException("Illegal PSD Blend Mode signature, expected 8BIM: " + PSDUtil.intToStr(blendModeSig));
+ }
+
+ mBlendMode = pInput.readInt();
+
+ mOpacity = pInput.readUnsignedByte();
+ mClipping = pInput.readUnsignedByte();
+ mFlags = pInput.readUnsignedByte();
+
+ pInput.readByte(); // Pad
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder(getClass().getSimpleName());
+
+ builder.append("[");
+ builder.append("mode: \"").append(PSDUtil.intToStr(mBlendMode));
+ builder.append("\", opacity: ").append(mOpacity);
+ builder.append(", clipping: ").append(mClipping);
+ builder.append(", flags: ").append(byteToBinary(mFlags));
+
+ // TODO: Maybe the flag bits have oposite order?
+ builder.append(" (");
+ if ((mFlags & 0x01) != 0) {
+ builder.append("Transp. protected ");
+ }
+ else {
+ builder.append("Transp. open");
+ }
+ if ((mFlags & 0x02) != 0) {
+ builder.append(", Visible");
+ }
+ else {
+ builder.append(", Hidden");
+ }
+ if ((mFlags & 0x04) != 0) {
+ builder.append(", Obsolete bit");
+ }
+ if ((mFlags & 0x08) != 0) {
+ builder.append(", Photoshop 5 data");
+ }
+ if ((mFlags & 0x10) != 0) {
+ builder.append(", Pixel data irrelevant");
+ }
+ if ((mFlags & 0x20) != 0) {
+ builder.append(", Unknown bit 5");
+ }
+ if ((mFlags & 0x40) != 0) {
+ builder.append(", Unknown bit 6");
+ }
+ if ((mFlags & 0x80) != 0) {
+ builder.append(", Unknown bit 7");
+ }
+ builder.append(")");
+
+ builder.append("]");
+
+ return builder.toString();
+ }
+
+ private static String byteToBinary(final int pFlags) {
+ String flagStr = Integer.toBinaryString(pFlags);
+ flagStr = "00000000".substring(flagStr.length()) + flagStr;
+ return flagStr;
+ }
+}
diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerInfo.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerInfo.java
new file mode 100755
index 00000000..3f306158
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerInfo.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2008, 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.psd;
+
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.IIOException;
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * PSDLayerInfo
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: PSDLayerInfo.java,v 1.0 Apr 29, 2008 6:01:12 PM haraldk Exp$
+ */
+class PSDLayerInfo {
+ private int mTop;
+ private int mLeft;
+ private int mBottom;
+ private int mRight;
+
+ PSDChannelInfo[] mChannelInfo;
+ private PSDLayerBlendMode mBlendMode;
+ private PSDLayerMaskData mLayerMaskData;
+ private PSDChannelSourceDestinationRange[] mRanges;
+ private String mLayerName;
+
+ PSDLayerInfo(ImageInputStream pInput) throws IOException {
+ mTop = pInput.readInt();
+ mLeft = pInput.readInt();
+ mBottom = pInput.readInt();
+ mRight = pInput.readInt();
+
+ int channels = pInput.readUnsignedShort();
+
+ mChannelInfo = new PSDChannelInfo[channels];
+ for (int i = 0; i < channels; i++) {
+ short channelId = pInput.readShort();
+ long length = pInput.readUnsignedInt();
+
+ mChannelInfo[i] = new PSDChannelInfo(channelId, length);
+ }
+
+ mBlendMode = new PSDLayerBlendMode(pInput);
+
+ // Lenght of layer mask data
+ long extraDataSize = pInput.readUnsignedInt();
+ // TODO: Allow skipping the rest here?
+ // pInput.skipBytes(extraDataSize);
+
+ // Layer mask/adjustment layer data
+ int layerMaskDataSize = pInput.readInt(); // May be 0, 20 or 36 bytes...
+ if (layerMaskDataSize != 0) {
+ mLayerMaskData = new PSDLayerMaskData(pInput, layerMaskDataSize);
+ }
+
+ int layerBlendingDataSize = pInput.readInt();
+ if (layerBlendingDataSize % 8 != 0) {
+ throw new IIOException("Illegal PSD Layer Blending Data size: " + layerBlendingDataSize + ", expected multiple of 8");
+ }
+
+ mRanges = new PSDChannelSourceDestinationRange[layerBlendingDataSize / 8];
+ for (int i = 0; i < mRanges.length; i++) {
+ mRanges[i] = new PSDChannelSourceDestinationRange(pInput, (i == 0 ? "Gray" : "Channel " + (i - 1)));
+ }
+
+
+ mLayerName = PSDUtil.readPascalString(pInput);
+
+ int layerNameSize = mLayerName.length() + 1;
+ // readPascalString has already read pad byte for word alignment
+ if (layerNameSize % 2 != 0) {
+ layerNameSize++;
+ }
+ // Skip two more pad bytes if needed
+ if (layerNameSize % 4 != 0) {
+ pInput.skipBytes(2);
+ layerNameSize += 2;
+ }
+
+ // TODO: There's some data skipped here...
+ // Adjustment layer info etc...
+ pInput.skipBytes(extraDataSize - layerMaskDataSize - 4 - layerBlendingDataSize - 4 - layerNameSize);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder(getClass().getSimpleName());
+ builder.append("[");
+ builder.append("top: ").append(mTop);
+ builder.append(", left: ").append(mLeft);
+ builder.append(", bottom: ").append(mBottom);
+ builder.append(", right: ").append(mRight);
+
+ builder.append(", channels: ").append(Arrays.toString(mChannelInfo));
+ builder.append(", blend mode: ").append(mBlendMode);
+ if (mLayerMaskData != null) {
+ builder.append(", layer mask data: ").append(mLayerMaskData);
+ }
+ builder.append(", ranges: ").append(Arrays.toString(mRanges));
+ builder.append(", layer name: \"").append(mLayerName).append("\"");
+
+ builder.append("]");
+ return builder.toString();
+ }
+}
diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerMaskData.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerMaskData.java
new file mode 100755
index 00000000..277938b6
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerMaskData.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2008, 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.psd;
+
+import javax.imageio.IIOException;
+import javax.imageio.stream.ImageInputStream;
+import java.io.IOException;
+
+/**
+ * PSDLayerMaskData
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: PSDLayerMaskData.java,v 1.0 May 6, 2008 5:15:05 PM haraldk Exp$
+ */
+class PSDLayerMaskData {
+ private int mTop;
+ private int mLeft;
+ private int mBottom;
+ private int mRight;
+ private int mDefaultColor;
+ private int mFlags;
+
+ private boolean mLarge;
+ private int mRealFlags;
+ private int mRealUserBackground;
+ private int mRealTop;
+ private int mRealLeft;
+ private int mRealBottom;
+ private int mRealRight;
+
+ PSDLayerMaskData(ImageInputStream pInput, int pSize) throws IOException {
+ if (pSize != 20 && pSize != 36) {
+ throw new IIOException("Illegal PSD Layer Mask data size: " + pSize + " (expeced 20 or 36)");
+ }
+ mTop = pInput.readInt();
+ mLeft = pInput.readInt();
+ mBottom = pInput.readInt();
+ mRight = pInput.readInt();
+
+ mDefaultColor = pInput.readUnsignedByte();
+
+ mFlags = pInput.readUnsignedByte();
+
+ if (pSize == 20) {
+ pInput.readShort(); // Pad
+ }
+ else {
+ // TODO: What to make out of this?
+ mLarge = true;
+
+ mRealFlags = pInput.readUnsignedByte();
+ mRealUserBackground = pInput.readUnsignedByte();
+
+ mRealTop = pInput.readInt();
+ mRealLeft = pInput.readInt();
+ mRealBottom = pInput.readInt();
+ mRealRight = pInput.readInt();
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder(getClass().getSimpleName());
+ builder.append("[");
+ builder.append("top: ").append(mTop);
+ builder.append(", left: ").append(mLeft);
+ builder.append(", bottom: ").append(mBottom);
+ builder.append(", right: ").append(mRight);
+ builder.append(", default color: ").append(mDefaultColor);
+ builder.append(", flags: ").append(Integer.toBinaryString(mFlags));
+
+ // TODO: Maybe the flag bits have oposite order?
+ builder.append(" (");
+ if ((mFlags & 0x01) != 0) {
+ builder.append("Pos. rel. to layer");
+ }
+ else {
+ builder.append("Pos. abs.");
+ }
+ if ((mFlags & 0x02) != 0) {
+ builder.append(", Mask disabled");
+ }
+ else {
+ builder.append(", Mask enabled");
+ }
+ if ((mFlags & 0x04) != 0) {
+ builder.append(", Invert mask");
+ }
+ if ((mFlags & 0x08) != 0) {
+ builder.append(", Unknown bit 3");
+ }
+ if ((mFlags & 0x10) != 0) {
+ builder.append(", Unknown bit 4");
+ }
+ if ((mFlags & 0x20) != 0) {
+ builder.append(", Unknown bit 5");
+ }
+ if ((mFlags & 0x40) != 0) {
+ builder.append(", Unknown bit 6");
+ }
+ if ((mFlags & 0x80) != 0) {
+ builder.append(", Unknown bit 7");
+ }
+ builder.append(")");
+
+ builder.append("]");
+ return builder.toString();
+ }
+}
diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlags.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlags.java
new file mode 100755
index 00000000..0cb3ddca
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlags.java
@@ -0,0 +1,58 @@
+package com.twelvemonkeys.imageio.plugins.psd;
+
+import javax.imageio.stream.ImageInputStream;
+import java.io.IOException;
+
+/**
+ * PSDPrintFlagsInfo
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: PSDPrintFlagsInfo.java,v 1.0 Jul 28, 2009 5:16:27 PM haraldk Exp$
+ */
+final class PSDPrintFlags extends PSDImageResource {
+ private boolean mLabels;
+ private boolean mCropMasks;
+ private boolean mColorBars;
+ private boolean mRegistrationMarks;
+ private boolean mNegative;
+ private boolean mFlip;
+ private boolean mInterpolate;
+ private boolean mCaption;
+
+ PSDPrintFlags(final short pId, final ImageInputStream pInput) throws IOException {
+ super(pId, pInput);
+ }
+
+ @Override
+ protected void readData(final ImageInputStream pInput) throws IOException {
+ mLabels = pInput.readUnsignedByte() != 0;
+ mCropMasks = pInput.readUnsignedByte() != 0;
+ mColorBars = pInput.readUnsignedByte() != 0;
+ mRegistrationMarks = pInput.readUnsignedByte() != 0;
+ mNegative = pInput.readUnsignedByte() != 0;
+ mFlip = pInput.readUnsignedByte() != 0;
+ mInterpolate = pInput.readUnsignedByte() != 0;
+ mCaption = pInput.readUnsignedByte() != 0;
+
+ pInput.readUnsignedByte(); // Pad
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = toStringBuilder();
+
+ builder.append(", labels: ").append(mLabels);
+ builder.append(", crop masks: ").append(mCropMasks);
+ builder.append(", color bars: ").append(mColorBars);
+ builder.append(", registration marks: ").append(mRegistrationMarks);
+ builder.append(", negative: ").append(mNegative);
+ builder.append(", flip: ").append(mFlip);
+ builder.append(", interpolate: ").append(mInterpolate);
+ builder.append(", caption: ").append(mCaption);
+
+ builder.append("]");
+
+ return builder.toString();
+ }
+}
diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlagsInformation.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlagsInformation.java
new file mode 100755
index 00000000..80583db7
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlagsInformation.java
@@ -0,0 +1,47 @@
+package com.twelvemonkeys.imageio.plugins.psd;
+
+import javax.imageio.stream.ImageInputStream;
+import java.io.IOException;
+
+/**
+ * PSDPrintFlagsInfo
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: PSDPrintFlagsInfo.java,v 1.0 Jul 28, 2009 5:16:27 PM haraldk Exp$
+ */
+final class PSDPrintFlagsInformation extends PSDImageResource {
+ private int mVersion;
+ private boolean mCropMasks;
+ private int mField;
+ private long mBleedWidth;
+ private int mBleedScale;
+
+ PSDPrintFlagsInformation(final short pId, final ImageInputStream pInput) throws IOException {
+ super(pId, pInput);
+ }
+
+ @Override
+ protected void readData(final ImageInputStream pInput) throws IOException {
+ mVersion = pInput.readUnsignedShort();
+ mCropMasks = pInput.readUnsignedByte() != 0;
+ mField = pInput.readUnsignedByte();
+ mBleedWidth = pInput.readUnsignedInt();
+ mBleedScale = pInput.readUnsignedShort();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = toStringBuilder();
+
+ builder.append(", version: ").append(mVersion);
+ builder.append(", crop masks: ").append(mCropMasks);
+ builder.append(", field: ").append(mField);
+ builder.append(", bleed width: ").append(mBleedWidth);
+ builder.append(", bleed scale: ").append(mBleedScale);
+
+ builder.append("]");
+
+ return builder.toString();
+ }
+}
\ No newline at end of file
diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDReader.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDReader.java
new file mode 100755
index 00000000..b38c6480
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDReader.java
@@ -0,0 +1,555 @@
+package com.twelvemonkeys.imageio.plugins.psd;
+
+import java.awt.*;
+import java.awt.image.*;
+import java.net.*;
+import java.io.*;
+
+/**
+ * Class PSDReader - Decodes a PhotoShop (.psd) file into one or more frames.
+ * Supports uncompressed or RLE-compressed RGB files only. Each layer may be
+ * retrieved as a full frame BufferedImage, or as a smaller image with an
+ * offset if the layer does not occupy the full frame size. Transparency
+ * in the original psd file is preserved in the returned BufferedImage's.
+ * Does not support additional features in PS versions higher than 3.0.
+ * Example:
+ *
+ * PSDReader r = new PSDReader();
+ * r.readData("sample.psd");
+ * int n = r.getFrameCount();
+ * for (int i = 0; i < n; i++) {
+ * BufferedImage image = r.getLayer(i);
+ * Point offset = r.getLayerOffset(i);
+ * // do something with image
+ * }
+ *
+ * No copyright asserted on the source code of this class. May be used for
+ * any purpose. Please forward any corrections to kweiner@fmsware.com.
+ *
+ * @author Kevin Weiner, FM Software.
+ * @version 1.1 January 2004 [bug fix; add RLE support]
+ *
+ */
+// SEE: http://www.fileformat.info/format/psd/egff.htm#ADOBEPHO-DMYID.4
+class PSDReader {
+
+ /**
+ * File readData status: No errors.
+ */
+ public static final int STATUS_OK = 0;
+
+ /**
+ * File readData status: Error decoding file (may be partially decoded)
+ */
+ public static final int STATUS_FORMAT_ERROR = 1;
+
+ /**
+ * File readData status: Unable to open source.
+ */
+ public static final int STATUS_OPEN_ERROR = 2;
+
+ /**
+ * File readData status: Unsupported format
+ */
+ public static final int STATUS_UNSUPPORTED = 3;
+
+ public static int ImageType = BufferedImage.TYPE_INT_ARGB;
+
+ protected BufferedInputStream input;
+ protected int frameCount;
+ protected BufferedImage[] frames;
+ protected int status = 0;
+ protected int nChan;
+ protected int width;
+ protected int height;
+ protected int nLayers;
+ protected int miscLen;
+ protected boolean hasLayers;
+ protected LayerInfo[] layers;
+ protected short[] lineLengths;
+ protected int lineIndex;
+ protected boolean rleEncoded;
+
+ protected class LayerInfo {
+ int x, y, w, h;
+ int nChan;
+ int[] chanID;
+ int alpha;
+ }
+
+ /**
+ * Gets the number of layers readData from file.
+ * @return frame count
+ */
+ public int getFrameCount() {
+ return frameCount;
+ }
+
+ protected void setInput(InputStream stream) {
+ // open input stream
+ init();
+ if (stream == null) {
+ status = STATUS_OPEN_ERROR;
+ } else {
+ if (stream instanceof BufferedInputStream)
+ input = (BufferedInputStream) stream;
+ else
+ input = new BufferedInputStream(stream);
+ }
+ }
+
+ protected void setInput(String name) {
+ // open input file
+ init();
+ try {
+ name = name.trim();
+ if (name.startsWith("file:")) {
+ name = name.substring(5);
+ while (name.startsWith("/"))
+ name = name.substring(1);
+ }
+ if (name.indexOf("://") > 0) {
+ URL url = new URL(name);
+ input = new BufferedInputStream(url.openStream());
+ } else {
+ input = new BufferedInputStream(new FileInputStream(name));
+ }
+ } catch (IOException e) {
+ status = STATUS_OPEN_ERROR;
+ }
+ }
+
+ /**
+ * Gets display duration for specified frame. Always returns 0.
+ *
+ */
+ public int getDelay(int forFrame) {
+ return 0;
+ }
+
+ /**
+ * Gets the image contents of frame n. Note that this expands the image
+ * to the full frame size (if the layer was smaller) and any subsequent
+ * use of getLayer() will return the full image.
+ *
+ * @return BufferedImage representation of frame, or null if n is invalid.
+ */
+ public BufferedImage getFrame(int n) {
+ BufferedImage im = null;
+ if ((n >= 0) && (n < nLayers)) {
+ im = frames[n];
+ LayerInfo info = layers[n];
+ if ((info.w != width) || (info.h != height)) {
+ BufferedImage temp =
+ new BufferedImage(width, height, ImageType);
+ Graphics2D gc = temp.createGraphics();
+ gc.drawImage(im, info.x, info.y, null);
+ gc.dispose();
+ im = temp;
+ frames[n] = im;
+ }
+ }
+ return im;
+ }
+
+ /**
+ * Gets maximum image size. Individual layers may be smaller.
+ *
+ * @return maximum image dimensions
+ */
+ public Dimension getFrameSize() {
+ return new Dimension(width, height);
+ }
+
+ /**
+ * Gets the first (or only) image readData.
+ *
+ * @return BufferedImage containing first frame, or null if none.
+ */
+ public BufferedImage getImage() {
+ return getFrame(0);
+ }
+
+ /**
+ * Gets the image contents of layer n. May be smaller than full frame
+ * size - use getFrameOffset() to obtain position of subimage within
+ * main image area.
+ *
+ * @return BufferedImage representation of layer, or null if n is invalid.
+ */
+ public BufferedImage getLayer(int n) {
+ BufferedImage im = null;
+ if ((n >= 0) && (n < nLayers)) {
+ im = frames[n];
+ }
+ return im;
+ }
+
+ /**
+ * Gets the subimage offset of layer n if it is smaller than the
+ * full frame size.
+ *
+ * @return Point indicating offset from upper left corner of frame.
+ */
+ public Point getLayerOffset(int n) {
+ Point p = null;
+ if ((n >= 0) && (n < nLayers)) {
+ int x = layers[n].x;
+ int y = layers[n].y;
+ p = new Point(x, y);
+ }
+ if (p == null) {
+ p = new Point(0, 0);
+ }
+ return p;
+ }
+
+ /**
+ * Reads PhotoShop layers from stream.
+ *
+ * @param InputStream in PhotoShop format.
+ * @return readData status code (0 = no errors)
+ */
+ public int read(InputStream stream) {
+ setInput(stream);
+ process();
+ return status;
+ }
+
+ /**
+ * Reads PhotoShop file from specified source (file or URL string)
+ *
+ * @param name String containing source
+ * @return readData status code (0 = no errors)
+ */
+ public int read(String name) {
+ setInput(name);
+ process();
+ return status;
+ }
+
+ /**
+ * Closes input stream and discards contents of all frames.
+ *
+ */
+ public void reset() {
+ init();
+ }
+
+ protected void close() {
+ if (input != null) {
+ try {
+ input.close();
+ } catch (Exception e) {}
+ input = null;
+ }
+ }
+ protected boolean err() {
+ return status != STATUS_OK;
+ }
+
+ protected byte[] fillBytes(int size, int value) {
+ // create byte array filled with given value
+ byte[] b = new byte[size];
+ if (value != 0) {
+ byte v = (byte) value;
+ for (int i = 0; i < size; i++) {
+ b[i] = v;
+ }
+ }
+ return b;
+ }
+
+ protected void init() {
+ close();
+ frameCount = 0;
+ frames = null;
+ layers = null;
+ hasLayers = true;
+ status = STATUS_OK;
+ }
+
+ protected void makeDummyLayer() {
+ // creat dummy layer for non-layered image
+ rleEncoded = readShort() == 1;
+ hasLayers = false;
+ nLayers = 1;
+ layers = new LayerInfo[1];
+ LayerInfo layer = new LayerInfo();
+ layers[0] = layer;
+ layer.h = height;
+ layer.w = width;
+ int nc = Math.min(nChan, 4);
+ if (rleEncoded) {
+ // get list of rle encoded line lengths for all channels
+ readLineLengths(height * nc);
+ }
+ layer.nChan = nc;
+ layer.chanID = new int[nc];
+ for (int i = 0; i < nc; i++) {
+ int id = i;
+ if (i == 3) id = -1;
+ layer.chanID[i] = id;
+ }
+ }
+
+ protected void readLineLengths(int nLines) {
+ // readData list of rle encoded line lengths
+ lineLengths = new short[nLines];
+ for (int i = 0; i < nLines; i++) {
+ lineLengths[i] = readShort();
+ }
+ lineIndex = 0;
+ }
+
+ protected BufferedImage makeImage(int w, int h, byte[] r, byte[] g, byte[] b, byte[] a) {
+ // create image from given plane data
+ BufferedImage im = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
+ int[] data = ((DataBufferInt) im.getRaster().getDataBuffer()).getData();
+ int n = w * h;
+ int j = 0;
+ while (j < n) {
+ try {
+ int ac = a[j] & 0xff;
+ int rc = r[j] & 0xff;
+ int gc = g[j] & 0xff;
+ int bc = b[j] & 0xff;
+ data[j] = (((((ac << 8) | rc) << 8) | gc) << 8) | bc;
+ } catch (Exception e) {}
+ j++;
+ }
+ return im;
+ }
+
+ protected void process() {
+ // decode PSD file
+ if (err()) return;
+ readHeader();
+ if (err()) return;
+ readLayerInfo();
+ if (err()) return;
+ if (nLayers == 0) {
+ makeDummyLayer();
+ if (err()) return;
+ }
+ readLayers();
+ }
+
+ protected int readByte() {
+ // readData single byte from input
+ int curByte = 0;
+ try {
+ curByte = input.read();
+ } catch (IOException e) {
+ status = STATUS_FORMAT_ERROR;
+ }
+ return curByte;
+ }
+
+ protected int readBytes(byte[] bytes, int n) {
+ // readData multiple bytes from input
+ if (bytes == null) return 0;
+ int r = 0;
+ try {
+ r = input.read(bytes, 0, n);
+ } catch (IOException e) {
+ status = STATUS_FORMAT_ERROR;
+ }
+ if (r < n) {
+ status = STATUS_FORMAT_ERROR;
+ }
+ return r;
+ }
+
+ protected void readHeader() {
+ // readData PSD header info
+ String sig = readString(4);
+ int ver = readShort();
+ skipBytes(6);
+ nChan = readShort();
+ height = readInt();
+ width = readInt();
+ int depth = readShort();
+ int mode = readShort();
+ int cmLen = readInt();
+ skipBytes(cmLen);
+ int imResLen = readInt();
+ skipBytes(imResLen);
+
+ // require 8-bit RGB data
+ if ((!sig.equals("8BPS")) || (ver != 1)) {
+ status = STATUS_FORMAT_ERROR;
+ } else if ((depth != 8) || (mode != 3)) {
+ status = STATUS_UNSUPPORTED;
+ }
+ }
+
+ protected int readInt() {
+ // readData big-endian 32-bit integer
+ return (((((readByte() << 8) | readByte()) << 8) | readByte()) << 8)
+ | readByte();
+ }
+
+ protected void readLayerInfo() {
+ // readData layer header info
+ miscLen = readInt();
+ if (miscLen == 0) {
+ return; // no layers, only base image
+ }
+ int layerInfoLen = readInt();
+ nLayers = readShort();
+ if (nLayers > 0) {
+ layers = new LayerInfo[nLayers];
+ }
+ for (int i = 0; i < nLayers; i++) {
+ LayerInfo info = new LayerInfo();
+ layers[i] = info;
+ info.y = readInt();
+ info.x = readInt();
+ info.h = readInt() - info.y;
+ info.w = readInt() - info.x;
+ info.nChan = readShort();
+ info.chanID = new int[info.nChan];
+ for (int j = 0; j < info.nChan; j++) {
+ int id = readShort();
+ int size = readInt();
+ info.chanID[j] = id;
+ }
+ String s = readString(4);
+ if (!s.equals("8BIM")) {
+ status = STATUS_FORMAT_ERROR;
+ return;
+ }
+ skipBytes(4); // blend mode
+ info.alpha = readByte();
+ int clipping = readByte();
+ int flags = readByte();
+ readByte(); // filler
+ int extraSize = readInt();
+ skipBytes(extraSize);
+ }
+ }
+
+ protected void readLayers() {
+ // readData and convert each layer to BufferedImage
+ frameCount = nLayers;
+ frames = new BufferedImage[nLayers];
+ for (int i = 0; i < nLayers; i++) {
+ LayerInfo info = layers[i];
+ byte[] r = null, g = null, b = null, a = null;
+ for (int j = 0; j < info.nChan; j++) {
+ int id = info.chanID[j];
+ switch (id) {
+ case 0 : r = readPlane(info.w, info.h); break;
+ case 1 : g = readPlane(info.w, info.h); break;
+ case 2 : b = readPlane(info.w, info.h); break;
+ case -1 : a = readPlane(info.w, info.h); break;
+ default : readPlane(info.w, info.h);
+ }
+ if (err()) break;
+ }
+ if (err()) break;
+ int n = info.w * info.h;
+ if (r == null) r = fillBytes(n, 0);
+ if (g == null) g = fillBytes(n, 0);
+ if (b == null) b = fillBytes(n, 0);
+ if (a == null) a = fillBytes(n, 255);
+
+ BufferedImage im = makeImage(info.w, info.h, r, g, b, a);
+ frames[i] = im;
+ }
+ lineLengths = null;
+ if ((miscLen > 0) && !err()) {
+ int n = readInt(); // global layer mask info len
+ skipBytes(n);
+ }
+ }
+
+ protected byte[] readPlane(int w, int h) {
+ // readData a single color plane
+ byte[] b = null;
+ int size = w * h;
+ if (hasLayers) {
+ // get RLE compression info for channel
+ rleEncoded = readShort() == 1;
+ if (rleEncoded) {
+ // list of encoded line lengths
+ readLineLengths(h);
+ }
+ }
+
+ if (rleEncoded) {
+ b = readPlaneCompressed(w, h);
+ } else {
+ b = new byte[size];
+ readBytes(b, size);
+ }
+
+ return b;
+
+ }
+
+ protected byte[] readPlaneCompressed(int w, int h) {
+ byte[] b = new byte[w * h];
+ byte[] s = new byte[w * 2];
+ int pos = 0;
+ for (int i = 0; i < h; i++) {
+ if (lineIndex >= lineLengths.length) {
+ status = STATUS_FORMAT_ERROR;
+ return null;
+ }
+ int len = lineLengths[lineIndex++];
+ readBytes(s, len);
+ decodeRLE(s, 0, len, b, pos);
+ pos += w;
+ }
+ return b;
+ }
+
+ protected void decodeRLE(byte[] src, int sindex, int slen, byte[] dst, int dindex) {
+ try {
+ int max = sindex + slen;
+ while (sindex < max) {
+ byte b = src[sindex++];
+ int n = (int) b;
+ if (n < 0) {
+ // dup next byte 1-n times
+ n = 1 - n;
+ b = src[sindex++];
+ for (int i = 0; i < n; i++) {
+ dst[dindex++] = b;
+ }
+ } else {
+ // copy next n+1 bytes
+ n = n + 1;
+ System.arraycopy(src, sindex, dst, dindex, n);
+ dindex += n;
+ sindex += n;
+ }
+ }
+ } catch (Exception e) {
+ status = STATUS_FORMAT_ERROR;
+ }
+ }
+
+ protected short readShort() {
+ // readData big-endian 16-bit integer
+ return (short) ((readByte() << 8) | readByte());
+ }
+
+ protected String readString(int len) {
+ // readData string of specified length
+ String s = "";
+ for (int i = 0; i < len; i++) {
+ s = s + (char) readByte();
+ }
+ return s;
+ }
+
+ protected void skipBytes(int n) {
+ // skip over n input bytes
+ for (int i = 0; i < n; i++) {
+ readByte();
+ }
+ }
+}
diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDResolutionInfo.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDResolutionInfo.java
new file mode 100755
index 00000000..43102599
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDResolutionInfo.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2008, 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.psd;
+
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.IIOException;
+import java.io.IOException;
+
+/**
+ * PSDResolutionInfo
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: PSDResolutionInfo.java,v 1.0 May 2, 2008 3:58:19 PM haraldk Exp$
+ */
+class PSDResolutionInfo extends PSDImageResource {
+ // typedef struct _ResolutionInfo
+ // {
+ // LONG hRes; /* Fixed-point number: pixels per inch */
+ // WORD hResUnit; /* 1=pixels per inch, 2=pixels per centimeter */
+ // WORD WidthUnit; /* 1=in, 2=cm, 3=pt, 4=picas, 5=columns */
+ // LONG vRes; /* Fixed-point number: pixels per inch */
+ // WORD vResUnit; /* 1=pixels per inch, 2=pixels per centimeter */
+ // WORD HeightUnit; /* 1=in, 2=cm, 3=pt, 4=picas, 5=columns */
+ // } RESOLUTIONINFO;
+
+ private float mHRes;
+ private short mHResUnit;
+ private short mWidthUnit;
+ private float mVRes;
+ private short mVResUnit;
+ private short mHeightUnit;
+
+
+ PSDResolutionInfo(final short pId, final ImageInputStream pInput) throws IOException {
+ super(pId, pInput);
+ }
+
+ @Override
+ protected void readData(ImageInputStream pInput) throws IOException {
+ if (mSize != 16) {
+ throw new IIOException("Resolution info length expected to be 16: " + mSize);
+ }
+
+ mHRes = PSDUtil.fixedPointToFloat(pInput.readInt());
+ mHResUnit = pInput.readShort();
+ mWidthUnit = pInput.readShort();
+ mVRes = PSDUtil.fixedPointToFloat(pInput.readInt());
+ mVResUnit = pInput.readShort();
+ mHeightUnit = pInput.readShort();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = toStringBuilder();
+
+ builder.append(", hRes: ").append(mHRes);
+ builder.append(" ");
+ builder.append(resUnit(mHResUnit));
+ builder.append(", width unit: ");
+ builder.append(dimUnit(mWidthUnit));
+ builder.append(", vRes: ").append(mVRes);
+ builder.append(" ");
+ builder.append(resUnit(mVResUnit));
+ builder.append(", height unit: ");
+ builder.append(dimUnit(mHeightUnit));
+
+ builder.append("]");
+
+ return builder.toString();
+ }
+
+ private String resUnit(final short pResUnit) {
+ switch (pResUnit) {
+ case 1:
+ return "pixels/inch";
+ case 2:
+ return "pixels/cm";
+ default:
+ return "unknown unit " + pResUnit;
+ }
+ }
+
+ private String dimUnit(final short pUnit) {
+ switch (pUnit) {
+ case 1:
+ return "in";
+ case 2:
+ return "cm";
+ case 3:
+ return "pt";
+ case 4:
+ return "pica";
+ case 5:
+ return "column";
+ default:
+ return "unknown unit " + pUnit;
+ }
+ }
+}
diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDThumbnail.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDThumbnail.java
new file mode 100755
index 00000000..6e1ae79b
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDThumbnail.java
@@ -0,0 +1,86 @@
+package com.twelvemonkeys.imageio.plugins.psd;
+
+import com.twelvemonkeys.imageio.util.IIOUtil;
+
+import javax.imageio.IIOException;
+import javax.imageio.ImageIO;
+import javax.imageio.stream.ImageInputStream;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+
+/**
+ * PSDThumbnail
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: PSDThumbnail.java,v 1.0 Jul 29, 2009 4:41:06 PM haraldk Exp$
+ */
+class PSDThumbnail extends PSDImageResource {
+ private BufferedImage mThumbnail;
+
+ public PSDThumbnail(final short pId, final ImageInputStream pInput) throws IOException {
+ super(pId, pInput);
+ }
+
+ /*
+ Thumbnail header, size 28
+ 4 Format. 1 = kJpegRGB . Also supports kRawRGB (0).
+ 4 Width of thumbnail in pixels.
+ 4 Height of thumbnail in pixels.
+ 4 Widthbytes: Padded row bytes = (width * bits per pixel + 31) / 32 * 4.
+ 4 Total size = widthbytes * height * planes
+ 4 Size after compression. Used for consistency check.
+ 2 Bits per pixel. = 24
+ 2 Number of planes. = 1
+ */
+ @Override
+ protected void readData(final ImageInputStream pInput) throws IOException {
+ // TODO: Support for RAW RGB (format == 0)
+ int format = pInput.readInt();
+ switch (format) {
+ case 0:
+ throw new IIOException("RAW RGB format thumbnail not supported yet");
+ case 1:
+ break;
+ default:
+ throw new IIOException(String.format("Unsupported thumbnail format (%s) in PSD document", format));
+ }
+
+ // This data isn't really useful, unless we're dealing with raw bytes
+ int width = pInput.readInt();
+ int height = pInput.readInt();
+ int widthBytes = pInput.readInt();
+ int totalSize = pInput.readInt();
+
+ // Consistency check
+ int sizeCompressed = pInput.readInt();
+ if (sizeCompressed != (mSize - 28)) {
+ throw new IIOException("Corrupt thumbnail in PSD document");
+ }
+
+ // According to the spec, only 24 bits and 1 plane is supported
+ int bits = pInput.readUnsignedShort();
+ int planes = pInput.readUnsignedShort();
+ if (bits != 24 && planes != 1) {
+ // TODO: Warning/Exception
+ }
+
+ // TODO: Support BGR if id == RES_THUMBNAIL_PS4? Or is that already supported in the JPEG?
+ mThumbnail = ImageIO.read(IIOUtil.createStreamAdapter(pInput, sizeCompressed));
+ }
+
+ public BufferedImage getThumbnail() {
+ return mThumbnail;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = toStringBuilder();
+
+ builder.append(", ").append(mThumbnail);
+
+ builder.append("]");
+
+ return builder.toString();
+ }
+}
diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtil.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtil.java
new file mode 100755
index 00000000..3865e2e3
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtil.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2008, 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.psd;
+
+import com.twelvemonkeys.imageio.util.IIOUtil;
+import com.twelvemonkeys.io.enc.DecoderStream;
+import com.twelvemonkeys.io.enc.InflateDecoder;
+import com.twelvemonkeys.io.enc.PackBitsDecoder;
+
+import javax.imageio.stream.ImageInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+
+/**
+ * PSDUtil
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: PSDUtil.java,v 1.0 Apr 29, 2008 5:05:00 PM haraldk Exp$
+ */
+final class PSDUtil {
+ // TODO: Duplicated code from IFF plugin, move to some common util?
+ static String intToStr(int pChunkId) {
+ return new String(
+ new byte[]{
+ (byte) ((pChunkId & 0xff000000) >> 24),
+ (byte) ((pChunkId & 0x00ff0000) >> 16),
+ (byte) ((pChunkId & 0x0000ff00) >> 8),
+ (byte) ((pChunkId & 0x000000ff))
+ }
+ );
+ }
+
+ // TODO: Proably also useful for PICT reader, move to some common util?
+ static String readPascalString(ImageInputStream pInput) throws IOException {
+ int length = pInput.readUnsignedByte();
+// int length = pInput.readUnsignedShort();
+ byte[] bytes = new byte[length];
+ pInput.readFully(bytes);
+ if (length % 2 == 0) {
+ pInput.readByte(); // Pad
+ }
+ return new String(bytes);
+ }
+
+ static String readPascalStringByte(ImageInputStream pInput) throws IOException {
+ int length = pInput.readUnsignedByte();
+ byte[] bytes = new byte[length];
+ pInput.readFully(bytes);
+ return new String(bytes);
+ }
+
+ static DataInputStream createPackBitsStream(final ImageInputStream pInput, int pLength) {
+ return new DataInputStream(new DecoderStream(IIOUtil.createStreamAdapter(pInput, pLength), new PackBitsDecoder()));
+ }
+
+ static DataInputStream createZipStream(final ImageInputStream pInput, int pLength) {
+ return new DataInputStream(new DecoderStream(IIOUtil.createStreamAdapter(pInput, pLength), new InflateDecoder()));
+ }
+
+ static DataInputStream createZipPredictorStream(final ImageInputStream pInput, int pLength) {
+ throw new UnsupportedOperationException("Method createZipPredictonStream not implemented");
+ }
+
+ public static float fixedPointToFloat(int pFP) {
+ return ((pFP & 0xffff0000) >> 16) + (pFP & 0xffff) / (float) 0xffff;
+ }
+}
diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDXMPData.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDXMPData.java
new file mode 100755
index 00000000..7ee84a5e
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDXMPData.java
@@ -0,0 +1,60 @@
+package com.twelvemonkeys.imageio.plugins.psd;
+
+import com.twelvemonkeys.lang.StringUtil;
+
+import javax.imageio.stream.ImageInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.Charset;
+
+/**
+ * XMP metadata.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: XMPData.java,v 1.0 Jul 28, 2009 5:50:34 PM haraldk Exp$
+ *
+ * @see Adobe Extensible Metadata Platform (XMP)
+ * @see Adobe XMP Developer Center
+ */
+public final class PSDXMPData extends PSDImageResource {
+ protected byte[] mData;
+
+ PSDXMPData(final short pId, final ImageInputStream pInput) throws IOException {
+ super(pId, pInput);
+ }
+
+ @Override
+ protected void readData(final ImageInputStream pInput) throws IOException {
+ mData = new byte[(int) mSize]; // TODO: Fix potential overflow, or document why that can't happen (read spec)
+ pInput.readFully(mData);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = toStringBuilder();
+
+ int length = Math.min(256, mData.length);
+ String data = StringUtil.decode(mData, 0, length, "UTF-8").replace('\n', ' ').replaceAll("\\s+", " ");
+ builder.append(", data: \"").append(data);
+
+ if (length < mData.length) {
+ builder.append("...");
+ }
+
+ builder.append("\"]");
+
+ return builder.toString();
+ }
+
+ /**
+ * Returns a character stream containing the XMP metadata (XML).
+ *
+ * @return the XMP metadata.
+ */
+ public Reader getData() {
+ return new InputStreamReader(new ByteArrayInputStream(mData), Charset.forName("UTF-8"));
+ }
+}
diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/YCbCrColorSpace.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/YCbCrColorSpace.java
new file mode 100755
index 00000000..d0d020f1
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/YCbCrColorSpace.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2008, 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.psd;
+
+import java.awt.color.ColorSpace;
+
+/**
+ * YCbCrColorSpace
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: YCbCrColorSpace.java,v 1.0 Jun 28, 2008 3:30:50 PM haraldk Exp$
+ */
+// TODO: Move to com.twlevemonkeys.image?
+// TODO: Read an ICC YCbCr profile from classpath resource? Is there such a thing?
+final class YCbCrColorSpace extends ColorSpace {
+
+ static final ColorSpace INSTANCE = new CMYKColorSpace();
+ final ColorSpace sRGB = getInstance(CS_sRGB);
+
+ YCbCrColorSpace() {
+ super(ColorSpace.TYPE_YCbCr, 3);
+ }
+
+ public static ColorSpace getInstance() {
+ return INSTANCE;
+ }
+
+ // http://www.w3.org/Graphics/JPEG/jfif.txt
+ /*
+ Conversion to and from RGB
+
+ Y, Cb, and Cr are converted from R, G, and B as defined in CCIR Recommendation 601
+ but are normalized so as to occupy the full 256 levels of a 8-bit binary encoding. More
+ precisely:
+
+ Y = 256 * E'y
+ Cb = 256 * [ E'Cb ] + 128
+ Cr = 256 * [ E'Cr ] + 128
+
+ where the E'y, E'Cb and E'Cb are defined as in CCIR 601. Since values of E'y have a
+ range of 0 to 1.0 and those for E'Cb and E'Cr have a range of -0.5 to +0.5, Y, Cb, and Cr
+ must be clamped to 255 when they are maximum value.
+
+ RGB to YCbCr Conversion
+
+ YCbCr (256 levels) can be computed directly from 8-bit RGB as follows:
+
+ Y = 0.299 R + 0.587 G + 0.114 B
+ Cb = - 0.1687 R - 0.3313 G + 0.5 B + 128
+ Cr = 0.5 R - 0.4187 G - 0.0813 B + 128
+
+ NOTE - Not all image file formats store image samples in the order R0, G0,
+ B0, ... Rn, Gn, Bn. Be sure to verify the sample order before converting an
+ RGB file to JFIF.
+
+ YCbCr to RGB Conversion
+
+ RGB can be computed directly from YCbCr (256 levels) as follows:
+
+ R = Y + 1.402 (Cr-128)
+ G = Y - 0.34414 (Cb-128) - 0.71414 (Cr-128)
+ B = Y + 1.772 (Cb-128)
+ */
+ public float[] toRGB(float[] colorvalue) {
+// R = Y + 1.402 (Cr-128)
+// G = Y - 0.34414 (Cb-128) - 0.71414 (Cr-128)
+// B = Y + 1.772 (Cb-128)
+ return new float[] {
+ colorvalue[0] + 1.402f * (colorvalue[2] - .5f),
+ colorvalue[0] - 0.34414f * (colorvalue[1] - .5f) - 0.71414f * (colorvalue[2] - .5f),
+ colorvalue[0] + 1.772f * (colorvalue[1] - .5f),
+ };
+ // TODO: Convert via CIEXYZ space using sRGB space, as suggested in docs
+ // return sRGB.fromCIEXYZ(toCIEXYZ(colorvalue));
+ }
+
+ public float[] fromRGB(float[] rgbvalue) {
+// Y = 0.299 R + 0.587 G + 0.114 B
+// Cb = - 0.1687 R - 0.3313 G + 0.5 B + 128
+// Cr = 0.5 R - 0.4187 G - 0.0813 B + 128
+ return new float[] {
+ 0.299f * rgbvalue[0] + 0.587f * rgbvalue[1] + 0.114f * rgbvalue[2],
+ -0.1687f * rgbvalue[0] - 0.3313f * rgbvalue[1] + 0.5f * rgbvalue[2] + .5f,
+ 0.5f * rgbvalue[0] - 0.4187f * rgbvalue[1] - 0.0813f * rgbvalue[2] + .5f
+ };
+ }
+
+ public float[] toCIEXYZ(float[] colorvalue) {
+ throw new UnsupportedOperationException("Method toCIEXYZ not implemented"); // TODO: Implement
+ }
+
+ public float[] fromCIEXYZ(float[] colorvalue) {
+ throw new UnsupportedOperationException("Method fromCIEXYZ not implemented"); // TODO: Implement
+ }
+}
diff --git a/twelvemonkeys-imageio/psd/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi b/twelvemonkeys-imageio/psd/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi
new file mode 100755
index 00000000..21a911af
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi
@@ -0,0 +1 @@
+com.twelvemonkeys.imageio.plugins.psd.PSDImageReaderSpi
\ No newline at end of file
diff --git a/twelvemonkeys-imageio/psd/src/test/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderTestCase.java b/twelvemonkeys-imageio/psd/src/test/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderTestCase.java
new file mode 100755
index 00000000..498eb398
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/src/test/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderTestCase.java
@@ -0,0 +1,68 @@
+package com.twelvemonkeys.imageio.plugins.psd;
+
+import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
+
+import javax.imageio.spi.ImageReaderSpi;
+import java.awt.*;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * PSDImageReaderTestCase
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: PSDImageReaderTestCase.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
+ */
+public class PSDImageReaderTestCase extends ImageReaderAbstractTestCase {
+
+ static ImageReaderSpi sProvider = new PSDImageReaderSpi();
+
+ protected List getTestData() {
+ return Arrays.asList(
+ // 5 channel, RGB
+ new TestData(getClassLoaderResource("/psd/photoshopping.psd"), new Dimension(300, 225)),
+ // 1 channel, gray, 8 bit samples
+ new TestData(getClassLoaderResource("/psd/buttons.psd"), new Dimension(20, 20)),
+ // 5 channel, CMYK
+ new TestData(getClassLoaderResource("/psd/escenic-liquid-logo.psd"), new Dimension(595, 420)),
+ // 3 channel RGB, no composite layer
+ new TestData(getClassLoaderResource("/psd/jugware-icon.psd"), new Dimension(128, 128)),
+ // 3 channel RGB, old data, no layer info/mask
+ new TestData(getClassLoaderResource("/psd/MARBLES.PSD"), new Dimension(1419, 1001)),
+ // 1 channel, indexed color
+ new TestData(getClassLoaderResource("/psd/coral_fish.psd"), new Dimension(800, 800))
+ // 1 channel, bitmap, 1 bit samples
+// new TestData(getClassLoaderResource("/psd/test_bitmap.psd"), new Dimension(800, 600))
+ // 1 channel, gray, 16 bit samples
+// new TestData(getClassLoaderResource("/psd/test_gray16.psd"), new Dimension(800, 600))
+ // TODO: Need uncompressed PSD
+ // TODO: Need more recent ZIP compressed PSD files from CS2/CS3+
+ );
+ }
+
+ protected ImageReaderSpi createProvider() {
+ return sProvider;
+ }
+
+ @Override
+ protected PSDImageReader createReader() {
+ return new PSDImageReader(sProvider);
+ }
+
+ protected Class getReaderClass() {
+ return PSDImageReader.class;
+ }
+
+ protected List getFormatNames() {
+ return Arrays.asList("psd");
+ }
+
+ protected List getSuffixes() {
+ return Arrays.asList("psd");
+ }
+
+ protected List getMIMETypes() {
+ return Arrays.asList("image/x-psd");
+ }
+}
\ No newline at end of file
diff --git a/twelvemonkeys-imageio/psd/src/test/resources/psd/AlphaChannelSample_720x480.psd b/twelvemonkeys-imageio/psd/src/test/resources/psd/AlphaChannelSample_720x480.psd
new file mode 100755
index 00000000..5b70ee40
Binary files /dev/null and b/twelvemonkeys-imageio/psd/src/test/resources/psd/AlphaChannelSample_720x480.psd differ
diff --git a/twelvemonkeys-imageio/psd/src/test/resources/psd/MARBLES.PSD b/twelvemonkeys-imageio/psd/src/test/resources/psd/MARBLES.PSD
new file mode 100755
index 00000000..f15802f8
Binary files /dev/null and b/twelvemonkeys-imageio/psd/src/test/resources/psd/MARBLES.PSD differ
diff --git a/twelvemonkeys-imageio/psd/src/test/resources/psd/buttons.psd b/twelvemonkeys-imageio/psd/src/test/resources/psd/buttons.psd
new file mode 100755
index 00000000..a56f6f21
Binary files /dev/null and b/twelvemonkeys-imageio/psd/src/test/resources/psd/buttons.psd differ
diff --git a/twelvemonkeys-imageio/psd/src/test/resources/psd/circles.psd b/twelvemonkeys-imageio/psd/src/test/resources/psd/circles.psd
new file mode 100755
index 00000000..f4d68338
Binary files /dev/null and b/twelvemonkeys-imageio/psd/src/test/resources/psd/circles.psd differ
diff --git a/twelvemonkeys-imageio/psd/src/test/resources/psd/cmyk_16bits.psd b/twelvemonkeys-imageio/psd/src/test/resources/psd/cmyk_16bits.psd
new file mode 100755
index 00000000..ec6fec51
Binary files /dev/null and b/twelvemonkeys-imageio/psd/src/test/resources/psd/cmyk_16bits.psd differ
diff --git a/twelvemonkeys-imageio/psd/src/test/resources/psd/cmyk_8bits.psd b/twelvemonkeys-imageio/psd/src/test/resources/psd/cmyk_8bits.psd
new file mode 100755
index 00000000..5a822f76
Binary files /dev/null and b/twelvemonkeys-imageio/psd/src/test/resources/psd/cmyk_8bits.psd differ
diff --git a/twelvemonkeys-imageio/psd/src/test/resources/psd/coral_fish.psd b/twelvemonkeys-imageio/psd/src/test/resources/psd/coral_fish.psd
new file mode 100755
index 00000000..2bbb5128
Binary files /dev/null and b/twelvemonkeys-imageio/psd/src/test/resources/psd/coral_fish.psd differ
diff --git a/twelvemonkeys-imageio/psd/src/test/resources/psd/escenic-liquid-logo.psd b/twelvemonkeys-imageio/psd/src/test/resources/psd/escenic-liquid-logo.psd
new file mode 100755
index 00000000..99b0cdd3
Binary files /dev/null and b/twelvemonkeys-imageio/psd/src/test/resources/psd/escenic-liquid-logo.psd differ
diff --git a/twelvemonkeys-imageio/psd/src/test/resources/psd/jugware-icon.psd b/twelvemonkeys-imageio/psd/src/test/resources/psd/jugware-icon.psd
new file mode 100755
index 00000000..ebf9ab9b
Binary files /dev/null and b/twelvemonkeys-imageio/psd/src/test/resources/psd/jugware-icon.psd differ
diff --git a/twelvemonkeys-imageio/psd/src/test/resources/psd/photoshopping.psd b/twelvemonkeys-imageio/psd/src/test/resources/psd/photoshopping.psd
new file mode 100755
index 00000000..41c880bf
Binary files /dev/null and b/twelvemonkeys-imageio/psd/src/test/resources/psd/photoshopping.psd differ
diff --git a/twelvemonkeys-imageio/psd/src/test/resources/psd/test_bitmap.psd b/twelvemonkeys-imageio/psd/src/test/resources/psd/test_bitmap.psd
new file mode 100755
index 00000000..73247167
Binary files /dev/null and b/twelvemonkeys-imageio/psd/src/test/resources/psd/test_bitmap.psd differ
diff --git a/twelvemonkeys-imageio/psd/src/test/resources/psd/test_gray.psd b/twelvemonkeys-imageio/psd/src/test/resources/psd/test_gray.psd
new file mode 100755
index 00000000..9f828b57
Binary files /dev/null and b/twelvemonkeys-imageio/psd/src/test/resources/psd/test_gray.psd differ
diff --git a/twelvemonkeys-imageio/psd/src/test/resources/psd/test_gray16.psd b/twelvemonkeys-imageio/psd/src/test/resources/psd/test_gray16.psd
new file mode 100755
index 00000000..e3b139a1
Binary files /dev/null and b/twelvemonkeys-imageio/psd/src/test/resources/psd/test_gray16.psd differ
diff --git a/twelvemonkeys-imageio/psd/src/test/resources/psd/test_indexed.psd b/twelvemonkeys-imageio/psd/src/test/resources/psd/test_indexed.psd
new file mode 100755
index 00000000..ad8d57a5
Binary files /dev/null and b/twelvemonkeys-imageio/psd/src/test/resources/psd/test_indexed.psd differ
diff --git a/twelvemonkeys-imageio/psd/src/test/resources/psd/test_original.psb b/twelvemonkeys-imageio/psd/src/test/resources/psd/test_original.psb
new file mode 100755
index 00000000..a5bfb5f9
Binary files /dev/null and b/twelvemonkeys-imageio/psd/src/test/resources/psd/test_original.psb differ
diff --git a/twelvemonkeys-imageio/psd/src/test/resources/psd/test_original.psd b/twelvemonkeys-imageio/psd/src/test/resources/psd/test_original.psd
new file mode 100755
index 00000000..0f553e47
Binary files /dev/null and b/twelvemonkeys-imageio/psd/src/test/resources/psd/test_original.psd differ
diff --git a/twelvemonkeys-imageio/psd/src/test/resources/psd/transp.psd b/twelvemonkeys-imageio/psd/src/test/resources/psd/transp.psd
new file mode 100755
index 00000000..d3ddb5cd
Binary files /dev/null and b/twelvemonkeys-imageio/psd/src/test/resources/psd/transp.psd differ
diff --git a/twelvemonkeys-imageio/psd/todo.txt b/twelvemonkeys-imageio/psd/todo.txt
new file mode 100755
index 00000000..efe9ceee
--- /dev/null
+++ b/twelvemonkeys-imageio/psd/todo.txt
@@ -0,0 +1,5 @@
+Implement source subsampling and region of interest
+Separate package for the resources (seems to be a lot)?
+Possibility to read only some resources? readResources(int[] resourceKeys)?
+ - Probably faster when we only need the color space
+PSDImageWriter
\ No newline at end of file
diff --git a/twelvemonkeys-imageio/thumbsdb/license.txt b/twelvemonkeys-imageio/thumbsdb/license.txt
new file mode 100755
index 00000000..2d8ee79c
--- /dev/null
+++ b/twelvemonkeys-imageio/thumbsdb/license.txt
@@ -0,0 +1,25 @@
+Copyright (c) 2009, 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.
\ No newline at end of file
diff --git a/twelvemonkeys-imageio/thumbsdb/pom.xml b/twelvemonkeys-imageio/thumbsdb/pom.xml
new file mode 100755
index 00000000..e8f4513f
--- /dev/null
+++ b/twelvemonkeys-imageio/thumbsdb/pom.xml
@@ -0,0 +1,31 @@
+
+
+ 4.0.0
+ com.twelvemonkeys.imageio
+ twelvemonkeys-imageio-thumbsdb
+ 2.1
+ TwelveMonkeys ImageIO Thumbs.db plugin
+
+ ImageIO plugin for Windows Thumbs DB (Thumbs.db) format.
+
+
+
+ twelvemonkeys-imageio
+ com.twelvemonkeys
+ 2.1
+
+
+
+
+ com.twelvemonkeys.imageio
+ twelvemonkeys-imageio-core
+
+
+ com.twelvemonkeys.imageio
+ twelvemonkeys-imageio-core
+ tests
+
+
+
\ No newline at end of file
diff --git a/twelvemonkeys-imageio/thumbsdb/src/main/java/com/twelvemonkeys/imageio/plugins/thumbsdb/Catalog.java b/twelvemonkeys-imageio/thumbsdb/src/main/java/com/twelvemonkeys/imageio/plugins/thumbsdb/Catalog.java
new file mode 100755
index 00000000..51a6f288
--- /dev/null
+++ b/twelvemonkeys-imageio/thumbsdb/src/main/java/com/twelvemonkeys/imageio/plugins/thumbsdb/Catalog.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (c) 2008, 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.thumbsdb;
+
+import com.twelvemonkeys.io.LittleEndianDataInputStream;
+import com.twelvemonkeys.io.ole2.CompoundDocument;
+import com.twelvemonkeys.lang.StringUtil;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.DataInput;
+import java.util.Iterator;
+import java.util.Date;
+
+/**
+ * Represents a {@code Catalog} structure, typically found in a {@link com.twelvemonkeys.io.ole2.CompoundDocument}.
+ *
+ * @see PeteDavis.NET
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku$
+ * @version $Id: Catalog.java,v 1.0 01.feb.2007 17:19:59 haku Exp$
+ */
+// TODO: Consider moving this one to io.ole2
+public final class Catalog implements Iterable {
+
+ private final CatalogHeader mHeader;
+ private final CatalogItem[] mItems;
+
+ Catalog(final CatalogHeader pHeader, final CatalogItem[] pItems) {
+ mHeader = pHeader;
+ mItems = pItems;
+ }
+
+ /**
+ * Reads the {@code Catalog} entry from the given input stream.
+ *
+ * @param pInput the input stream
+ * @return a new {@code Catalog}
+ *
+ * @throws java.io.IOException if an I/O exception occurs during read
+ */
+ public static Catalog read(final InputStream pInput) throws IOException {
+ DataInput dataInput = new LittleEndianDataInputStream(pInput);
+ return read(dataInput);
+ }
+
+ /**
+ * Reads the {@code Catalog} entry from the given input stream.
+ *
+ * The data is assumed to be in little endian byte order.
+ *
+ * @param pDataInput the input stream
+ * @return a new {@code Catalog}
+ *
+ * @throws java.io.IOException if an I/O exception occurs during read
+ */
+ public static Catalog read(final DataInput pDataInput) throws IOException {
+ CatalogHeader header = CatalogHeader.read(pDataInput);
+
+ CatalogItem[] items = new CatalogItem[header.getThumbnailCount()];
+ for (int i = 0; i < header.getThumbnailCount(); i++) {
+ CatalogItem item = CatalogItem.read(pDataInput);
+ //System.out.println("item: " + item);
+ items[item.getItemId() - 1] = item;
+ }
+
+ return new Catalog(header, items);
+ }
+
+ public final int getThumbnailCount() {
+ return mHeader.mThumbCount;
+ }
+
+ public final int getMaxThumbnailWidth() {
+ return mHeader.mThumbWidth;
+ }
+
+ public final int getMaxThumbnailHeight() {
+ return mHeader.mThumbHeight;
+ }
+
+ final CatalogItem getItem(final int pIndex) {
+ return mItems[pIndex];
+ }
+
+ final CatalogItem getItem(final String pName) {
+ return mItems[getIndex(pName)];
+ }
+
+ final int getItemId(final int pIndex) {
+ return mItems[pIndex].getItemId();
+ }
+
+ public final int getIndex(final String pName) {
+ for (int i = 0; i < mItems.length; i++) {
+ CatalogItem item = mItems[i];
+
+ if (item.getName().equals(pName)) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ public final String getStreamName(final int pIndex) {
+ return StringUtil.reverse(String.valueOf(getItemId(pIndex)));
+ }
+
+ public final String getName(String pStreamName) {
+ return getName(Integer.parseInt(StringUtil.reverse(pStreamName)));
+ }
+
+ final String getName(int pItemId) {
+ return mItems[pItemId - 1].getName();
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s[%s]", getClass().getSimpleName(), mHeader);
+ }
+
+ public Iterator iterator() {
+ return new Iterator() {
+ int mCurrentIdx;
+
+ public boolean hasNext() {
+ return mCurrentIdx < mItems.length;
+ }
+
+ public CatalogItem next() {
+ return mItems[mCurrentIdx++];
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException("Remove not supported");
+ }
+ };
+ }
+
+ private static class CatalogHeader {
+ short mReserved1;
+ short mReserved2;
+ int mThumbCount;
+ int mThumbWidth;
+ int mThumbHeight;
+
+ CatalogHeader() {
+ }
+
+ public static CatalogHeader read(final DataInput pDataInput) throws IOException {
+ CatalogHeader header = new CatalogHeader();
+
+ header.mReserved1 = pDataInput.readShort();
+ header.mReserved2 = pDataInput.readShort();
+ header.mThumbCount = pDataInput.readInt();
+ header.mThumbWidth = pDataInput.readInt();
+ header.mThumbHeight = pDataInput.readInt();
+
+ return header;
+ }
+
+ public int getThumbnailCount() {
+ return mThumbCount;
+ }
+
+ public int getThumbHeight() {
+ return mThumbHeight;
+ }
+
+ public int getThumbWidth() {
+ return mThumbWidth;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "%s: %s %s thumbs: %d maxWidth: %d maxHeight: %d",
+ getClass().getSimpleName(), mReserved1, mReserved2, mThumbCount, mThumbWidth, mThumbHeight
+ );
+ }
+ }
+
+ public static final class CatalogItem {
+ int mReserved1;
+ int mItemId; // Reversed stream name
+ String mFilename;
+ short mReserved2;
+ private long mLastModified;
+
+ private static CatalogItem read(final DataInput pDataInput) throws IOException {
+ CatalogItem item = new CatalogItem();
+ item.mReserved1 = pDataInput.readInt();
+ item.mItemId = pDataInput.readInt();
+
+ item.mLastModified = CompoundDocument.toJavaTimeInMillis(pDataInput.readLong());
+
+ char[] chars = new char[256];
+ char ch;
+ int len = 0;
+ while ((ch = pDataInput.readChar()) != 0) {
+ chars[len++] = ch;
+ }
+
+ String name = new String(chars, 0, len);
+ item.mFilename = StringUtil.getLastElement(name, "\\");
+
+ item.mReserved2 = pDataInput.readShort();
+ return item;
+ }
+
+ public String getName() {
+ return mFilename;
+ }
+
+ public int getItemId() {
+ return mItemId;
+ }
+
+ public long lastModified() {
+ return mLastModified;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "%s: %d itemId: %d lastModified: %s fileName: %s %s",
+ getClass().getSimpleName(), mReserved1, mItemId, new Date(mLastModified), mFilename, mReserved2
+ );
+ }
+ }
+}
diff --git a/twelvemonkeys-imageio/thumbsdb/src/main/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBImageReader.java b/twelvemonkeys-imageio/thumbsdb/src/main/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBImageReader.java
new file mode 100755
index 00000000..33929f50
--- /dev/null
+++ b/twelvemonkeys-imageio/thumbsdb/src/main/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBImageReader.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright (c) 2008, 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.thumbsdb;
+
+import com.twelvemonkeys.imageio.ImageReaderBase;
+import com.twelvemonkeys.imageio.util.ProgressListenerBase;
+import com.twelvemonkeys.io.FileUtil;
+import com.twelvemonkeys.io.ole2.CompoundDocument;
+import com.twelvemonkeys.io.ole2.Entry;
+import com.twelvemonkeys.lang.StringUtil;
+
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReadParam;
+import javax.imageio.ImageReader;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.stream.MemoryCacheImageInputStream;
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.ByteOrder;
+import java.util.Iterator;
+import java.util.SortedSet;
+
+/**
+ * ThumbsDBImageReader
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku$
+ * @version $Id: ThumbsDBImageReader.java,v 1.0 22.jan.2007 18:49:38 haku Exp$
+ * @see com.twelvemonkeys.io.ole2.CompoundDocument
+ * @see getImageTypes(int pIndex) throws IOException {
+ init(pIndex);
+ return mReader.getImageTypes(0);
+ }
+
+ public boolean isPresent(final String pFileName) {
+ try {
+ init();
+ }
+ catch (IOException e) {
+ resetMembers();
+ return false;
+ }
+
+ // TODO: Rethink this...
+ // Seems to be up to Windows and the installed programs what formats
+ // are supported...
+ // Some thumbs are just icons, and it might be better to use ImageIO to create thumbs for these... :-/
+ // At least this seems fine for now
+ String extension = FileUtil.getExtension(pFileName);
+ if (StringUtil.isEmpty(extension)) {
+ return false;
+ }
+ extension = extension.toLowerCase();
+
+ return !"psd".equals(extension) && !"svg".equals(extension) && mCatalog != null && mCatalog.getIndex(pFileName) != -1;
+ }
+
+ /// Test code below
+
+ public static void main(String[] pArgs) throws IOException {
+ ThumbsDBImageReader reader = new ThumbsDBImageReader();
+ reader.setInput(ImageIO.createImageInputStream(new File(pArgs[0])));
+// reader.setLoadEagerly(true);
+
+ if (pArgs.length > 1) {
+ long start = System.currentTimeMillis();
+ reader.init();
+ for (Catalog.CatalogItem item : reader.mCatalog) {
+ reader.read(item.getName(), null);
+ }
+ long end = System.currentTimeMillis();
+ System.out.println("Time: " + (end - start) + " ms");
+ }
+ else {
+ JFrame frame = createWindow(pArgs[0]);
+ JPanel panel = new JPanel();
+ panel.setBackground(Color.WHITE);
+ panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+
+ long start = System.currentTimeMillis();
+ reader.init();
+ for (Catalog.CatalogItem item : reader.mCatalog) {
+ addImage(panel, reader, reader.mCatalog.getIndex(item.getName()), item.getName());
+ }
+ long end = System.currentTimeMillis();
+ System.out.println("Time: " + (end - start) + " ms");
+
+ frame.getContentPane().add(new JScrollPane(panel));
+ frame.pack();
+ frame.setVisible(true);
+ }
+ }
+
+
+ private static JFrame createWindow(final String pTitle) {
+ JFrame frame = new JFrame(pTitle);
+ frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+ frame.addWindowListener(new WindowAdapter() {
+ public void windowClosed(WindowEvent e) {
+ System.exit(0);
+ }
+ });
+
+ return frame;
+ }
+
+ private static void addImage(Container pParent, ImageReader pReader, int pImageNo, String pName) throws IOException {
+ final JLabel label = new JLabel();
+ final BufferedImage image = pReader.read(pImageNo);
+ label.setIcon(new Icon() {
+ private static final int SIZE = 110;
+
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ g.setColor(Color.DARK_GRAY);
+ g.fillRoundRect(x, y, SIZE, SIZE, 10, 10);
+ g.drawImage(image, (SIZE - image.getWidth()) / 2 + x, (SIZE - image.getHeight()) / 2 + y, null);
+ }
+
+ public int getIconWidth() {
+ return SIZE;
+ }
+
+ public int getIconHeight() {
+ return SIZE;
+ }
+ });
+ label.setText("" + image.getWidth() + "x" + image.getHeight() + ": " + pName);
+ label.setToolTipText(image.toString());
+ pParent.add(label);
+ }
+
+}
diff --git a/twelvemonkeys-imageio/thumbsdb/src/main/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBImageReaderSpi.java b/twelvemonkeys-imageio/thumbsdb/src/main/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBImageReaderSpi.java
new file mode 100755
index 00000000..a392d882
--- /dev/null
+++ b/twelvemonkeys-imageio/thumbsdb/src/main/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBImageReaderSpi.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2008, 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.thumbsdb;
+
+import com.twelvemonkeys.io.ole2.CompoundDocument;
+
+import javax.imageio.ImageReader;
+import javax.imageio.spi.IIORegistry;
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.spi.ServiceRegistry;
+import javax.imageio.stream.ImageInputStream;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Locale;
+
+/**
+ * ThumbsDBImageReaderSpi
+ *
+ *
+ * @author Harald Kuhr
+ * @version $Id: ThumbsDBImageReaderSpi.java,v 1.0 28.feb.2006 19:21:05 haku Exp$
+ */
+public class ThumbsDBImageReaderSpi extends ImageReaderSpi {
+ private ImageReaderSpi mJPEGProvider;
+
+ /**
+ * Creates an IFFImageReaderSpi
+ */
+ public ThumbsDBImageReaderSpi() {
+ super(
+ "TwelveMonkeys",
+ "2.0",
+ new String[]{"thumbs", "THUMBS", "Thumbs DB"},
+ new String[]{"db"},
+ new String[]{"image/x-thumbs-db", "application/octet-stream"}, // TODO: Check IANA et al...
+ ThumbsDBImageReader.class.getName(),
+ STANDARD_INPUT_TYPE,
+ null,
+ true, null, null, null, null,
+ true, null, null, null, null
+ );
+ }
+
+ public boolean canDecodeInput(Object source) throws IOException {
+ return source instanceof ImageInputStream && canDecode((ImageInputStream) source);
+ }
+
+ public boolean canDecode(ImageInputStream pInput) throws IOException {
+ maybeInitJPEGProvider();
+ // If this is a OLE 2 CompoundDocument, we could try...
+ // TODO: How do we know it's thumbs.db format (structure), without reading quite a lot?
+ return mJPEGProvider != null && CompoundDocument.canRead(pInput);
+ }
+
+ public ImageReader createReaderInstance(Object extension) throws IOException {
+ return new ThumbsDBImageReader(this);
+ }
+
+ private void maybeInitJPEGProvider() {
+ // NOTE: Can't do this from constructor, as ImageIO itself is not initialized yet,
+ // and the lookup below will produce a NPE..
+
+ // TODO: A better approach...
+ // - Could have a list with known working JPEG decoders?
+ // - System property?
+ // - Class path lookup of properties file with reader?
+ // This way we could deregister immediately
+
+ if (mJPEGProvider == null) {
+ ImageReaderSpi provider = null;
+ try {
+ Iterator providers = getJPEGProviders();
+
+ while (providers.hasNext()) {
+ provider = providers.next();
+
+ // Prefer the one we know
+ if ("Sun Microsystems, Inc.".equals(provider.getVendorName())) {
+ break;
+ }
+ }
+ }
+ catch (Exception ignore) {
+ // It's pretty safe to assume there's always a JPEG reader out there
+ // In any case, we deregister the provider if there isn't one
+ IIORegistry.getDefaultInstance().deregisterServiceProvider(this, ImageReaderSpi.class);
+ }
+ mJPEGProvider = provider;
+ }
+ }
+
+ private Iterator getJPEGProviders() {
+ return IIORegistry.getDefaultInstance().getServiceProviders(
+ ImageReaderSpi.class,
+ new ServiceRegistry.Filter() {
+ public boolean filter(Object provider) {
+ if (provider instanceof ImageReaderSpi) {
+ ImageReaderSpi spi = (ImageReaderSpi) provider;
+ for (String format : spi.getFormatNames()) {
+ if ("JPEG".equals(format)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ }, true
+ );
+ }
+
+ /**
+ * Returns a new {@code ImageReader} that can read JPEG images.
+ *
+ * @return a new {@code ImageReader} that can read JPEG images.
+ * @throws IllegalStateException if no JPEG provider was found
+ * @throws Error if the reader can't be instantiated
+ */
+ ImageReader createJPEGReader() {
+ maybeInitJPEGProvider();
+ if (mJPEGProvider == null) {
+ throw new IllegalStateException("No suitable JPEG reader provider found");
+ }
+
+ try {
+ return mJPEGProvider.createReaderInstance();
+ }
+ catch (IOException e) {
+ // NOTE: The default Sun version never throws IOException here
+ throw new Error("Could not create JPEG reader: " + e.getMessage(), e);
+ }
+ }
+
+ public String getDescription(Locale locale) {
+ return "Microsoft Windows Thumbs DB (Thumbs.db) image reader";
+ }
+
+// @Override
+// public void onRegistration(ServiceRegistry registry, Class> category) {
+// System.out.println("ThumbsDBImageReaderSpi.onRegistration");
+// maybeInitJPEGProvider();
+// if (mJPEGProvider == null) {
+// System.out.println("Deregistering");
+// registry.deregisterServiceProvider(this, ImageReaderSpi.class);
+// }
+// }
+
+}
diff --git a/twelvemonkeys-imageio/thumbsdb/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi b/twelvemonkeys-imageio/thumbsdb/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi
new file mode 100755
index 00000000..374d7633
--- /dev/null
+++ b/twelvemonkeys-imageio/thumbsdb/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi
@@ -0,0 +1 @@
+com.twelvemonkeys.imageio.plugins.thumbsdb.ThumbsDBImageReaderSpi
diff --git a/twelvemonkeys-imageio/thumbsdb/src/test/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBImageReaderTestCase.java b/twelvemonkeys-imageio/thumbsdb/src/test/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBImageReaderTestCase.java
new file mode 100755
index 00000000..63aacaf2
--- /dev/null
+++ b/twelvemonkeys-imageio/thumbsdb/src/test/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBImageReaderTestCase.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2008, 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.thumbsdb;
+
+import com.twelvemonkeys.imageio.stream.BufferedImageInputStream;
+import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
+import com.twelvemonkeys.io.ole2.CompoundDocument;
+import com.twelvemonkeys.io.ole2.Entry;
+
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.stream.MemoryCacheImageInputStream;
+import java.awt.*;
+import java.io.IOException;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * ICOImageReaderTestCase
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: ICOImageReaderTestCase.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
+ */
+public class ThumbsDBImageReaderTestCase extends ImageReaderAbstractTestCase {
+ private ThumbsDBImageReaderSpi mProvider = new ThumbsDBImageReaderSpi();
+
+ protected List getTestData() {
+ return Arrays.asList(
+ new TestData(
+ getClassLoaderResource("/thumbsdb/Thumbs.db"),
+ new Dimension(96, 96), new Dimension(96, 96), new Dimension(16, 16),
+ new Dimension(96, 45), new Dimension(63, 96), new Dimension(96, 96),
+ new Dimension(96, 64), new Dimension(96, 96), new Dimension(96, 77)
+ ),
+ new TestData(
+ getClassLoaderResource("/thumbsdb/Thumbs-camera.db"),
+ new Dimension(96, 96),
+ new Dimension(64, 96),
+ new Dimension(96, 96),
+ new Dimension(96, 64), new Dimension(64, 96),
+ new Dimension(96, 64), new Dimension(96, 64), new Dimension(96, 64),
+ new Dimension(64, 96), new Dimension(64, 96), new Dimension(64, 96),
+ new Dimension(64, 96), new Dimension(64, 96), new Dimension(64, 96),
+ new Dimension(96, 64),
+ new Dimension(64, 96),
+ new Dimension(96, 64), new Dimension(96, 64), new Dimension(96, 64),
+ new Dimension(64, 96), new Dimension(64, 96),
+ new Dimension(64, 96), new Dimension(64, 96), new Dimension(64, 96)
+ )
+ );
+ }
+
+ protected ImageReaderSpi createProvider() {
+ return mProvider;
+ }
+
+ @Override
+ protected ThumbsDBImageReader createReader() {
+ return new ThumbsDBImageReader(mProvider);
+ }
+
+ protected Class getReaderClass() {
+ return ThumbsDBImageReader.class;
+ }
+
+ protected List getFormatNames() {
+ return Arrays.asList("thumbs");
+ }
+
+ protected List getSuffixes() {
+ return Arrays.asList("db");
+ }
+
+ protected List getMIMETypes() {
+ return Arrays.asList("image/x-thumbs-db");
+ }
+
+ public void testArrayIndexOutOfBoundsBufferedReadBug() throws IOException {
+ ImageInputStream input = new BufferedImageInputStream(new MemoryCacheImageInputStream(getClass().getResourceAsStream("/thumbsdb/Thumbs-camera.db")));
+ input.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+ Entry root = new CompoundDocument(input).getRootEntry();
+
+ Entry child = root.getChildEntry("Catalog");
+ child.getInputStream();
+ }
+}
\ No newline at end of file
diff --git a/twelvemonkeys-imageio/thumbsdb/src/test/resources/thumbsdb/Thumbs-camera.db b/twelvemonkeys-imageio/thumbsdb/src/test/resources/thumbsdb/Thumbs-camera.db
new file mode 100755
index 00000000..1adedbb8
Binary files /dev/null and b/twelvemonkeys-imageio/thumbsdb/src/test/resources/thumbsdb/Thumbs-camera.db differ
diff --git a/twelvemonkeys-imageio/thumbsdb/todo.txt b/twelvemonkeys-imageio/thumbsdb/todo.txt
new file mode 100755
index 00000000..ca61a0e0
--- /dev/null
+++ b/twelvemonkeys-imageio/thumbsdb/todo.txt
@@ -0,0 +1,14 @@
+- Listeners
+
+- Expose one of the methods below:
+ - String getName(int pIndex)
+ - String[] getNames()
+ - Catalog getCatalog()
+
+- Make it multi-threaded? Or at least support multi-threaded reading
+
+- There seems to be different versions of the Thumbs.db:
+ - http://vinetto.sourceforge.net/docs.html
+ - We probably want to support all of these
+
+DONE:
diff --git a/twelvemonkeys-imageio/todo.txt b/twelvemonkeys-imageio/todo.txt
new file mode 100755
index 00000000..8fa3c43f
--- /dev/null
+++ b/twelvemonkeys-imageio/todo.txt
@@ -0,0 +1,11 @@
+Consider creating a raw ImageReader (or util class?) that can read raw bitmaps:
+ - Interleaved (A)RGB (as in BMP, PICT, IFF PBM etc) -> A1R1G1B1, A2R2G2B2, ..., AnRnGnNn
+ - Channeled (A)RGB (as in Photoshop) -> A1A2...An, R1R2...Rn, G1G2...Gn, B1B2...Bn
+ - Planar RGB (as in IFF ILBM) -> ....
+Formats that internally have these structures could delegate to an instance of this class.
+Could also be interesting to allow for raw reading using a RawImageReader.
+ - Would need to specify width, height
+ - Pixel layout
+ - Channel order
+ - Compression? RLE/PackBits/LZW/ZIP?
+ - IndexColorModel?