diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBits16Decoder.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBits16Decoder.java deleted file mode 100644 index a25e8671..00000000 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBits16Decoder.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * 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.io.enc; - -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; - -/** - * Decoder implementation for 16 bit-chunked Apple PackBits-like run-length - * encoding. - *

- * This version of the decoder decodes chunk of 16 bit, instead of 8 bit. - * This format is used in certain PICT files. - * - * @see PackBitsDecoder - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/PackBits16Decoder.java#2 $ - */ -public final class PackBits16Decoder implements Decoder { - // TODO: Refactor this into an option for the PackBitsDecoder (bytesPerSample, default == 1)? - private final boolean disableNoop; - - private int leftOfRun; - private boolean splitRun; - private boolean reachedEOF; - - /** - * Creates a {@code PackBitsDecoder}. - */ - public PackBits16Decoder() { - this(false); - } - - /** - * Creates a {@code PackBitsDecoder}. - *

- * As some implementations of PackBits-like encoders treat {@code -128} as length of - * a compressed run, instead of a no-op, it's possible to disable no-ops - * for compatibility. - * Should be used with caution, even though, most known encoders never write - * no-ops in the compressed streams. - * - * @param pDisableNoop {@code true} if {@code -128} should be treated as a compressed run, and not a no-op - */ - public PackBits16Decoder(final boolean pDisableNoop) { - disableNoop = pDisableNoop; - } - - /** - * Decodes bytes from the given input stream, to the given buffer. - * - * @param stream the stream to decode from - * @param buffer a byte array, minimum 128 (or 129 if no-op is disabled) - * bytes long - * @return The number of bytes decoded - * - * @throws java.io.IOException - */ - public int decode(final InputStream stream, final ByteBuffer buffer) throws IOException { - if (reachedEOF) { - return -1; - } - - while (buffer.hasRemaining()) { - int n; - - if (splitRun) { - // Continue run - n = leftOfRun; - splitRun = false; - } - else { - // Start new run - int b = stream.read(); - if (b < 0) { - reachedEOF = true; - break; - } - n = (byte) b; - } - - // Split run at or before max - if (n >= 0 && 2 * (n + 1) > buffer.remaining()) { - leftOfRun = n; - splitRun = true; - break; - } - else if (n < 0 && 2 * (-n + 1) > buffer.remaining()) { - leftOfRun = n; - splitRun = true; - break; - } - - try { - if (n >= 0) { - // Copy next n + 1 shorts literally - readFully(stream, buffer, 2 * (n + 1)); - } - // Allow -128 for compatibility, see above - else if (disableNoop || n != -128) { - // Replicate the next short -n + 1 times - byte value1 = readByte(stream); - byte value2 = readByte(stream); - - for (int i = -n + 1; i > 0; i--) { - buffer.put(value1); - buffer.put(value2); - } - } - // else NOOP (-128) - } - catch (IndexOutOfBoundsException e) { - throw new DecodeException("Error in PackBits decompression, data seems corrupt", e); - } - } - - return buffer.position(); - } - - private static byte readByte(final InputStream pStream) throws IOException { - int read = pStream.read(); - - if (read < 0) { - throw new EOFException("Unexpected end of PackBits stream"); - } - - return (byte) read; - } - - private static void readFully(final InputStream pStream, final ByteBuffer pBuffer, final int pLength) throws IOException { - if (pLength < 0) { - throw new IndexOutOfBoundsException(); - } - - int total = 0; - - while (total < pLength) { - int count = pStream.read(pBuffer.array(), pBuffer.arrayOffset() + pBuffer.position() + total, pLength - total); - - if (count < 0) { - throw new EOFException("Unexpected end of PackBits stream"); - } - - total += count; - } - - pBuffer.position(pBuffer.position() + total); - } -} diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBitsDecoder.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBitsDecoder.java index 0c173754..86c32a1f 100644 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBitsDecoder.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBitsDecoder.java @@ -66,7 +66,8 @@ import java.nio.ByteBuffer; public final class PackBitsDecoder implements Decoder { // TODO: Look at ICNSImageReader#unpackbits... What is this weirdness? - private final boolean disableNoop; + private final boolean disableNoOp; + private final byte[] sample; private int leftOfRun; private boolean splitRun; @@ -74,7 +75,7 @@ public final class PackBitsDecoder implements Decoder { /** Creates a {@code PackBitsDecoder}. */ public PackBitsDecoder() { - this(false); + this(1, false); } /** @@ -84,10 +85,24 @@ public final class PackBitsDecoder implements Decoder { * a compressed run, instead of a no-op, it's possible to disable no-ops for compatibility. * Should be used with caution, even though, most known encoders never write no-ops in the compressed streams. * - * @param pDisableNoop {@code true} if {@code -128} should be treated as a compressed run, and not a no-op + * @param disableNoOp {@code true} if {@code -128} should be treated as a compressed run, and not a no-op */ - public PackBitsDecoder(final boolean pDisableNoop) { - disableNoop = pDisableNoop; + public PackBitsDecoder(final boolean disableNoOp) { + this(1, disableNoOp); + } + + /** + * Creates a {@code PackBitsDecoder}, with optional compatibility mode. + *

+ * As some implementations of PackBits-like encoders treat {@code -128} as length of + * a compressed run, instead of a no-op, it's possible to disable no-ops for compatibility. + * Should be used with caution, even though, most known encoders never write no-ops in the compressed streams. + * + * @param disableNoOp {@code true} if {@code -128} should be treated as a compressed run, and not a no-op + */ + public PackBitsDecoder(int sampleSize, final boolean disableNoOp) { + this.sample = new byte[sampleSize]; + this.disableNoOp = disableNoOp; } /** @@ -138,15 +153,17 @@ public final class PackBitsDecoder implements Decoder { try { if (n >= 0) { // Copy next n + 1 bytes literally - readFully(stream, buffer, n + 1); + readFully(stream, buffer, sample.length * (n + 1)); } // Allow -128 for compatibility, see above - else if (disableNoop || n != -128) { + else if (disableNoOp || n != -128) { // Replicate the next byte -n + 1 times - byte value = readByte(stream); + for (int s = 0; s < sample.length; s++) { + sample[s] = readByte(stream); + } for (int i = -n + 1; i > 0; i--) { - buffer.put(value); + buffer.put(sample); } } // else NOOP (-128) diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/BitMap.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/BitMap.java new file mode 100644 index 00000000..20ad04fd --- /dev/null +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/BitMap.java @@ -0,0 +1,11 @@ +package com.twelvemonkeys.imageio.plugins.pict; + +/** + * BitMap. + * + * @author Harald Kuhr + * @author last modified by $Author: harald.kuhr$ + * @version $Id: BitMap.java,v 1.0 20/02/15 harald.kuhr Exp$ + */ +final class BitMap { +} diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/BitMapPattern.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/BitMapPattern.java index de6f80f2..4eb79f01 100755 --- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/BitMapPattern.java +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/BitMapPattern.java @@ -29,10 +29,9 @@ package com.twelvemonkeys.imageio.plugins.pict; import java.awt.*; -import java.awt.image.WritableRaster; -import java.awt.image.DataBufferByte; -import java.awt.image.BufferedImage; -import java.awt.image.Raster; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.awt.image.*; /** * BitMapPattern @@ -43,22 +42,46 @@ import java.awt.image.Raster; */ final class BitMapPattern extends Pattern { + private final byte[] pattern; + BitMapPattern(final Paint pColor) { - super(pColor); + this(pColor, null); } public BitMapPattern(final byte[] pPattern) { - this(create8x8Pattern(pPattern)); + this(create8x8Pattern(pPattern), pPattern); + } + + private BitMapPattern(final Paint pColor, final byte[] pPattern) { + super(pColor); + + pattern = pPattern; + } + + // TODO: Refactor, don't need both BitMapPattern constructors and create8x8Pattern methods? + public BitMapPattern(final byte[] pPattern, Color fg, Color bg) { + this(create8x8Pattern(pPattern, fg, bg)); } BitMapPattern(final int pPattern) { this(create8x8Pattern(pPattern)); } - private static TexturePaint create8x8Pattern(final int pPattern) { - // TODO: Creating a special purpose Pattern might be faster than piggy-backing on TexturePaint - WritableRaster raster = QuickDraw.MONOCHROME.createCompatibleWritableRaster(8, 8); - byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData(); + private static Paint create8x8Pattern(final int pPattern) { +// // TODO: Creating a special purpose Pattern might be faster than piggy-backing on TexturePaint +// WritableRaster raster = QuickDraw.MONOCHROME.createCompatibleWritableRaster(8, 8); +// byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData(); +// +// for (int i = 0; i < data.length; i += 4) { +// data[i ] = (byte) ((pPattern >> 24) & 0xFF); +// data[i + 1] = (byte) ((pPattern >> 16) & 0xFF); +// data[i + 2] = (byte) ((pPattern >> 8) & 0xFF); +// data[i + 3] = (byte) ((pPattern ) & 0xFF); +// } +// +// BufferedImage img = new BufferedImage(QuickDraw.MONOCHROME, raster, false, null); +// return new TexturePaint(img, new Rectangle(8, 8)); + byte[] data = new byte[8]; for (int i = 0; i < data.length; i += 4) { data[i ] = (byte) ((pPattern >> 24) & 0xFF); @@ -67,13 +90,57 @@ final class BitMapPattern extends Pattern { data[i + 3] = (byte) ((pPattern ) & 0xFF); } - BufferedImage img = new BufferedImage(QuickDraw.MONOCHROME, raster, false, null); - return new TexturePaint(img, new Rectangle(8, 8)); + return create8x8Pattern(data); } - private static TexturePaint create8x8Pattern(final byte[] pPattern) { + private static Paint create8x8Pattern(final byte[] pPattern) { WritableRaster raster = Raster.createPackedRaster(new DataBufferByte(pPattern, 8), 8, 8, 1, new Point()); BufferedImage img = new BufferedImage(QuickDraw.MONOCHROME, raster, false, null); return new TexturePaint(img, new Rectangle(8, 8)); } + + private static Paint create8x8Pattern(final byte[] pPattern, Color fg, Color bg) { + switch (isSolid(pPattern)) { + case 0: // 0x00 + return bg; + case -1: // 0xff + return fg; + default: + // Fall through + } + + WritableRaster raster = Raster.createPackedRaster(new DataBufferByte(pPattern, 8), 8, 8, 1, new Point()); + IndexColorModel cm = new IndexColorModel(1, 2, new int[] {bg.getRGB(), fg.getRGB()}, 0, false, -1, DataBuffer.TYPE_BYTE); + BufferedImage img = new BufferedImage(cm, raster, false, null); + return new TexturePaint(img, new Rectangle(8, 8)); + } + + private static int isSolid(byte[] pPattern) { + int prev = pPattern[0]; + + for (int i = 1; i < pPattern.length; i++) { + if (prev != pPattern[i]) { + return 1; + } + } + + return prev; + } + + @Override + public PaintContext createContext(ColorModel pModel, Rectangle pDeviceBounds, Rectangle2D pUserBounds, AffineTransform pTransform, RenderingHints pHints) { +// switch (isSolid(pattern)) { +// } + return super.createContext(pModel, pDeviceBounds, pUserBounds, pTransform, pHints); + } + + @Override + public Pattern derive(final Color foreground, final Color background) { + if (paint instanceof Color) { + // TODO: This only holds for patterns that are already foregrounds... + return new BitMapPattern(foreground); + } + + return null; + } } diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICT.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICT.java index eca3c6a4..5038f791 100755 --- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICT.java +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICT.java @@ -175,4 +175,41 @@ interface PICT { int OP_UNCOMPRESSED_QUICKTIME = 0x8201; String APPLE_USE_RESERVED_FIELD = "Reserved for Apple use."; + + /* + * Picture comment 'kind' codes from: http://developer.apple.com/technotes/qd/qd_10.html + int TextBegin = 150; + int TextEnd = 151; + int StringBegin = 152; + int StringEnd = 153; + int TextCenter = 154; + int LineLayoutOff = 155; + int LineLayoutOn = 156; + int ClientLineLayout = 157; + int PolyBegin = 160; + int PolyEnd = 161; + int PolyIgnore = 163; + int PolySmooth = 164; + int PolyClose = 165; + int DashedLine = 180; + int DashedStop = 181; + int SetLineWidth = 182; + int PostScriptBegin = 190; + int PostScriptEnd = 191; + int PostScriptHandle = 192; + int PostScriptFile = 193; + int TextIsPostScript = 194; + int ResourcePS = 195; + int PSBeginNoSave = 196; + int SetGrayLevel = 197; + int RotateBegin = 200; + int RotateEnd = 201; + int RotateCenter = 202; + int FormsPrinting = 210; + int EndFormsPrinting = 211; + int ICC_Profile = 224; + int Photoshop_Data = 498; + int BitMapThinningOff = 1000; + int BitMapThinningOn = 1001; + */ } diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReader.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReader.java index 2cc202d6..c954ff47 100644 --- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReader.java +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReader.java @@ -64,7 +64,6 @@ import com.twelvemonkeys.imageio.util.IIOUtil; import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers; import com.twelvemonkeys.io.enc.Decoder; import com.twelvemonkeys.io.enc.DecoderStream; -import com.twelvemonkeys.io.enc.PackBits16Decoder; import com.twelvemonkeys.io.enc.PackBitsDecoder; import javax.imageio.*; @@ -100,23 +99,21 @@ import java.util.List; * - Or methods like frameRect(pen, penmode, penwidth, rect), frameOval(pen, penmode, penwidth, rect), etc? * - Or methods like frameShape(pen, penmode, penwidth, shape), paintShape(pen, penmode, shape) etc?? * QuickDrawContext that wraps an AWT Grpahics, and with methods macthing opcodes, seems like the best fit ATM - * @todo Remove null-checks for Graphics, as null-graphics makes no sense. * @todo Some MAJOR clean up - * @todo Object orientation of different opcodes? * @todo As we now have Graphics2D with more options, support more of the format? - * @todo Support for some other compression (packType 3) that seems to be common... */ public class PICTImageReader extends ImageReaderBase { - static boolean DEBUG = false; + final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.pict.debug")); - // Private fields private QuickDrawContext context; private Rectangle frame; + // TODO: Do we need this? private int version; // Variables for storing draw status + // TODO: Get rid of these, or move to context private Point penPosition = new Point(0, 0); private Rectangle lastRectangle = new Rectangle(0, 0); @@ -180,7 +177,7 @@ public class PICTImageReader extends ImageReaderBase { pStream.seek(0l); // Skip first 512 bytes - skipNullHeader(pStream); + PICTImageReaderSpi.skipNullHeader(pStream); readPICTHeader0(pStream); } } @@ -326,12 +323,6 @@ public class PICTImageReader extends ImageReaderBase { pStream.flushBefore(imageStartStreamPos); } - static void skipNullHeader(final ImageInputStream pStream) throws IOException { - // NOTE: Only skip if FILE FORMAT, not needed for Mac OS DnD - // Spec says "platofrm dependent", may not be all nulls.. - pStream.skipBytes(PICT.PICT_NULL_HEADER_SIZE); - } - /** * Reads the PICT stream. * The contents of the stream will be drawn onto the supplied graphics @@ -369,12 +360,9 @@ public class PICTImageReader extends ImageReaderBase { int opCode, dh, dv, dataLength; byte[] colorBuffer = new byte[3 * PICT.COLOR_COMP_SIZE]; - Pattern fill = QuickDraw.BLACK; Pattern bg; Pattern pen; - Paint foreground; - Paint background; Color hilight = Color.RED; Point origin, dh_dv; @@ -441,39 +429,27 @@ public class PICTImageReader extends ImageReaderBase { } break; - case PICT.OP_TX_FONT:// DIFFICULT TO KNOW THE FONT??? + case PICT.OP_TX_FONT: // Get the data - pStream.readFully(new byte[2], 0, 2); + byte[] fontData = new byte[2]; + pStream.readFully(fontData, 0, 2); // TODO: Font family id, 0 - System font, 1 - Application font. // But how can we get these mappings? if (DEBUG) { - System.out.println("txFont"); + System.out.println("txFont: " + Arrays.toString(fontData)); } break; - case PICT.OP_TX_FACE:// SEE IF IT IS TO BE IMPLEMENTED FOR NOW? + case PICT.OP_TX_FACE: // Get the data - byte txFace = pStream.readByte(); - - //// Construct text face mask -// currentFont = mGraphics.getFont(); - //int awt_face_mask = 0; - //if ((txFace & (byte) QuickDraw.TX_BOLD_MASK) > 0) { - // awt_face_mask |= Font.BOLD; - //} - //if ((txFace & (byte) QuickDraw.TX_ITALIC_MASK) > 0) { - // awt_face_mask |= Font.ITALIC; - //} - // - //// Set the font - //mGraphics.setFont(new Font(currentFont.getName(), awt_face_mask, currentFont.getSize())); - + int txFace = pStream.readUnsignedByte(); + context.setTextFace(txFace); if (DEBUG) { System.out.println("txFace: " + txFace); } break; - case PICT.OP_TX_MODE:// SEE IF IT IS TO BE IMPLEMENTED FOR NOW? + case PICT.OP_TX_MODE: // Get the data byte[] mode_buf = new byte[2]; pStream.readFully(mode_buf, 0, mode_buf.length); @@ -494,25 +470,25 @@ public class PICTImageReader extends ImageReaderBase { // Get the two words // NOTE: This is out of order, compared to other Points Dimension pnsize = new Dimension(pStream.readUnsignedShort(), pStream.readUnsignedShort()); - context.setPenSize(pnsize); if (DEBUG) { System.out.println("pnsize: " + pnsize); } + context.setPenSize(pnsize); + break; - case PICT.OP_PN_MODE:// TRY EMULATING WITH SETXORMODE ETC + case PICT.OP_PN_MODE: // Get the data int mode = pStream.readUnsignedShort(); if (DEBUG) { System.out.println("pnMode: " + mode); } - context.setPenMode(mode); break; case PICT.OP_PN_PAT: - context.setPenPattern(PICTUtil.readPattern(pStream)); + context.setPenPattern(PICTUtil.readPattern(pStream, context.getForeground(), context.getBackground())); if (DEBUG) { System.out.println("pnPat"); } @@ -546,9 +522,6 @@ public class PICTImageReader extends ImageReaderBase { y = getYPtCoord(pStream.readUnsignedShort()); x = getXPtCoord(pStream.readUnsignedShort()); origin = new Point(x, y); - //if (mGraphics != null) { - // mGraphics.translate(origin.x, origin.y); - //} if (DEBUG) { System.out.println("Origin: " + origin); } @@ -557,10 +530,6 @@ public class PICTImageReader extends ImageReaderBase { case PICT.OP_TX_SIZE:// OK // Get the text size int tx_size = getYPtCoord(pStream.readUnsignedShort()); - //if (mGraphics != null) { - // currentFont = mGraphics.getFont(); - // mGraphics.setFont(new Font(currentFont.getName(), currentFont.getStyle(), tx_size)); - //} context.setTextSize(tx_size); if (DEBUG) { System.out.println("txSize: " + tx_size); @@ -604,14 +573,23 @@ public class PICTImageReader extends ImageReaderBase { case 0x0012: // BkPixPat bg = PICTUtil.readColorPattern(pStream); context.setBackgroundPattern(bg); + if (DEBUG) { + System.out.println("BkPixPat"); + } break; case 0x0013: // PnPixPat pen = PICTUtil.readColorPattern(pStream); - context.setBackgroundPattern(pen); + context.setPenPattern(pen); + if (DEBUG) { + System.out.println("PnPixPat"); + } break; case 0x0014: // FillPixPat fill = PICTUtil.readColorPattern(pStream); - context.setBackgroundPattern(fill); + context.setFillPattern(fill); + if (DEBUG) { + System.out.println("FillPixPat"); + } break; case PICT.OP_PN_LOC_H_FRAC:// TO BE DONE??? @@ -633,23 +611,22 @@ public class PICTImageReader extends ImageReaderBase { case PICT.OP_RGB_FG_COL:// OK // Get the color pStream.readFully(colorBuffer, 0, colorBuffer.length); - foreground = new Color((colorBuffer[0] & 0xFF), (colorBuffer[2] & 0xFF), (colorBuffer[4] & 0xFF)); - //if (mGraphics != null) { - // mGraphics.setColor(foreground); - //} + Color foreground = new Color((colorBuffer[0] & 0xFF), (colorBuffer[2] & 0xFF), (colorBuffer[4] & 0xFF)); if (DEBUG) { System.out.println("rgbFgColor: " + foreground); } + context.setForeground(foreground); break; case PICT.OP_RGB_BK_COL:// OK // Get the color pStream.readFully(colorBuffer, 0, colorBuffer.length); - // TODO: The color might be 16 bit per component.. - background = new Color((colorBuffer[0] & 0xFF), (colorBuffer[2] & 0xFF), (colorBuffer[4] & 0xFF)); + // The color might be 16 bit per component.. + Color background = new Color(colorBuffer[0] & 0xFF, colorBuffer[2] & 0xFF, colorBuffer[4] & 0xFF); if (DEBUG) { System.out.println("rgbBgColor: " + background); } + context.setBackground(background); break; case PICT.OP_HILITE_MODE: @@ -725,11 +702,12 @@ public class PICTImageReader extends ImageReaderBase { x = getXPtCoord(pStream.readUnsignedShort()); origin = new Point(x, y); - y = getYPtCoord(pStream.readByte()); x = getXPtCoord(pStream.readByte()); + y = getYPtCoord(pStream.readByte()); dh_dv = new Point(x, y); // Move pen to new position, draw line if we have a graphics + context.moveTo(origin); penPosition.setLocation(origin.x + dh_dv.x, origin.y + dh_dv.y); context.lineTo(penPosition); @@ -740,8 +718,8 @@ public class PICTImageReader extends ImageReaderBase { case PICT.OP_SHORT_LINE_FROM:// OK // Get dh, dv - y = getYPtCoord(pStream.readByte()); x = getXPtCoord(pStream.readByte()); + y = getYPtCoord(pStream.readByte()); // Draw line context.line(x, y); @@ -788,10 +766,6 @@ public class PICTImageReader extends ImageReaderBase { penPosition.translate(dh, 0); context.moveTo(penPosition); text = PICTUtil.readPascalString(pStream); - // TODO -// if (mGraphics != null) { -// mGraphics.drawString(text, penPosition.x, penPosition.y); -// } context.drawString(text); if (DEBUG) { System.out.println("DHText dh: " + dh + ", text:" + text); @@ -804,10 +778,6 @@ public class PICTImageReader extends ImageReaderBase { penPosition.translate(0, dv); context.moveTo(penPosition); text = PICTUtil.readPascalString(pStream); - // TODO - //if (mGraphics != null) { - // mGraphics.drawString(text, penPosition.x, penPosition.y); - //} context.drawString(text); if (DEBUG) { System.out.println("DVText dv: " + dv + ", text:" + text); @@ -821,10 +791,6 @@ public class PICTImageReader extends ImageReaderBase { penPosition.translate(x, y); context.moveTo(penPosition); text = PICTUtil.readPascalString(pStream); - // TODO - //if (mGraphics != null) { - // mGraphics.drawString(text, penPosition.x, penPosition.y); - //} context.drawString(text); if (DEBUG) { System.out.println("DHDVText penPosition: " + penPosition + ", text:" + text); @@ -837,25 +803,20 @@ public class PICTImageReader extends ImageReaderBase { pStream.readShort(); // Get old font ID, ignored -// pStream.readInt(); pStream.readUnsignedShort(); // Get font name and set the new font if we have one - text = PICTUtil.readPascalString(pStream); - // TODO - //if (mGraphics != null) { - // mGraphics.setFont(Font.decode(text) - // .deriveFont(currentFont.getStyle(), currentFont.getSize())); - //} - context.drawString(text); + String fontName = PICTUtil.readPascalString(pStream); + context.setTextFont(fontName); if (DEBUG) { - System.out.println("fontName: \"" + text +"\""); + System.out.println("fontName: \"" + fontName +"\""); } break; - case PICT.OP_LINE_JUSTIFY:// TO BE DONE??? + case PICT.OP_LINE_JUSTIFY:// TODO // Get data - pStream.readFully(new byte[10], 0, 10); + byte[] lineJustifyData = new byte[10]; + pStream.readFully(lineJustifyData, 0, lineJustifyData.length); if (DEBUG) { System.out.println("opLineJustify"); } @@ -863,9 +824,10 @@ public class PICTImageReader extends ImageReaderBase { case PICT.OP_GLYPH_STATE:// TODO: NOT SUPPORTED IN AWT GRAPHICS YET? // Get data - pStream.readFully(new byte[6], 0, 6); + byte[] glyphState = new byte[6]; + pStream.readFully(glyphState, 0, glyphState.length); if (DEBUG) { - System.out.println("glyphState"); + System.out.println("glyphState: " + Arrays.toString(glyphState)); } break; @@ -1319,6 +1281,14 @@ public class PICTImageReader extends ImageReaderBase { // Polygon treatments finished break; + case 0x7d: + case 0x7e: + case 0x7f: + if (DEBUG) { + System.out.println(String.format("%s: 0x%04x", PICT.APPLE_USE_RESERVED_FIELD, opCode)); + } + break; + case 0x75: case 0x76: case 0x77: @@ -1440,14 +1410,14 @@ public class PICTImageReader extends ImageReaderBase { */ int rowBytesRaw = pStream.readUnsignedShort(); - int rowBytes = rowBytesRaw & 0x3FFF; + int rowBytes = rowBytesRaw & 0x7FFF; // TODO: Use rowBytes to determine size of PixMap/ColorTable? if ((rowBytesRaw & 0x8000) > 0) { // Do stuff... } - // Get bounds rectangle. THIS IS NOT TO BE SCALED BY THE RESOLUTION! TODO: ?! + // Get bounds rectangle. THIS IS NOT TO BE SCALED BY THE RESOLUTION! bounds = new Rectangle(); y = pStream.readUnsignedShort(); x = pStream.readUnsignedShort(); @@ -1455,8 +1425,7 @@ public class PICTImageReader extends ImageReaderBase { y = pStream.readUnsignedShort(); x = pStream.readUnsignedShort(); - bounds.setSize(x - bounds.x, - y - bounds.y); + bounds.setSize(x - bounds.x, y - bounds.y); Rectangle srcRect = new Rectangle(); readRectangle(pStream, srcRect); @@ -1465,7 +1434,6 @@ public class PICTImageReader extends ImageReaderBase { readRectangle(pStream, dstRect); mode = pStream.readUnsignedShort(); - context.setPenMode(mode); // TODO: Or parameter? if (DEBUG) { System.out.print("bitsRect, rowBytes: " + rowBytes); @@ -1496,13 +1464,6 @@ public class PICTImageReader extends ImageReaderBase { Rectangle rect = new Rectangle(srcRect); rect.translate(-bounds.x, -bounds.y); context.copyBits(image, rect, dstRect, mode, null); - //mGraphics.drawImage(image, - // dstRect.x, dstRect.y, - // dstRect.x + dstRect.width, dstRect.y + dstRect.height, - // srcRect.x - bounds.x, srcRect.y - bounds.y, - // srcRect.x - bounds.x + srcRect.width, srcRect.y - bounds.y + srcRect.height, - // null); - // break; case PICT.OP_BITS_RGN: @@ -1518,7 +1479,7 @@ public class PICTImageReader extends ImageReaderBase { pixData: PixData; */ if (DEBUG) { - System.out.println("bitsRgn"); + System.out.println("bitsRgn - TODO"); } break; @@ -1531,15 +1492,12 @@ public class PICTImageReader extends ImageReaderBase { dataLength = pStream.readUnsignedShort(); pStream.readFully(new byte[dataLength], 0, dataLength); if (DEBUG) { - System.out.println(String.format("%s: 0x%04x", PICT.APPLE_USE_RESERVED_FIELD, opCode)); + System.out.println(String.format("%s: 0x%04x - length: %d", PICT.APPLE_USE_RESERVED_FIELD, opCode, dataLength)); } break; case PICT.OP_PACK_BITS_RECT: - readOpPackBitsRect(pStream, bounds, pixmapCount++); - if (DEBUG) { - System.out.println("packBitsRect - TODO"); - } + readOpPackBitsRect(pStream, pixmapCount++); break; case PICT.OP_PACK_BITS_RGN: @@ -1551,7 +1509,7 @@ public class PICTImageReader extends ImageReaderBase { break; case PICT.OP_DIRECT_BITS_RECT: - readOpDirectBitsRect(pStream, bounds, pixmapCount++); + readOpDirectBitsRect(pStream, pixmapCount++); break; case PICT.OP_DIRECT_BITS_RGN: @@ -1575,17 +1533,21 @@ public class PICTImageReader extends ImageReaderBase { break; case PICT.OP_SHORT_COMMENT:// NOTHING TO DO, JUST JUMP OVER - pStream.readFully(new byte[2], 0, 2); + byte[] shortComment = new byte[2]; + pStream.readFully(shortComment, 0, 2); if (DEBUG) { - System.out.println("Short comment"); + System.out.println("Short comment: " + Arrays.toString(shortComment)); } break; case PICT.OP_LONG_COMMENT:// NOTHING TO DO, JUST JUMP OVER - readLongComment(pStream); - if (DEBUG) { - System.out.println("Long comment"); - } + /*byte[] longComment =*/ readLongComment(pStream); + // TODO: Don't just skip... + // https://developer.apple.com/legacy/library/documentation/mac/pdf/Imaging_With_QuickDraw/Appendix_B.pdf + // Long comments can be used for PhotoShop IRBs (kind 498) or ICC profiles (224) and other meta data... +// if (DEBUG) { +// System.out.println("Long comment: " + Arrays.toString(longComment)); +// } break; case PICT.OP_END_OF_PICTURE:// OK @@ -1654,6 +1616,8 @@ public class PICTImageReader extends ImageReaderBase { pStream.readFully(new byte[dataLength], 0, dataLength); } else { + // TODO: We could issue a warning and return instead? In any case, can't continue, as we don't know the length of the opcode... +// return; throw new IIOException(String.format("Found unknown opcode: 0x%04x", opCode)); } @@ -1737,7 +1701,7 @@ public class PICTImageReader extends ImageReaderBase { pStream.seek(pos + dataLength); // Might be word-align mismatch here - // Skip "QuickTime? and a ... decompressor required" text + // Skip "QuickTime™ and a ... decompressor required" text // TODO: Verify that this is correct. It works with all my test data, but the algorithm is // reverse-engineered by looking at the input data and not from any spec I've seen... int penSizeMagic = pStream.readInt(); @@ -1768,23 +1732,17 @@ public class PICTImageReader extends ImageReaderBase { */ - private void readOpPackBitsRect(ImageInputStream pStream, Rectangle pBounds, int pPixmapCount) throws IOException { - if (DEBUG) { - System.out.println("packBitsRect"); - } - - // Skip PixMap pointer (always 0x000000FF); -// pStream.skipBytes(4); -// int pixmapPointer = pStream.readInt(); -// System.out.println(String.format("%08d: 0x%08x", pStream.getStreamPosition(), pixmapPointer)); - + private void readOpPackBitsRect(final ImageInputStream pStream, final int pPixmapCount) throws IOException { // Get rowBytes int rowBytesRaw = pStream.readUnsignedShort(); // System.out.println(String.format("%08d: 0x%04x", pStream.getStreamPosition(), rowBytesRaw)); - int rowBytes = rowBytesRaw & 0x3FFF; + // TODO: This way to determine pixmap vs bitmap is for version 2 only! + int rowBytes = rowBytesRaw & 0x7FFF; + boolean isPixMap = (rowBytesRaw & 0x8000) > 0; + if (DEBUG) { System.out.print("packBitsRect, rowBytes: " + rowBytes); - if ((rowBytesRaw & 0x8000) > 0) { + if (isPixMap) { System.out.print(", it is a PixMap"); } else { @@ -1793,98 +1751,114 @@ public class PICTImageReader extends ImageReaderBase { } // Get bounds rectangle. THIS IS NOT TO BE SCALED BY THE RESOLUTION! + // TODO: ...or then again...? :-) + Rectangle bounds = new Rectangle(); int y = pStream.readUnsignedShort(); int x = pStream.readUnsignedShort(); - pBounds.setLocation(x, y); + bounds.setLocation(x, y); y = pStream.readUnsignedShort(); x = pStream.readUnsignedShort(); - pBounds.setSize(x - pBounds.x, y - pBounds.y); + bounds.setSize(x - bounds.x, y - bounds.y); if (DEBUG) { - System.out.print(", bounds: " + pBounds); + System.out.print(", bounds: " + bounds); } - // Get PixMap record version number - int pmVersion = pStream.readUnsignedShort() & 0xFFFF; - if (DEBUG) { - System.out.print(", pmVersion: " + pmVersion); - } - - // Get packing format - int packType = pStream.readUnsignedShort() & 0xFFFF; - if (DEBUG) { - System.out.print(", packType: " + packType); - } - - // Get size of packed data (not used for v2) - int packSize = pStream.readInt(); - if (DEBUG) { - System.out.println(", packSize: " + packSize); - } - - // Get resolution info - double hRes = PICTUtil.readFixedPoint(pStream); - double vRes = PICTUtil.readFixedPoint(pStream); - if (DEBUG) { - System.out.print("hRes: " + hRes + ", vRes: " + vRes); - } - - // Get pixel type - int pixelType = pStream.readUnsignedShort(); - if (DEBUG) { - if (pixelType == 0) { - System.out.print(", indexed pixels"); - } - else { - System.out.print(", RGBDirect"); - } - } - - // Get pixel size - int pixelSize = pStream.readUnsignedShort(); - if (DEBUG) { - System.out.print(", pixelSize:" + pixelSize); - } - - // Get pixel component count - int cmpCount = pStream.readUnsignedShort(); - if (DEBUG) { - System.out.print(", cmpCount:" + cmpCount); - } - - // Get pixel component size - int cmpSize = pStream.readUnsignedShort(); - if (DEBUG) { - System.out.print(", cmpSize:" + cmpSize); - } - - // planeBytes (ignored) - int planeBytes = pStream.readInt(); - if (DEBUG) { - System.out.print(", planeBytes:" + planeBytes); - } - - // Handle to ColorTable record, there should be none for direct - // bits so this should be 0, just skip - int clutId = pStream.readInt(); - if (DEBUG) { - System.out.println(", clutId:" + clutId); - } - - // Reserved - pStream.readInt(); - - // Color table ColorModel colorModel; - if (pixelType == 0) { + int cmpSize; + + if (isPixMap) { + // Get PixMap record version number + int pmVersion = pStream.readUnsignedShort(); + if (DEBUG) { + System.out.print(", pmVersion: " + pmVersion); + } + + // Get packing format + int packType = pStream.readUnsignedShort(); + if (DEBUG) { + System.out.print(", packType: " + packType); + } + + // Get size of packed data (not used for v2) + int packSize = pStream.readInt(); // TODO: Probably not int for BitMap (value seems too high)? + if (DEBUG) { + System.out.println(", packSize: " + packSize); + } + + // Get resolution info + double hRes = PICTUtil.readFixedPoint(pStream); + double vRes = PICTUtil.readFixedPoint(pStream); + if (DEBUG) { + System.out.print("hRes: " + hRes + ", vRes: " + vRes); + } + + // Get pixel type + int pixelType = pStream.readUnsignedShort(); + if (DEBUG) { + if (pixelType == 0) { + System.out.print(", indexed pixels"); + } + else { + System.out.print(", RGBDirect"); + } + } + + // Get pixel size + int pixelSize = pStream.readUnsignedShort(); + if (DEBUG) { + System.out.print(", pixelSize:" + pixelSize); + } + + // Get pixel component count + int cmpCount = pStream.readUnsignedShort(); + if (DEBUG) { + System.out.print(", cmpCount:" + cmpCount); + } + + // Get pixel component size + cmpSize = pStream.readUnsignedShort(); + if (DEBUG) { + System.out.print(", cmpSize:" + cmpSize); + } + + // planeBytes (ignored) + int planeBytes = pStream.readInt(); + if (DEBUG) { + System.out.print(", planeBytes:" + planeBytes); + } + + // Handle to ColorTable record + int clutId = pStream.readInt(); + if (DEBUG) { + System.out.println(", clutId:" + clutId); + } + + // Reserved + pStream.readInt(); + + // TODO: Seems to be packType 0 all the time? + // packType = 0 means default.... + + if (packType != 0) { + throw new IIOException("Unknown pack type: " + packType); + } + if (pixelType != 0) { + throw new IIOException("Unsupported pixel type: " + pixelType); + } + + // Color table colorModel = PICTUtil.readColorTable(pStream, pixelSize); } else { - throw new IIOException("Unsupported pixel type: " + pixelType); + // Old style BitMap record + cmpSize = 1; + colorModel = QuickDraw.MONOCHROME; } // Get source rectangle. We DO NOT scale the coordinates by the // resolution info, since we are in pixmap coordinates here + // TODO: readReactangleNonScaled() Rectangle srcRect = new Rectangle(); y = pStream.readUnsignedShort(); x = pStream.readUnsignedShort(); @@ -1910,157 +1884,58 @@ public class PICTImageReader extends ImageReaderBase { // Get transfer mode int transferMode = pStream.readUnsignedShort(); if (DEBUG) { - System.out.print(", mode: " + transferMode); + System.out.println(", mode: " + transferMode); } // Set up pixel buffer for the RGB values - - // TODO: Seems to be packType 0 all the time? - // packType = 0 means default.... - - - // Read in the RGB arrays - byte[] dstBytes; - /* - if (packType == 1 || rowBytes < 8) { - // TODO: Verify this... - dstBytes = new byte[rowBytes]; - } - else if (packType == 2) { - // TODO: Verify this... - dstBytes = new byte[rowBytes * 3 / 4]; - } - else if (packType == 3) { - dstBytes = new byte[2 * pBounds.width]; - } - else if (packType == 4) { - dstBytes = new byte[cmpCount * pBounds.width]; - } - else { - throw new IIOException("Unknown pack type: " + packType); - } - */ - if (packType == 0) { - dstBytes = new byte[cmpCount * pBounds.width]; - } - else { - throw new IIOException("Unknown pack type: " + packType); - } - -// int[] pixArray = new int[pBounds.height * pBounds.width]; - byte[] pixArray = new byte[pBounds.height * pBounds.width]; + byte[] pixArray = new byte[srcRect.height * rowBytes]; int pixBufOffset = 0; - int packedBytesCount; - for (int scanline = 0; scanline < pBounds.height; scanline++) { - // Get byteCount of the scanline - if (rowBytes > 250) { - packedBytesCount = pStream.readUnsignedShort(); - } - else { - packedBytesCount = pStream.readUnsignedByte(); - } - if (DEBUG) { - System.out.println(); - System.out.print("Line " + scanline + ", byteCount: " + packedBytesCount); - System.out.print(" dstBytes: " + dstBytes.length); - } - + // Read in the RGB arrays + for (int scanline = 0; scanline < srcRect.height; scanline++) { // Read in the scanline - /*if (packType > 2) { - // Unpack them all*/ - Decoder decoder;/* - if (packType == 3) { - decoder = new PackBits16Decoder(); - } - else {*/ - decoder = new PackBitsDecoder(); - /*}*/ - DataInput unPackBits = new DataInputStream(new DecoderStream(IIOUtil.createStreamAdapter(pStream, packedBytesCount), decoder)); -// unPackBits.readFully(dstBytes); - unPackBits.readFully(pixArray, pixBufOffset, pBounds.width); - /*} - else { - imageInput.readFully(dstBytes); - }*/ + if (rowBytes > 8) { + // Get byteCount of the scanline + int packedBytesCount = rowBytes > 250 ? pStream.readUnsignedShort() : pStream.readUnsignedByte(); - // TODO: Use TYPE_USHORT_555_RGB for 16 bit - /* - if (packType == 3) { - for (int i = 0; i < pBounds.width; i++) { - // Set alpha values to all opaque - pixArray[pixBufOffset + i] = 0xFF000000; - - // Get red values - int red = 8 * ((dstBytes[2 * i] & 0x7C) >> 2); - pixArray[pixBufOffset + i] |= red << 16; - // Get green values - int green = 8 * (((dstBytes[2 * i] & 0x07) << 3) + ((dstBytes[2 * i + 1] & 0xE0) >> 5)); - pixArray[pixBufOffset + i] |= green << 8; - // Get blue values - int blue = 8 * ((dstBytes[2 * i + 1] & 0x1F)); - pixArray[pixBufOffset + i] |= blue; - } + // Unpack them all + Decoder decoder = new PackBitsDecoder(); + DataInput unPackBits = new DataInputStream(new DecoderStream(IIOUtil.createStreamAdapter(pStream, packedBytesCount), decoder)); + unPackBits.readFully(pixArray, pixBufOffset, rowBytes); } else { - if (cmpCount == 3) { - for (int i = 0; i < pBounds.width; i++) { - // Set alpha values to all opaque - pixArray[pixBufOffset + i] = 0xFF000000; - // Get red values - pixArray[pixBufOffset + i] |= (dstBytes[i] & 0xFF) << 16; - // Get green values - pixArray[pixBufOffset + i] |= (dstBytes[pBounds.width + i] & 0xFF) << 8; - // Get blue values - pixArray[pixBufOffset + i] |= (dstBytes[2 * pBounds.width + i] & 0xFF); - } - } - else { - for (int i = 0; i < pBounds.width; i++) { -// // Get alpha values -// pixArray[pixBufOffset + i] = (dstBytes[i] & 0xFF) << 24; -// // Get red values -// pixArray[pixBufOffset + i] |= (dstBytes[pBounds.width + i] & 0xFF) << 16; -// // Get green values -// pixArray[pixBufOffset + i] |= (dstBytes[2 * pBounds.width + i] & 0xFF) << 8; -// // Get blue values -// pixArray[pixBufOffset + i] |= (dstBytes[3 * pBounds.width + i] & 0xFF); - - // TODO: Fake it for now... Should ideally just use byte array and use the ICM -// pixArray[pixBufOffset + i] = 0xFF << 24; -// pixArray[pixBufOffset + i] |= colorModel.getRed(dstBytes[i] & 0xFF) << 16; -// pixArray[pixBufOffset + i] |= colorModel.getGreen(dstBytes[i] & 0xFF) << 8; -// pixArray[pixBufOffset + i] |= colorModel.getBlue(dstBytes[i] & 0xFF); - - pixArray[pixBufOffset + i] = dstBytes[i]; - } -// } -// } -*/ + // Uncompressed + imageInput.readFully(pixArray, pixBufOffset, rowBytes); + } // Increment pixel buffer offset - pixBufOffset += pBounds.width; + pixBufOffset += rowBytes; //////////////////////////////////////////////////// // TODO: This works for single image PICTs only... // However, this is the most common case. Ok for now - processImageProgress(scanline * 100 / pBounds.height); + processImageProgress(scanline * 100 / srcRect.height); if (abortRequested()) { processReadAborted(); // Skip rest of image data - for (int skip = scanline + 1; skip < pBounds.height; skip++) { + for (int skip = scanline + 1; skip < srcRect.height; skip++) { // Get byteCount of the scanline - if (rowBytes > 250) { + int packedBytesCount; + + if (rowBytes <= 8) { + packedBytesCount = rowBytes; + } + else if (rowBytes > 250) { packedBytesCount = pStream.readUnsignedShort(); } else { packedBytesCount = pStream.readUnsignedByte(); } + pStream.readFully(new byte[packedBytesCount], 0, packedBytesCount); if (DEBUG) { - System.out.println(); System.out.print("Skip " + skip + ", byteCount: " + packedBytesCount); } } @@ -2074,11 +1949,8 @@ public class PICTImageReader extends ImageReaderBase { // "pPixmapCount" will never be greater than the size of the vector if (images.size() <= pPixmapCount) { // Create BufferedImage and add buffer it for multiple reads -// DirectColorModel cm = (DirectColorModel) ColorModel.getRGBdefault(); -// DataBuffer db = new DataBufferInt(pixArray, pixArray.length); -// WritableRaster raster = Raster.createPackedRaster(db, pBounds.width, pBounds.height, pBounds.width, cm.getMasks(), null); DataBuffer db = new DataBufferByte(pixArray, pixArray.length); - WritableRaster raster = Raster.createPackedRaster(db, pBounds.width, pBounds.height, cmpSize, null); // TODO: last param should ideally be srcRect.getLocation() + WritableRaster raster = Raster.createPackedRaster(db, (rowBytes * 8) / cmpSize, srcRect.height, cmpSize, null); // TODO: last param should ideally be srcRect.getLocation() BufferedImage img = new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null); images.add(img); @@ -2102,24 +1974,23 @@ public class PICTImageReader extends ImageReaderBase { * Reads the data following a {@code directBitsRect} opcode. * * @param pStream the stream to read from - * @param pBounds the bounding rectangle * @param pPixmapCount the index of the bitmap in the PICT file, used for * cahcing. * * @throws javax.imageio.IIOException if the data can not be read. - * @throws IOException if an I/O error occurs while reading the image. + * @throws java.io.IOException if an I/O error occurs while reading the image. */ - private void readOpDirectBitsRect(ImageInputStream pStream, Rectangle pBounds, int pPixmapCount) throws IOException { + private void readOpDirectBitsRect(final ImageInputStream pStream, final int pPixmapCount) throws IOException { if (DEBUG) { System.out.println("directBitsRect"); } // Skip PixMap pointer (always 0x000000FF); - pStream.skipBytes(4); + pStream.readInt(); // Get rowBytes int rowBytesRaw = pStream.readUnsignedShort(); - int rowBytes = rowBytesRaw & 0x3FFF; + int rowBytes = rowBytesRaw & 0x7FFF; if (DEBUG) { System.out.print("directBitsRect, rowBytes: " + rowBytes); if ((rowBytesRaw & 0x8000) > 0) { @@ -2131,25 +2002,27 @@ public class PICTImageReader extends ImageReaderBase { } // Get bounds rectangle. THIS IS NOT TO BE SCALED BY THE RESOLUTION! + // TODO: ...or then again...? :-) + Rectangle bounds = new Rectangle(); int y = pStream.readUnsignedShort(); int x = pStream.readUnsignedShort(); - pBounds.setLocation(x, y); + bounds.setLocation(x, y); y = pStream.readUnsignedShort(); x = pStream.readUnsignedShort(); - pBounds.setSize(x - pBounds.x, y - pBounds.y); + bounds.setSize(x - bounds.x, y - bounds.y); if (DEBUG) { - System.out.print(", bounds: " + pBounds); + System.out.print(", bounds: " + bounds); } // Get PixMap record version number - int pmVersion = pStream.readUnsignedShort() & 0xFFFF; + int pmVersion = pStream.readUnsignedShort(); if (DEBUG) { System.out.print(", pmVersion: " + pmVersion); } // Get packing format - int packType = pStream.readUnsignedShort() & 0xFFFF; + int packType = pStream.readUnsignedShort(); if (DEBUG) { System.out.print(", packType: " + packType); } @@ -2221,7 +2094,6 @@ public class PICTImageReader extends ImageReaderBase { System.out.print("opDirectBitsRect, srcRect:" + srcRect); } - // TODO: FixMe... // Get destination rectangle. We DO scale the coordinates according to // the image resolution, since we are working in display coordinates Rectangle dstRect = new Rectangle(); @@ -2240,19 +2112,11 @@ public class PICTImageReader extends ImageReaderBase { // Read in the RGB arrays byte[] dstBytes; - if (packType == 1 || rowBytes < 8) { - // TODO: Verify this... + if (packType == 1 || packType == 2 || packType == 3) { dstBytes = new byte[rowBytes]; } - else if (packType == 2) { - // TODO: Verify this... - dstBytes = new byte[rowBytes * 3 / 4]; - } - else if (packType == 3) { - dstBytes = new byte[2 * pBounds.width]; - } else if (packType == 4) { - dstBytes = new byte[cmpCount * pBounds.width]; + dstBytes = new byte[cmpCount * rowBytes / 4]; } else { throw new IIOException("Unknown pack type: " + packType); @@ -2261,40 +2125,35 @@ public class PICTImageReader extends ImageReaderBase { int[] pixArray = null; short[] shortArray = null; if (packType == 3) { - shortArray = new short[pBounds.height * pBounds.width]; + shortArray = new short[srcRect.height * (rowBytes + 1) / 2]; } else { - pixArray = new int[pBounds.height * pBounds.width]; + pixArray = new int[srcRect.height * (rowBytes + 3) / 4]; } int pixBufOffset = 0; int packedBytesCount; - for (int scanline = 0; scanline < pBounds.height; scanline++) { - // Get byteCount of the scanline - if (rowBytes > 250) { - packedBytesCount = pStream.readUnsignedShort(); - } - else { - packedBytesCount = pStream.readUnsignedByte(); - } - if (DEBUG) { - System.out.println(); - System.out.print("Line " + scanline + ", byteCount: " + packedBytesCount); - System.out.print(" dstBytes: " + dstBytes.length); - } - + for (int scanline = 0; scanline < srcRect.height; scanline++) { // Read in the scanline if (packType > 2) { - // Unpack them all - Decoder decoder; - if (packType == 3) { - decoder = new PackBits16Decoder(); + // Get byteCount of the scanline + if (rowBytes > 250) { + packedBytesCount = pStream.readUnsignedShort(); } else { - decoder = new PackBitsDecoder(); + packedBytesCount = pStream.readUnsignedByte(); } - DataInput unPackBits = new DataInputStream(new DecoderStream(IIOUtil.createStreamAdapter(pStream, packedBytesCount), decoder)); + + if (DEBUG) { + System.out.print("Line " + scanline + ", byteCount: " + packedBytesCount); + System.out.print(" dstBytes: " + dstBytes.length); + System.out.println(); + } + + // Unpack them all + Decoder decoder = packType == 3 ? new PackBitsDecoder(2, false) : new PackBitsDecoder(); + DataInput unPackBits = new DataInputStream(new DecoderStream(IIOUtil.createStreamAdapter(pStream, packedBytesCount), decoder, dstBytes.length)); unPackBits.readFully(dstBytes); } else { @@ -2303,63 +2162,51 @@ public class PICTImageReader extends ImageReaderBase { if (packType == 3) { // TYPE_USHORT_555_RGB for 16 bit - for (int i = 0; i < pBounds.width; i++) { + for (int i = 0; i < srcRect.width; i++) { shortArray[pixBufOffset + i] = (short) (((0xff & dstBytes[2 * i]) << 8) | (0xff & dstBytes[2 * i + 1])); -// // Set alpha values to all opaque -// pixArray[pixBufOffset + i] = 0xFF000000; -// -// // Get red values -// int red = 8 * ((dstBytes[2 * i] & 0x7C) >> 2); -// pixArray[pixBufOffset + i] |= red << 16; -// // Get green values -// int green = 8 * (((dstBytes[2 * i] & 0x07) << 3) + ((dstBytes[2 * i + 1] & 0xE0) >> 5)); -// pixArray[pixBufOffset + i] |= green << 8; -// // Get blue values -// int blue = 8 * ((dstBytes[2 * i + 1] & 0x1F)); -// pixArray[pixBufOffset + i] |= blue; } } else { if (cmpCount == 3) { // RGB - for (int i = 0; i < pBounds.width; i++) { + for (int i = 0; i < srcRect.width; i++) { // Set alpha values to all opaque - pixArray[pixBufOffset + i] = 0xFF000000; + pixArray[pixBufOffset + i] = 0xFF000000 // Get red values - pixArray[pixBufOffset + i] |= (dstBytes[i] & 0xFF) << 16; + | (dstBytes[/*0* bounds.width*/i] & 0xFF) << 16 // Get green values - pixArray[pixBufOffset + i] |= (dstBytes[pBounds.width + i] & 0xFF) << 8; + | (dstBytes[/**/bounds.width + i] & 0xFF) << 8 // Get blue values - pixArray[pixBufOffset + i] |= (dstBytes[2 * pBounds.width + i] & 0xFF); + | (dstBytes[2 * bounds.width + i] & 0xFF); } } else { // ARGB - for (int i = 0; i < pBounds.width; i++) { + for (int i = 0; i < srcRect.width; i++) { // Get alpha values - pixArray[pixBufOffset + i] = (dstBytes[i] & 0xFF) << 24; + pixArray[pixBufOffset + i] = (dstBytes[/*0* bounds.width*/i] & 0xFF) << 24 // Get red values - pixArray[pixBufOffset + i] |= (dstBytes[pBounds.width + i] & 0xFF) << 16; + | (dstBytes[/**/bounds.width + i] & 0xFF) << 16 // Get green values - pixArray[pixBufOffset + i] |= (dstBytes[2 * pBounds.width + i] & 0xFF) << 8; + | (dstBytes[2 * bounds.width + i] & 0xFF) << 8 // Get blue values - pixArray[pixBufOffset + i] |= (dstBytes[3 * pBounds.width + i] & 0xFF); + | (dstBytes[3 * bounds.width + i] & 0xFF); } } } // Increment pixel buffer offset - pixBufOffset += pBounds.width; + pixBufOffset += srcRect.width; //////////////////////////////////////////////////// // TODO: This works for single image PICTs only... // However, this is the most common case. Ok for now - processImageProgress(scanline * 100 / pBounds.height); + processImageProgress(scanline * 100 / srcRect.height); if (abortRequested()) { processReadAborted(); // Skip rest of image data - for (int skip = scanline + 1; skip < pBounds.height; skip++) { + for (int skip = scanline + 1; skip < srcRect.height; skip++) { // Get byteCount of the scanline if (rowBytes > 250) { packedBytesCount = pStream.readUnsignedShort(); @@ -2390,12 +2237,12 @@ public class PICTImageReader extends ImageReaderBase { if (packType == 3) { cm = new DirectColorModel(15, 0x7C00, 0x03E0, 0x001F); // See BufferedImage TYPE_USHORT_555_RGB DataBuffer db = new DataBufferUShort(shortArray, shortArray.length); - raster = Raster.createPackedRaster(db, pBounds.width, pBounds.height, pBounds.width, cm.getMasks(), null); // TODO: last param should ideally be srcRect.getLocation() + raster = Raster.createPackedRaster(db, srcRect.width, srcRect.height, srcRect.width, cm.getMasks(), null); // TODO: last param should ideally be srcRect.getLocation() } else { cm = (DirectColorModel) ColorModel.getRGBdefault(); DataBuffer db = new DataBufferInt(pixArray, pixArray.length); - raster = Raster.createPackedRaster(db, pBounds.width, pBounds.height, pBounds.width, cm.getMasks(), null); // TODO: last param should ideally be srcRect.getLocation() + raster = Raster.createPackedRaster(db, srcRect.width, srcRect.height, srcRect.width, cm.getMasks(), null); // TODO: last param should ideally be srcRect.getLocation() } BufferedImage img = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null); @@ -2540,13 +2387,20 @@ public class PICTImageReader extends ImageReaderBase { /* * Read a long comment from the stream. */ - private void readLongComment(final DataInput pStream) throws IOException { + private byte[] readLongComment(final DataInput pStream) throws IOException { // Comment kind and data byte count - pStream.readShort(); + short kind = pStream.readShort(); + int length = pStream.readUnsignedShort(); + + if (DEBUG) { + System.err.println("Long comment: " + kind + ", " + length + " bytes"); + } // Get as many bytes as indicated by byte count - int length = pStream.readUnsignedShort(); - pStream.readFully(new byte[length], 0, length); + byte[] bytes = new byte[length]; + pStream.readFully(bytes, 0, length); + + return bytes; } /* @@ -2623,12 +2477,19 @@ public class PICTImageReader extends ImageReaderBase { BufferedImage image = getDestination(pParam, getImageTypes(pIndex), getXPtCoord(frame.width), getYPtCoord(frame.height)); Graphics2D g = image.createGraphics(); try { - // TODO: Might need to clear background + // Might need to clear background + g.setComposite(AlphaComposite.Src); + g.setColor(new Color(0x00ffffff, true)); // Transparent white +// g.setColor(Color.WHITE); + g.fillRect(0, 0, image.getWidth(), image.getHeight()); + AffineTransform instance = new AffineTransform(); + if (pParam != null && pParam.getSourceRegion() != null) { Rectangle rectangle = pParam.getSourceRegion(); instance.translate(-rectangle.x, -rectangle.y); } + instance.scale(screenImageXRatio / subX, screenImageYRatio / subY); g.setTransform(instance); // try { @@ -2667,158 +2528,37 @@ public class PICTImageReader extends ImageReaderBase { ).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); + protected static void showIt(final BufferedImage pImage, final String pTitle) { + ImageReaderBase.showIt(pImage, pTitle); } - // 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 */ - }; + public static void main(final String[] pArgs) throws IOException { + ImageReader reader = new PICTImageReader(new PICTImageReaderSpi()); - 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 */ - }; + for (String arg : pArgs) { + File file = new File(arg); + try { + ImageInputStream input = ImageIO.createImageInputStream(file); + String title = file.getName(); - 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 */ - }; + System.out.println("canRead: " + reader.getOriginatingProvider().canDecodeInput(input)); - // 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 */ - }; + reader.setInput(input); + // BufferedImage image = reader.getImageTypes(0).next().createBufferedImage(reader.getWidth(0), reader.getHeight(0)); + // ImageReadParam param = reader.getDefaultReadParam(); + // param.setDestination(image); - 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 */ - }; + long start = System.currentTimeMillis(); + BufferedImage image = reader.read(0); + System.out.println("time: " + (System.currentTimeMillis() - start)); - 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 */ - }; + showIt(image, title); + + System.out.println("image = " + image); + } + catch (IOException e) { + System.err.println("Could not read " + file.getAbsolutePath() + ": " + e); + } + } + } } diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderSpi.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderSpi.java index bfa92b0c..76a23cab 100755 --- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderSpi.java +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderSpi.java @@ -34,8 +34,8 @@ import com.twelvemonkeys.imageio.util.IIOUtil; import javax.imageio.ImageReader; import javax.imageio.spi.ImageReaderSpi; import javax.imageio.stream.ImageInputStream; -import java.io.IOException; import java.io.EOFException; +import java.io.IOException; import java.util.Locale; /** @@ -85,7 +85,7 @@ public class PICTImageReaderSpi extends ImageReaderSpi { else { // Skip header 512 bytes for file-based streams stream.reset(); - PICTImageReader.skipNullHeader(stream); + skipNullHeader(stream); } return isPICT(stream); @@ -98,6 +98,12 @@ public class PICTImageReaderSpi extends ImageReaderSpi { } } + static void skipNullHeader(final ImageInputStream pStream) throws IOException { + // NOTE: Only skip if FILE FORMAT, not needed for Mac OS DnD + // Spec says "platofrm dependent", may not be all nulls.. + pStream.skipBytes(PICT.PICT_NULL_HEADER_SIZE); + } + private boolean isPICT(final ImageInputStream pStream) throws IOException { // Size may be 0, so we can't use this for validation... pStream.readUnsignedShort(); diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageWriter.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageWriter.java index a4baa53c..df8734b6 100755 --- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageWriter.java +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageWriter.java @@ -258,7 +258,7 @@ public class PICTImageWriter extends ImageWriterBase { // Treat the scanline. for (int j = 0; j < w; j++) { if (model instanceof ComponentColorModel && model.getColorSpace().getType() == ColorSpace.TYPE_RGB) { - // NOTE: Assumes component order always (A)BGR + // NOTE: Assumes component order always (A)BGR and sRGB // TODO: Alpha support scanlineBytes[x + j] = pixels[off + i * scansize * components + components * j + components - 1]; scanlineBytes[x + w + j] = pixels[off + i * scansize * components + components * j + components - 2]; diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTUtil.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTUtil.java index ef503715..08de9277 100755 --- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTUtil.java +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTUtil.java @@ -34,7 +34,8 @@ import java.awt.image.DataBuffer; import java.awt.image.IndexColorModel; import java.io.DataInput; import java.io.IOException; -import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.nio.charset.UnsupportedCharsetException; /** * PICTUtil @@ -47,15 +48,14 @@ final class PICTUtil { private static final String ENC_MAC_ROMAN = "MacRoman"; - public static final String ENCODING = initEncoding(); + public static final Charset ENCODING = initEncoding(); - private static String initEncoding() { + private static Charset initEncoding() { try { - new String("\uF8FF".getBytes(), ENC_MAC_ROMAN); - return ENC_MAC_ROMAN; + return Charset.forName(ENC_MAC_ROMAN); } - catch (UnsupportedEncodingException e) { - return "ISO-8859-1"; + catch (UnsupportedCharsetException e) { + return Charset.forName("ISO-8859-1"); } } @@ -86,9 +86,9 @@ final class PICTUtil { * @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); + int h = pStream.readShort(); + int v = pStream.readShort(); + return new Dimension(h, v); } /** @@ -102,8 +102,8 @@ final class PICTUtil { * @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(); + String text = readPascalString(pStream); + int length = 31 - text.length(); if (length < 0) { throw new IOException("String length exceeds maximum (31): " + text.length()); } @@ -112,7 +112,7 @@ final class PICTUtil { } /** - * Reads a Pascal String from the given strean. + * Reads a Pascal String from the given stream. * The input stream must be positioned at the length byte of the text, * which can thus be a maximum of 255 characters long. * @@ -146,6 +146,14 @@ final class PICTUtil { return new BitMapPattern(data); } + // TODO: Refactor, don't need both readPattern methods + public static Pattern readPattern(final DataInput pStream, final Color fg, final Color bg) throws IOException { + // Get the data (8 bytes) + byte[] data = new byte[8]; + pStream.readFully(data); + return new BitMapPattern(data, fg, bg); + } + /** * Reads a variable width {@link Pattern color pattern} from the given stream * @@ -221,7 +229,7 @@ final class PICTUtil { /** * Reads a {@code ColorTable} data structure from the given stream. * - * @param pStream the input stream + * @param pStream the input stream * @param pPixelSize the pixel size * @return the indexed color model created from the {@code ColorSpec} records read. * @@ -252,7 +260,7 @@ final class PICTUtil { int[] colors = new int[size]; - for (int i = 0; i < size ; i++) { + for (int i = 0; i < size; i++) { // Read ColorSpec records int index = pStream.readUnsignedShort(); Color color = readRGBColor(pStream); diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/Pattern.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/Pattern.java index 32841877..13ceeec1 100755 --- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/Pattern.java +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/Pattern.java @@ -29,9 +29,9 @@ 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.awt.geom.Rectangle2D; +import java.awt.image.ColorModel; import java.util.Collections; /** @@ -42,7 +42,7 @@ import java.util.Collections; * @version $Id: Pattern.java,v 1.0 Oct 9, 2007 1:21:38 AM haraldk Exp$ */ abstract class Pattern implements Paint { - private final Paint paint; + protected final Paint paint; Pattern(final Paint pPaint) { paint = pPaint; @@ -60,5 +60,7 @@ abstract class Pattern implements Paint { public int getTransparency() { return paint.getTransparency(); - } + } + + public abstract Pattern derive(Color foreground, Color background); } diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PixMap.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PixMap.java new file mode 100644 index 00000000..43192ca8 --- /dev/null +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PixMap.java @@ -0,0 +1,11 @@ +package com.twelvemonkeys.imageio.plugins.pict; + +/** + * PixMap. + * + * @author Harald Kuhr + * @author last modified by $Author: harald.kuhr$ + * @version $Id: PixMap.java,v 1.0 20/02/15 harald.kuhr Exp$ + */ +final class PixMap { +} diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PixMapPattern.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PixMapPattern.java index d2108320..d84f0a45 100755 --- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PixMapPattern.java +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PixMapPattern.java @@ -48,7 +48,12 @@ final class PixMapPattern extends Pattern { /** * @return the fallback B/W pattern */ - public Pattern getPattern() { + public Pattern getFallbackPattern() { return fallback; } + + @Override + public Pattern derive(final Color foreground, final Color background) { + return getFallbackPattern().derive(foreground, background); + } } diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QuickDrawContext.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QuickDrawContext.java index e9f52f9d..fd5a0567 100755 --- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QuickDrawContext.java +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QuickDrawContext.java @@ -31,8 +31,11 @@ package com.twelvemonkeys.imageio.plugins.pict; import com.twelvemonkeys.lang.Validate; import java.awt.*; -import java.awt.image.BufferedImage; import java.awt.geom.*; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; /** * Emulates an Apple QuickDraw rendering context, backed by a Java {@link Graphics2D}. @@ -121,9 +124,20 @@ class QuickDrawContext { private Dimension2D penSize = new Dimension(); private int penMode; - QuickDrawContext(Graphics2D pGraphics) { + // TODO: Make sure setting bgColor/fgColor does not reset pattern, and pattern not resetting bg/fg! + private Color bgColor = Color.WHITE; + private Color fgColor = Color.BLACK; + + private int textMode; + private Pattern textPattern = new BitMapPattern(Color.BLACK); + private Pattern fillPattern; + + QuickDrawContext(final Graphics2D pGraphics) { graphics = Validate.notNull(pGraphics, "graphics"); - + + graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + setPenNormal(); } @@ -144,18 +158,34 @@ class QuickDrawContext { // Font number (sic), integer void setTextFont(int fontFamily) { // ..? - System.err.println("QuickDrawContext.setTextFont"); + System.err.println("QuickDrawContext.setTextFont: " + fontFamily); + } + + public void setTextFont(final String fontName) { + // TODO: Need mapping between known QD font names and Java font names? + Font current = graphics.getFont(); + graphics.setFont(Font.decode(fontName).deriveFont(current.getStyle(), (float) current.getSize())); } // Sets the text's font style (0..255) - void setTextFace(int face) { - // int? - System.err.println("QuickDrawContext.setTextFace"); + void setTextFace(final int face) { + int style = 0; + if ((face & QuickDraw.TX_BOLD_MASK) > 0) { + style |= Font.BOLD; + } + if ((face & QuickDraw.TX_ITALIC_MASK) > 0) { + style |= Font.ITALIC; + } + + // TODO: Other face options, like underline, shadow, etc... + + graphics.setFont(graphics.getFont().deriveFont(style)); } void setTextMode(int pSourceMode) { // ..? System.err.println("QuickDrawContext.setTextMode"); + textMode = pSourceMode; } public void setTextSize(int pSize) { @@ -175,15 +205,24 @@ class QuickDrawContext { graphics.translate(pOrigin.getX(), pOrigin.getY()); } - public void setForeground(Color pColor) { - // TODO: Is this really correct? Or does it depend on pattern mode? + public void setForeground(final Color pColor) { + fgColor = pColor; penPattern = new BitMapPattern(pColor); } - public void setBackground(Color pColor) { + Color getForeground() { + return fgColor; + } + + public void setBackground(final Color pColor) { + bgColor = pColor; background = new BitMapPattern(pColor); } + Color getBackground() { + return bgColor; + } + /* // 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. @@ -306,10 +345,14 @@ class QuickDrawContext { BackPat // Used by the Erase* methods *BackPixPat */ - public void setBackgroundPattern(Pattern pPaint) { + public void setBackgroundPattern(final Pattern pPaint) { background = pPaint; } + public void setFillPattern(final Pattern fillPattern) { + this.fillPattern = fillPattern; + } + private Composite getCompositeFor(final int pMode) { switch (pMode & ~QuickDraw.DITHER_COPY) { // Boolean source transfer modes @@ -321,9 +364,10 @@ class QuickDrawContext { return AlphaComposite.Xor; case QuickDraw.SRC_BIC: return AlphaComposite.Clear; + case QuickDraw.NOT_SRC_XOR: + return new NotSrcXor(); 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; @@ -349,6 +393,15 @@ class QuickDrawContext { } } + /** + * Sets up context for text drawing. + */ + protected void setupForText() { + graphics.setPaint(textPattern); + graphics.setComposite(getCompositeFor(textMode)); + } + + /** * Sets up context for line drawing/painting. */ @@ -415,9 +468,7 @@ class QuickDrawContext { if (isPenVisible()) { // NOTE: Workaround for known Mac JDK bug: Paint, not frame - //graphics.setStroke(getStroke(penSize)); // Make sure we have correct stroke paintShape(graphics.getStroke().createStrokedShape(line)); - } moveTo(pX, pY); @@ -811,13 +862,18 @@ class QuickDrawContext { // TODO: All other operations can delegate to these! :-) private void frameShape(final Shape pShape) { - setupForPaint(); - graphics.draw(pShape); + if (isPenVisible()) { + setupForPaint(); + + Stroke stroke = getStroke(penSize); + Shape shape = stroke.createStrokedShape(pShape); + graphics.draw(shape); + } } private void paintShape(final Shape pShape) { setupForPaint(); - graphics.fill(pShape); + graphics.fill(pShape); // Yes, fill } private void fillShape(final Shape pShape, final Pattern pPattern) { @@ -878,20 +934,22 @@ class QuickDrawContext { pSrcRect.y + pSrcRect.height, null ); + + setClipRegion(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 + throw new UnsupportedOperationException("Method copyMask 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 + throw new UnsupportedOperationException("Method copyDeepMask not implemented"); // TODO: Implement } /* @@ -926,7 +984,8 @@ class QuickDrawContext { * @param pString a Pascal string (a string of length less than or equal to 255 chars). */ public void drawString(String pString) { - graphics.drawString(pString, (float) getPenPosition().getX(), (float) getPenPosition().getY()); + setupForText(); + graphics.drawString(pString, (float) getPenPosition().getX(), (float) getPenPosition().getY()); } /* @@ -1049,4 +1108,41 @@ class QuickDrawContext { } } + + private static class NotSrcXor implements Composite { + // TODO: Src can probably be any color model that can be encoded in PICT, dst is always RGB/TYPE_INT + public CompositeContext createContext(final ColorModel srcColorModel, final ColorModel dstColorModel, RenderingHints hints) { + { + if (!srcColorModel.getColorSpace().isCS_sRGB() || !dstColorModel.getColorSpace().isCS_sRGB()) { + throw new IllegalArgumentException("Only sRGB supported"); + } + } + + return new CompositeContext() { + public void dispose() { + + } + + public void compose(Raster src, Raster dstIn, WritableRaster dstOut) { + // We always work in RGB, using DataBuffer.TYPE_INT transfer type. + int[] srcData = null; + int[] dstData = null; + int[] resData = new int[src.getWidth() - src.getMinX()]; + + for (int y = src.getMinY(); y < src.getHeight(); y++) { + srcData = (int[]) src.getDataElements(src.getMinX(), y, src.getWidth(), 1, srcData); + dstData = (int[]) dstIn.getDataElements(src.getMinX(), y, src.getWidth(), 1, dstData); + + for (int x = src.getMinX(); x < src.getWidth(); x++) { + // TODO: Decide how to handle alpha (if at all) + resData[x] = 0xff000000 | ((~ srcData[x] ^ dstData[x])) & 0xffffff ; +// resData[x] = ~ srcData[x] ^ dstData[x]; + } + + dstOut.setDataElements(src.getMinX(), y, src.getWidth(), 1, resData); + } + } + }; + } + } } diff --git a/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderTest.java b/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderTest.java index d85e6fb3..41efe9f5 100644 --- a/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderTest.java +++ b/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderTest.java @@ -1,15 +1,19 @@ package com.twelvemonkeys.imageio.plugins.pict; +import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream; +import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStreamSpi; import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase; import org.junit.Test; +import javax.imageio.spi.IIORegistry; import javax.imageio.spi.ImageReaderSpi; import java.awt.*; +import java.awt.image.BufferedImage; import java.io.IOException; import java.util.Arrays; import java.util.List; -import static org.junit.Assert.*; +import static org.junit.Assert.assertFalse; /** * ICOImageReaderTestCase @@ -20,6 +24,10 @@ import static org.junit.Assert.*; */ public class PICTImageReaderTest extends ImageReaderAbstractTestCase { + static { + IIORegistry.getDefaultInstance().registerServiceProvider(new ByteArrayImageInputStreamSpi()); + } + static ImageReaderSpi sProvider = new PICTImageReaderSpi(); // TODO: Should also test the clipboard format (without 512 byte header) @@ -32,8 +40,20 @@ public class PICTImageReaderTest extends ImageReaderAbstractTestCase 0); - ImageInputStream input = ImageIO.createImageInputStream(new ByteArrayInputStream(buffer.toByteArray())); + ImageInputStream input = new ByteArrayImageInputStream(buffer.toByteArray()); BufferedImage written = ImageIO.read(input); assertNotNull(written); @@ -113,16 +112,23 @@ public class PICTImageWriterTest extends ImageWriterAbstractTestCase { int originalRGB = original.getRGB(x, y); int writtenRGB = written.getRGB(x, y); + int expectedR = (originalRGB & 0xff0000) >> 16; + int actualR = (writtenRGB & 0xff0000) >> 16; + int expectedG = (originalRGB & 0x00ff00) >> 8; + int actualG = (writtenRGB & 0x00ff00) >> 8; + int expectedB = originalRGB & 0x0000ff; + int actualB = writtenRGB & 0x0000ff; + if (original.getColorModel().getColorSpace().getType() == ColorSpace.TYPE_GRAY) { // NOTE: For some reason, gray data seems to be one step off... - assertEquals("Test data " + i + " R(" + x + "," + y + ")", originalRGB & 0xff0000, writtenRGB & 0xff0000, 0x10000); - assertEquals("Test data " + i + " G(" + x + "," + y + ")", originalRGB & 0x00ff00, writtenRGB & 0x00ff00, 0x100); - assertEquals("Test data " + i + " B(" + x + "," + y + ")", originalRGB & 0x0000ff, writtenRGB & 0x0000ff, 0x1); + // ...and vary with different backing CMSs... :-( + assertTrue(String.format("original 0x%08x != gray! (%d,%d)", originalRGB, x, y), expectedR == expectedG && expectedG == expectedB); + assertTrue(String.format("written 0x%08x != gray! (%d,%d)", writtenRGB, x, y), actualR == actualG && actualG == actualB); } else { - assertEquals("Test data " + i + " R(" + x + "," + y + ")", originalRGB & 0xff0000, writtenRGB & 0xff0000); - assertEquals("Test data " + i + " G(" + x + "," + y + ")", originalRGB & 0x00ff00, writtenRGB & 0x00ff00); - assertEquals("Test data " + i + " B(" + x + "," + y + ")", originalRGB & 0x0000ff, writtenRGB & 0x0000ff); + assertEquals(String.format("Test data %d R(%d,%d)", i, x, y), expectedR, actualR); + assertEquals(String.format("Test data %d G(%d,%d)", i, x, y), expectedG, actualG); + assertEquals(String.format("Test data %d B(%d,%d)", i, x, y), expectedB, actualB); } } } diff --git a/imageio/imageio-pict/src/test/resources/pict/FC10.PCT b/imageio/imageio-pict/src/test/resources/pict/FC10.PCT new file mode 100644 index 00000000..9f80bd9d Binary files /dev/null and b/imageio/imageio-pict/src/test/resources/pict/FC10.PCT differ