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 33fc1dae..90af9cff 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 @@ -332,7 +332,7 @@ public final class PICTImageReader extends ImageReaderBase { * * @param pGraphics the graphics object to draw onto. * - * @throws javax.imageio.IIOException if the data can not be read. + * @throws IIOException if the data can not be read. * @throws IOException if an I/O error occurs while reading the image. */ private void drawOnto(Graphics2D pGraphics) throws IOException { @@ -351,8 +351,8 @@ public final class PICTImageReader extends ImageReaderBase { * * @param pStream the stream to read from * - * @throws javax.imageio.IIOException if the data can not be read. - * @throws java.io.IOException if an I/O error occurs while reading the image. + * @throws IIOException if the data can not be read. + * @throws IOException if an I/O error occurs while reading the image. */ private void readPICTopcodes(ImageInputStream pStream) throws IOException { pStream.seek(imageStartStreamPos); @@ -371,7 +371,7 @@ public final class PICTImageReader extends ImageReaderBase { String text; Rectangle bounds = new Rectangle(); Polygon polygon = new Polygon(); - Polygon region = new Polygon(); + Area region = new Area(); int pixmapCount = 0; try { @@ -1312,27 +1312,27 @@ public final class PICTImageReader extends ImageReaderBase { case PICT.OP_INVERT_SAME_RGN:// OK, not tested case PICT.OP_FILL_SAME_RGN:// OK, not tested // Draw - if (region != null && region.npoints > 1) { + if (region != null && !region.getBounds().isEmpty()) { switch (opCode) { case PICT.OP_FRAME_RGN: case PICT.OP_FRAME_SAME_RGN: - context.frameRegion(new Area(region)); + context.frameRegion(region); break; case PICT.OP_PAINT_RGN: case PICT.OP_PAINT_SAME_RGN: - context.paintRegion(new Area(region)); + context.paintRegion(region); break; case PICT.OP_ERASE_RGN: case PICT.OP_ERASE_SAME_RGN: - context.eraseRegion(new Area(region)); + context.eraseRegion(region); break; case PICT.OP_INVERT_RGN: case PICT.OP_INVERT_SAME_RGN: - context.invertRegion(new Area(region)); + context.invertRegion(region); break; case PICT.OP_FILL_RGN: case PICT.OP_FILL_SAME_RGN: - context.fillRegion(new Area(region), fill); + context.fillRegion(region, fill); break; } } @@ -1395,29 +1395,10 @@ public final class PICTImageReader extends ImageReaderBase { // map replaces the bitmap, a color table has been added, and pixData replaces bitData. // [5] For opcodes $0090 (BitsRect) and $0091 (BitsRgn), the data is unpacked. These // opcodes can be used only when rowBytes is less than 8. - /* - pixMap: PixMap; {pixel map} - ColorTable: ColorTable; {ColorTable record} - srcRect: Rect; {source rectangle} - dstRect: Rect; {destination rectangle} - mode: Word; {transfer mode (may include } - { new transfer modes)} - pixData: PixData; - */ readOpBits(pStream, false); break; case PICT.OP_BITS_RGN: - /* - pixMap: PixMap; - colorTable: ColorTable; - srcRect: Rect; {source rectangle} - dstRect: Rect; {destination rectangle} - mode: Word; {transfer mode (may } - { include new modes)} - maskRgn: Rgn; {region for masking} - pixData: PixData; - */ readOpBits(pStream, true); break; @@ -1486,10 +1467,6 @@ public final class PICTImageReader extends ImageReaderBase { case PICT.OP_COMPRESSED_QUICKTIME: // $8200: CompressedQuickTime Data length (Long), data (private to QuickTime) 4 + data length - if (DEBUG) { - System.out.println("compressedQuickTime"); - } - readCompressedQT(pStream); break; @@ -1585,43 +1562,56 @@ public final class PICTImageReader extends ImageReaderBase { int dataLength = pStream.readInt(); long pos = pStream.getStreamPosition(); - if (DEBUG) { - System.out.println("QT data length: " + dataLength); - } - - // TODO: Need to figure out what the skipped data is? - for (int i = 0; i < 13; i++) { - int value = pStream.readInt(); - if (DEBUG) { - System.out.println(String.format("%2d: 0x%08x", i * 4, value)); - } - } - - // Read the destination rectangle - Rectangle destination = new Rectangle(); - readRectangle(pStream, destination); + int version = pStream.readUnsignedShort(); if (DEBUG) { - System.out.println("..."); + System.out.print("compressedQuickTime"); + System.out.println(", size: " + dataLength + ", version: " + version); } - for (int i = 0; i < 2; i++) { - int value = pStream.readInt(); - if (DEBUG) { - System.out.println(String.format("%2d: 0x%08x", (i + 15) * 4, value)); - } + // Matrix + int[] matrix = new int[9]; + for (int i = 0; i < matrix.length; i++) { + matrix[i] = pStream.readInt(); + } + if (DEBUG) { + System.out.println(String.format("matrix: %s", Arrays.toString(matrix))); + } + + // Matte + long matteSize = pStream.readUnsignedInt(); + Rectangle matteRect = new Rectangle(); + readRectangle(pStream, matteRect); + + // Transfer mode + int mode = pStream.readUnsignedShort(); + + // Read the source rectangle + Rectangle srcRect = new Rectangle(); + readRectangle(pStream, srcRect); + + // ...and more + int accuracy = pStream.readInt(); + int maskSize = pStream.readInt(); + + if (DEBUG) { + System.out.print("matteSize: " + matteSize); + System.out.print(", matteRect: " + matteRect); + System.out.print(", mode: " + mode); + System.out.print(", srcRect: " + srcRect); + System.out.print(", accuracy: " + accuracy); + System.out.println(", maskSize: " + maskSize); } BufferedImage image = QuickTime.decompress(pStream); if (image != null) { - context.copyBits(image, new Rectangle(image.getWidth(), image.getHeight()), destination, QuickDraw.SRC_COPY, null); + context.copyBits(image, srcRect, srcRect, mode, null); pStream.seek(pos + dataLength); // Might be word-align mismatch here // 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... + // NOTE: This algorithm is reverse-engineered by looking at various test inputs and not from any spec... int penSizeMagic = pStream.readInt(); if (penSizeMagic == 0x000700ae) { // OP_PN_SIZE + bogus x value..? int skip = pStream.readUnsignedShort(); // bogus y value is the number of bytes to skip @@ -1803,10 +1793,15 @@ public final class PICTImageReader extends ImageReaderBase { } Rectangle regionBounds = new Rectangle(); - Polygon region = hasRegion ? readRegion(pStream, regionBounds) : null; + Area region = hasRegion ? readRegion(pStream, regionBounds) : null; if (DEBUG) { - System.out.println(hasRegion ? ", region: " + region : "" ); + if (hasRegion) { + verboseRegionCmd(", region", regionBounds, region); + } + else { + System.out.println(); + } } // Set up pixel buffer for the RGB values @@ -1882,8 +1877,7 @@ public final class PICTImageReader extends ImageReaderBase { BufferedImage img = images.get(pPixmapCount); if (img != null) { srcRect.setLocation(0, 0); // Raster always start at 0,0 -// dstRect.translate(-frame.x, -frame.y); - context.copyBits(img, srcRect, dstRect, transferMode, null); + context.copyBits(img, srcRect, dstRect, transferMode, region); } // Line break at the end @@ -1899,8 +1893,8 @@ public final class PICTImageReader extends ImageReaderBase { * @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 java.io.IOException if an I/O error occurs while reading the image. + * @throws IIOException if the data can not be read. + * @throws IOException if an I/O error occurs while reading the image. */ private void readOpDirectBits(final ImageInputStream pStream, final boolean hasRegion, final int pPixmapCount) throws IOException { // Skip PixMap pointer (always 0x000000FF); @@ -2039,7 +2033,7 @@ public final class PICTImageReader extends ImageReaderBase { System.out.print(", mode: " + transferMode); } - Polygon region = hasRegion ? readRegion(pStream, new Rectangle()) : null; + Area region = hasRegion ? readRegion(pStream, new Rectangle()) : null; if (DEBUG) { System.out.println(hasRegion ? ", region: " + region : ""); @@ -2196,8 +2190,7 @@ public final class PICTImageReader extends ImageReaderBase { BufferedImage img = images.get(pPixmapCount); if (img != null) { srcRect.setLocation(0, 0); // Raster always starts at 0,0 -// dstRect.translate(-frame.x, -frame.y); - context.copyBits(img, srcRect, dstRect, transferMode, null); + context.copyBits(img, srcRect, dstRect, transferMode, region); } // Line break at the end @@ -2206,6 +2199,16 @@ public final class PICTImageReader extends ImageReaderBase { } } + /* + pixMap: PixMap; + colorTable: ColorTable; + srcRect: Rect; {source rectangle} + dstRect: Rect; {destination rectangle} + mode: Word; {transfer mode (may } + { include new modes)} + maskRgn: Rgn; {region for masking} + pixData: PixData; + */ private void readOpBits(ImageInputStream pStream, boolean hasRegion) throws IOException { // Get rowBytes int rowBytesRaw = pStream.readUnsignedShort(); @@ -2323,7 +2326,7 @@ public final class PICTImageReader extends ImageReaderBase { // Get transfer mode int mode = pStream.readUnsignedShort(); - Polygon region = hasRegion ? readRegion(pStream, new Rectangle()) : null; + Area region = hasRegion ? readRegion(pStream, new Rectangle()) : null; if (DEBUG) { System.out.print(", bounds: " + bounds); @@ -2347,8 +2350,7 @@ public final class PICTImageReader extends ImageReaderBase { // Draw pixel data srcRect.setLocation(0, 0); // Raster always start at 0,0 -// dstRect.translate(-frame.x, -frame.y); - context.copyBits(image, srcRect, dstRect, mode, null); + context.copyBits(image, srcRect, dstRect, mode, region); } /** @@ -2368,84 +2370,90 @@ public final class PICTImageReader extends ImageReaderBase { pDestRect.setLocation(getXPtCoord(x), getYPtCoord(y)); pDestRect.setSize(getXPtCoord(w - x), getYPtCoord(h - y)); - } /** * Read in a region. The input stream should be positioned at the first byte - * of the region. {@code pBoundsRect} is a rectangle that will be set to the + * of the region. {@code pBounds} is a rectangle that will be set to the * region bounds. * The point array may therefore be empty if the region is just a rectangle. * * @param pStream the stream to read from - * @param pBoundsRect the bounds rectangle to read into + * @param pBounds the bounds rectangle to read into * - * @return the polygon containing the region, or an empty polygon if the + * @return the area containing the region, or an empty polygon if the * region is a rectangle. * * @throws IOException if an I/O error occurs while reading the image. */ - private Polygon readRegion(DataInput pStream, Rectangle pBoundsRect) throws IOException { - // Get minimal region - + private Area readRegion(DataInput pStream, Rectangle pBounds) throws IOException { // Get region data size int size = pStream.readUnsignedShort(); // Get region bounds - int y = getYPtCoord(pStream.readShort()); - int x = getXPtCoord(pStream.readShort()); - pBoundsRect.setLocation(x, y); + readRectangle(pStream, pBounds); - y = getYPtCoord(pStream.readShort()) - pBoundsRect.getLocation().y; - x = getXPtCoord(pStream.readShort()) - pBoundsRect.getLocation().x; - pBoundsRect.setSize(x, y); - - Polygon polygon = new Polygon(); int count = (size - 10) / 2; - boolean nextIsV = true; - for (int i = 0; i < count; i++) { - short point = pStream.readShort(); - - if (nextIsV) { - if (point == 0x7fff) { - // Done - break; - } - - y = point; - nextIsV = false; - } - else { - if (point == 0x7fff) { - nextIsV = true; - continue; - } - - x = point; - polygon.addPoint(x, y); - } + if (count == 0) { + // Minimal region, just the bounds + return new Area(pBounds); } + else { + // Normal region, parse inversion points and build area + Area area = new Area(); - return polygon; + boolean nextIsVertical = true; + + int x, y = 0; + for (int i = 0; i < count; i++) { + short point = pStream.readShort(); + + if (nextIsVertical) { + if (point == 0x7fff) { + // Done + break; + } + + y = point; + nextIsVertical = false; + } + else { + if (point == 0x7fff) { + nextIsVertical = true; + continue; + } + + x = point; + area.exclusiveOr(new Area(new Rectangle(x, y, pBounds.x + pBounds.width - x, pBounds.y + pBounds.height - y))); + } + } + + if (!pBounds.contains(area.getBounds())) { + processWarningOccurred("Bad region, contains point(s) out of bounds " + pBounds + ": " + area); + } + + return area; + } } - /* + /** * Read in a polygon. The input stream should be positioned at the first byte * of the polygon. + * + * @param pStream the stream to read from + * @param pBounds the bounds rectangle to read into + * + * @return the polygon + * + * @throws IOException if an I/O error occurs while reading the image. */ - private Polygon readPoly(DataInput pStream, Rectangle pBoundsRect) throws IOException { + private Polygon readPoly(DataInput pStream, Rectangle pBounds) throws IOException { // Get polygon data size int size = pStream.readUnsignedShort(); // Get poly bounds - int y = getYPtCoord(pStream.readShort()); - int x = getXPtCoord(pStream.readShort()); - pBoundsRect.setLocation(x, y); - - y = getYPtCoord(pStream.readShort()) - pBoundsRect.getLocation().y; - x = getXPtCoord(pStream.readShort()) - pBoundsRect.getLocation().x; - pBoundsRect.setSize(x, y); + readRectangle(pStream, pBounds); // Initialize the point array to the right size int points = (size - 10) / 4; @@ -2454,11 +2462,15 @@ public final class PICTImageReader extends ImageReaderBase { Polygon polygon = new Polygon(); for (int i = 0; i < points; i++) { - y = getYPtCoord(pStream.readShort()); - x = getXPtCoord(pStream.readShort()); + int y = getYPtCoord(pStream.readShort()); + int x = getXPtCoord(pStream.readShort()); polygon.addPoint(x, y); } + if (!pBounds.contains(polygon.getBounds())) { + processWarningOccurred("Bad poly, contains point(s) out of bounds " + pBounds + ": " + polygon); + } + return polygon; } @@ -2540,15 +2552,15 @@ public final class PICTImageReader extends ImageReaderBase { /* * Write out region command, bounds and points. */ - private void verboseRegionCmd(String pCmd, Rectangle pBounds, Polygon pPolygon) { + private void verboseRegionCmd(String pCmd, Rectangle pBounds, Area pRegion) { System.out.println(pCmd + ": " + pBounds); - System.out.print("Region points: "); - if (pPolygon != null && pPolygon.npoints > 0) { - System.out.print("(" + pPolygon.xpoints[0] + "," + pPolygon.ypoints[0] + ")"); - } - for (int i = 1; pPolygon != null && i < pPolygon.npoints; i++) { - System.out.print(", (" + pPolygon.xpoints[i] + "," + pPolygon.ypoints[i] + ")"); - } +// System.out.print("Region points: "); +// if (pPolygon != null && pPolygon.npoints > 0) { +// System.out.print("(" + pPolygon.xpoints[0] + "," + pPolygon.ypoints[0] + ")"); +// } +// for (int i = 1; pPolygon != null && i < pPolygon.npoints; i++) { +// System.out.print(", (" + pPolygon.xpoints[i] + "," + pPolygon.ypoints[i] + ")"); +// } System.out.println(); } diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QuickDraw.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QuickDraw.java old mode 100755 new mode 100644 index 757a5328..63c7b151 --- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QuickDraw.java +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QuickDraw.java @@ -28,8 +28,9 @@ package com.twelvemonkeys.imageio.plugins.pict; -import java.awt.image.*; import java.awt.*; +import java.awt.image.DataBuffer; +import java.awt.image.IndexColorModel; /** * QuickDraw constants. @@ -61,6 +62,18 @@ interface QuickDraw { // 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! + /* + Source mode Action on destination pixel + If source pixel is black If source pixel is white If source pixel is any other color + srcCopy Apply foreground color Apply background color Apply weighted portions of foreground and background colors + notSrcCopy Apply background color Apply foreground color Apply weighted portions of background and foreground colors + srcOr Apply foreground color Leave alone Apply weighted portions of foreground color + notSrcOr Leave alone Apply foreground color Apply weighted portions of foreground color + srcXor Invert (undefined for colored destination pixel) Leave alone Leave alone + notSrcXor Leave alone Invert (undefined for colored destination pixel) Leave alone + srcBic Apply background color Leave alone Apply weighted portions of background color + notSrcBic Leave alone Apply background color Apply weighted portions of background color + */ int SRC_COPY = 0; int SRC_OR = 1; int SRC_XOR = 2; @@ -84,16 +97,73 @@ interface QuickDraw { // Arithmetic Transfer Modes // http://developer.apple.com/documentation/mac/quickdraw/QuickDraw-199.html#HEADING199-112 + /* + CONST + blend = 32; {replace destination pixel with a blend } + { of the source and destination pixel } + { colors; if the destination is a bitmap or } + { 1-bit pixel map, revert to srcCopy mode} + addPin = 33; {replace destination pixel with the sum of } + { the source and destination pixel colors-- } + { up to a maximum allowable value; if } + { the destination is a bitmap or } + { 1-bit pixel map, revert to srcBic mode} + addOver = 34; {replace destination pixel with the sum of } + { the source and destination pixel colors-- } + { but if the value of the red, green, or } + { blue component exceeds 65,536, then } + { subtract 65,536 from that value; if the } + { destination is a bitmap or 1-bit } + { pixel map, revert to srcXor mode} + subPin = 35; {replace destination pixel with the } + { difference of the source and destination } + { pixel colors--but not less than a minimum } + { allowable value; if the destination } + { is a bitmap or 1-bit pixel map, revert to } + { srcOr mode} + transparent = 36; {replace the destination pixel with the } + { source pixel if the source pixel isn't } + { equal to the background color} + addMax = 37; {compare the source and destination pixels, } + { and replace the destination pixel with } + { the color containing the greater } + { saturation of each of the RGB components; } + { if the destination is a bitmap or } + { 1-bit pixel map, revert to srcBic mode} + subOver = 38; {replace destination pixel with the } + { difference of the source and destination } + { pixel colors--but if the value of a red, } + { green, or blue component is } + { less than 0, add the negative result to } + { 65,536; if the destination is a bitmap or } + { 1-bit pixel map, revert to srcXor mode} + adMin = 39; {compare the source and destination pixels, } + { and replace the destination pixel with } + { the color containing the lesser } + { saturation of each of the RGB components; } + { if the destination is a bitmap or } + { 1-bit pixel map, revert to srcOr mode} + */ 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 ADD_MAX = 37; int SUB_OVER = 38; - int AD_MIN = 39; + int ADD_MIN = 39; + + // Transfer mode for text only + /* + If the destination device is color and grayishTextOr is the transfer mode, + QuickDraw draws with a blend of the foreground and background colors. If + the destination device is black and white, the grayishTextOr mode dithers + black and white. + + Note that grayishTextOr is not considered a standard transfer mode because + currently it is not stored in pictures, and printing with it is undefined. + */ int GRAYISH_TEXT_OR = 49; -// int MASK = 64; // ?! From Käry's code.. /* * Text face masks. diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QuickDrawComposite.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QuickDrawComposite.java new file mode 100644 index 00000000..dda83e90 --- /dev/null +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QuickDrawComposite.java @@ -0,0 +1,156 @@ +package com.twelvemonkeys.imageio.plugins.pict; + +import java.awt.*; +import java.awt.image.ColorModel; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; + +import static java.lang.Math.max; +import static java.lang.Math.min; + +/** + * QuickDrawComposite + */ +interface QuickDrawComposite extends Composite { + + QuickDrawComposite NotSrcXor = new NotSrcXor(); + QuickDrawComposite AddMax = new AddMax(); + QuickDrawComposite AddMin = new AddMin(); + + class NotSrcXor implements QuickDrawComposite { + + // 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) { + int width = min(src.getWidth(), dstIn.getWidth()); + + // We always work in RGB, using DataBuffer.TYPE_INT transfer type. + int[] srcData = null; + int[] dstData = null; + int[] resData = new int[width - src.getMinX()]; + + for (int y = src.getMinY(); y < src.getHeight(); y++) { + srcData = (int[]) src.getDataElements(src.getMinX(), y, width, 1, srcData); + dstData = (int[]) dstIn.getDataElements(src.getMinX(), y, width, 1, dstData); + + for (int x = src.getMinX(); x < width; x++) { + // TODO: Decide how to handle alpha (if at all) + resData[x] = 0xff000000 | ((~srcData[x] ^ dstData[x])) & 0xffffff; + } + + dstOut.setDataElements(src.getMinX(), y, width, 1, resData); + } + } + }; + } + } + + class AddMax implements QuickDrawComposite { + // 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) { + int width = min(src.getWidth(), dstIn.getWidth()); + + // We always work in RGB, using DataBuffer.TYPE_INT transfer type. + int[] srcData = null; + int[] dstData = null; + + int[] resData = new int[width - src.getMinX()]; + + for (int y = src.getMinY(); y < src.getHeight(); y++) { + srcData = (int[]) src.getDataElements(src.getMinX(), y, width, 1, srcData); + dstData = (int[]) dstIn.getDataElements(src.getMinX(), y, width, 1, dstData); + + for (int x = src.getMinX(); x < width; x++) { + int sAlpha = (srcData[x] >>> 24) & 0xFF; + int sRed = sAlpha * ((srcData[x] >> 16) & 0xFF) / 0xFF; + int sGreen = sAlpha * ((srcData[x] >> 8) & 0xFF) / 0xFF; + int sBlue = sAlpha * ((srcData[x]) & 0xFF) / 0xFF; + + int dAlpha = (dstData[x] >>> 24) & 0xFF; + int dRed = dAlpha * ((dstData[x] >> 16) & 0xFF) / 0xFF; + int dGreen = dAlpha * ((dstData[x] >> 8) & 0xFF) / 0xFF; + int dBlue = dAlpha * ((dstData[x]) & 0xFF) / 0xFF; + + resData[x] = (max(sAlpha, dAlpha) << 24) + | (max(sRed, dRed) << 16) + | (max(sGreen, dGreen) << 8) + | (max(sBlue, dBlue)); + } + + dstOut.setDataElements(src.getMinX(), y, width, 1, resData); + } + } + }; + } + } + + class AddMin implements QuickDrawComposite { + // 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) { + int width = min(src.getWidth(), dstIn.getWidth()); + + // We always work in RGB, using DataBuffer.TYPE_INT transfer type. + int[] srcData = null; + int[] dstData = null; + int[] resData = new int[width - src.getMinX()]; + + for (int y = src.getMinY(); y < src.getHeight(); y++) { + srcData = (int[]) src.getDataElements(src.getMinX(), y, width, 1, srcData); + dstData = (int[]) dstIn.getDataElements(src.getMinX(), y, width, 1, dstData); + + for (int x = src.getMinX(); x < width; x++) { + int sAlpha = (srcData[x] >>> 24) & 0xFF; + int sRed = sAlpha * ((srcData[x] >> 16) & 0xFF) / 0xFF; + int sGreen = sAlpha * ((srcData[x] >> 8) & 0xFF) / 0xFF; + int sBlue = sAlpha * ((srcData[x]) & 0xFF) / 0xFF; + + int dAlpha = (dstData[x] >>> 24) & 0xFF; + int dRed = dAlpha * ((dstData[x] >> 16) & 0xFF) / 0xFF; + int dGreen = dAlpha * ((dstData[x] >> 8) & 0xFF) / 0xFF; + int dBlue = dAlpha * ((dstData[x]) & 0xFF) / 0xFF; + + resData[x] = (min(sAlpha, dAlpha) << 24) + | (min(sRed, dRed) << 16) + | (min(sGreen, dGreen) << 8) + | (min(sBlue, dBlue)); + } + + dstOut.setDataElements(src.getMinX(), y, width, 1, resData); + } + } + }; + } + } +} 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 old mode 100755 new mode 100644 index fd5a0567..b9b80dd0 --- 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 @@ -33,9 +33,8 @@ import com.twelvemonkeys.lang.Validate; import java.awt.*; import java.awt.geom.*; import java.awt.image.BufferedImage; -import java.awt.image.ColorModel; -import java.awt.image.Raster; -import java.awt.image.WritableRaster; + +import static java.lang.Math.sqrt; /** * Emulates an Apple QuickDraw rendering context, backed by a Java {@link Graphics2D}. @@ -170,7 +169,7 @@ class QuickDrawContext { // Sets the text's font style (0..255) void setTextFace(final int face) { int style = 0; - if ((face & QuickDraw.TX_BOLD_MASK) > 0) { + if ((face & QuickDraw.TX_BOLD_MASK) > 0) { style |= Font.BOLD; } if ((face & QuickDraw.TX_ITALIC_MASK) > 0) { @@ -308,9 +307,9 @@ class QuickDrawContext { case QuickDraw.ADD_OVER: case QuickDraw.SUB_PIN: case QuickDraw.TRANSPARENT: - case QuickDraw.AD_MAX: + case QuickDraw.ADD_MAX: case QuickDraw.SUB_OVER: - case QuickDraw.AD_MIN: + case QuickDraw.ADD_MIN: case QuickDraw.GRAYISH_TEXT_OR: penMode = pPenMode; break; @@ -365,12 +364,11 @@ class QuickDrawContext { case QuickDraw.SRC_BIC: return AlphaComposite.Clear; case QuickDraw.NOT_SRC_XOR: - return new NotSrcXor(); + return QuickDrawComposite.NotSrcXor; case QuickDraw.NOT_SRC_COPY: case QuickDraw.NOT_SRC_OR: 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 @@ -385,8 +383,22 @@ class QuickDrawContext { case QuickDraw.NOT_PAT_XOR: case QuickDraw.NOT_PAT_BIC: throw new UnsupportedOperationException("Not implemented for mode " + pMode); -// return null; - // TODO: Aritmetic transfer modes + // Aritmetic transfer modes + case QuickDraw.BLEND: + return AlphaComposite.SrcOver.derive(.5f); + case QuickDraw.ADD_PIN: + case QuickDraw.ADD_OVER: + case QuickDraw.SUB_PIN: + case QuickDraw.TRANSPARENT: + throw new UnsupportedOperationException("Not implemented for mode " + pMode); + case QuickDraw.ADD_MAX: + return QuickDrawComposite.AddMax; + case QuickDraw.SUB_OVER: + throw new UnsupportedOperationException("Not implemented for mode " + pMode); + case QuickDraw.ADD_MIN: + return QuickDrawComposite.AddMin; + case QuickDraw.GRAYISH_TEXT_OR: + throw new UnsupportedOperationException("Not implemented for mode " + pMode); default: throw new IllegalArgumentException("Unknown pnMode: " + pMode); @@ -401,7 +413,6 @@ class QuickDrawContext { graphics.setComposite(getCompositeFor(textMode)); } - /** * Sets up context for line drawing/painting. */ @@ -575,8 +586,8 @@ class QuickDrawContext { * 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. + * @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)); @@ -587,8 +598,8 @@ class QuickDrawContext { * 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. + * @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)); @@ -694,75 +705,75 @@ class QuickDrawContext { /** * Converts a rectangle to an arc. * - * @param pRectangle the framing rectangle + * @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 + * @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); - } + 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)); - } + * 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)); - } + * 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); - } + * 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)); - } + * 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)); - } + * 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 @@ -776,7 +787,7 @@ class QuickDrawContext { // Drawing Polygons: // TODO: What is the Xxx2D equivalent of Polygon!? GeneralPath? - // FramePoly + // 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... @@ -905,18 +916,19 @@ class QuickDrawContext { // 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) { + + /** + * 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) { graphics.setComposite(getCompositeFor(pMode)); if (pMaskRgn != null) { setClipRegion(pMaskRgn); @@ -924,7 +936,7 @@ class QuickDrawContext { graphics.drawImage( pSrcBitmap, - pDstRect.x, + pDstRect.x, pDstRect.y, pDstRect.x + pDstRect.width, pDstRect.y + pDstRect.height, @@ -935,22 +947,34 @@ class QuickDrawContext { null ); - setClipRegion(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 copyMask not implemented"); // TODO: Implement + public void copyMask(BufferedImage pSrcBitmap, + BufferedImage pMaskBitmap, + Rectangle pSrcRect, + Rectangle pMaskRect, + Rectangle pDstRect, + int pSrcCopy, + Shape pMaskRgn) { + 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) { + + /** + * 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 copyDeepMask not implemented"); // TODO: Implement - } + } /* // Drawing With the Eight-Color System: @@ -978,15 +1002,15 @@ class QuickDrawContext { 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) { - setupForText(); - graphics.drawString(pString, (float) getPenPosition().getX(), (float) getPenPosition().getY()); - } + /** + * 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) { + setupForText(); + graphics.drawString(pString, (float) getPenPosition().getX(), (float) getPenPosition().getY()); + } /* DrawText // draws the glyphs of a sequence of characters. @@ -1079,7 +1103,7 @@ class QuickDrawContext { thisY = points[1]; float dx = thisX - lastX; float dy = thisY - lastY; - float distance = (float) Math.sqrt(dx * dx + dy * dy); + float distance = (float) sqrt(dx * dx + dy * dy); if (distance >= next) { float r = 1.0f / distance; //float angle = (float) Math.atan2(dy, dx); @@ -1106,43 +1130,5 @@ class QuickDrawContext { return result; } - } - - 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); - } - } - }; - } - } -} +} \ No newline at end of file