diff --git a/imageio/imageio-cr2/src/main/java/com/twelvemonkeys/imageio/plugins/cr2/CR2ImageReader.java b/imageio/imageio-cr2/src/main/java/com/twelvemonkeys/imageio/plugins/cr2/CR2ImageReader.java index cd0bc6c6..f9d4a9bd 100644 --- a/imageio/imageio-cr2/src/main/java/com/twelvemonkeys/imageio/plugins/cr2/CR2ImageReader.java +++ b/imageio/imageio-cr2/src/main/java/com/twelvemonkeys/imageio/plugins/cr2/CR2ImageReader.java @@ -36,6 +36,8 @@ import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment; import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil; import com.twelvemonkeys.imageio.metadata.tiff.TIFF; import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader; +import com.twelvemonkeys.imageio.plugins.jpeg.Slice; +import com.twelvemonkeys.imageio.plugins.jpeg.SliceContext; import com.twelvemonkeys.imageio.stream.SubImageInputStream; import javax.imageio.IIOException; @@ -432,17 +434,20 @@ public final class CR2ImageReader extends ImageReaderBase { int stripByteCounts = getValueAsInt(TIFF.TAG_STRIP_BYTE_COUNTS, "StripByteCounts"); long[] slices = getValueAsLongArray(50752, "Slices", true); - // Format of this array, is slices[0] = N, slices[1] = slice0.width ... slices[N + 1] = sliceN.width - if (slices[0] != slices.length - 2) { - throw new IIOException("Unexpected slices array: " + Arrays.toString(slices)); + try { + final Slice slice = Slice.createSlice(slices); + SliceContext.set(slice); + + // TODO: Get correct dimensions (sensor size?) + int width = getWidth(0); + int height = getHeight(0); + + imageInput.seek(stripOffsets); + return ImageIO.read(new SubImageInputStream(imageInput, stripByteCounts)); + } finally { + SliceContext.remove(); } - // TODO: We really have multiple slices... - // TODO: Get correct dimensions (sensor size?) - int width = getWidth(0); - int height = getHeight(0); - - imageInput.seek(stripOffsets); // byte[] data = new LosslessJPEGDecoder().decompress(new SubImageInputStream(imageInput, stripByteCounts), null); // // // TODO: We really have 2 bytes/sample @@ -477,7 +482,7 @@ public final class CR2ImageReader extends ImageReaderBase { for (int i = 0; i < numImages; i++) { int numThumbnails = reader.getNumThumbnails(i); for (int n = 0; n < numThumbnails; n++) { - showIt(reader.readThumbnail(i, n), arg + " image thumbnail" + n); + showIt(reader.readThumbnail(i, n), arg + " image " + i + " thumbnail " + n); } showIt(reader.read(i), arg + " image " + i); diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGLosslessDecoder.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGLosslessDecoder.java index 312f9b33..01b40dda 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGLosslessDecoder.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGLosslessDecoder.java @@ -66,10 +66,7 @@ final class JPEGLosslessDecoder { private int xLoc; private int yLoc; private int mask; - private int[] outputData; - private int[] outputRedData; - private int[] outputGreenData; - private int[] outputBlueData; + private int[][] outputData; private static final int IDCT_P[] = { 0, 5, 40, 16, 45, 2, 7, 42, @@ -146,8 +143,6 @@ final class JPEGLosslessDecoder { int[][] decode() throws IOException { int current, scanNum = 0; - final int pred[] = new int[10]; - int[][] outputRef; xLoc = 0; yLoc = 0; @@ -216,32 +211,27 @@ final class JPEGLosslessDecoder { xDim = frame.samplesPerLine; yDim = frame.lines; - outputRef = new int[numComp][]; + outputData = new int[numComp][]; - // TODO: Support 4 components (RGBA/YCCA/CMYK/YCCK), others? - if (numComp == 1) { - outputData = new int[xDim * yDim]; - outputRef[0] = outputData; + for (int componentIndex = 0; componentIndex < numComp; ++componentIndex) { + // not a good use of memory, but I had trouble packing bytes into int. some values exceeded 255. + outputData[componentIndex] = new int[xDim * yDim]; } - else { - outputRedData = new int[xDim * yDim]; // not a good use of memory, but I had trouble packing bytes into int. some values exceeded 255. - outputGreenData = new int[xDim * yDim]; - outputBlueData = new int[xDim * yDim]; - outputRef[0] = outputRedData; - outputRef[1] = outputGreenData; - outputRef[2] = outputBlueData; + final int firstValue[] = new int[numComp]; + for (int i = 0; i < numComp; i++) { + firstValue[i] = (1 << (precision - 1)); } + final int pred[] = new int[numComp]; + scanNum++; while (true) { // Decode one scan int temp[] = new int[1]; // to store remainder bits int index[] = new int[1]; - for (int i = 0; i < 10; i++) { - pred[i] = (1 << (precision - 1)); - } + System.arraycopy(firstValue, 0, pred, 0, numComp); if (restartInterval == 0) { current = decode(pred, temp, index); @@ -283,9 +273,10 @@ final class JPEGLosslessDecoder { readNumber(); current = input.readUnsignedShort(); } + // TODO oe: 05.05.2018 Is it correct loop? Content of outputData from previous iteration is always lost. } while ((current != JPEG.EOI) && ((xLoc < xDim) && (yLoc < yDim)) && (scanNum == 0)); - return outputRef; + return outputData; } private void processWarningOccured(String warning) { @@ -339,7 +330,7 @@ final class JPEGLosslessDecoder { return decodeRGB(prev, temp, index); } else { - return -1; + return decodeAny(prev, temp, index); } } @@ -351,6 +342,7 @@ final class JPEGLosslessDecoder { prev[0] = (1 << (frame.samplePrecision - 1)); } else { + final int[] outputData = this.outputData[0]; switch (selection) { case 2: prev[0] = getPreviousY(outputData); @@ -397,6 +389,9 @@ final class JPEGLosslessDecoder { } private int decodeRGB(final int prev[], final int temp[], final int index[]) throws IOException { + final int[] outputRedData = outputData[0]; + final int[] outputGreenData = outputData[1]; + final int[] outputBlueData = outputData[2]; switch (selection) { case 2: prev[0] = getPreviousY(outputRedData); @@ -435,6 +430,43 @@ final class JPEGLosslessDecoder { break; } + return decode0(prev, temp, index); + } + + private int decodeAny(final int prev[], final int temp[], final int index[]) throws IOException { + for (int componentIndex = 0; componentIndex < outputData.length; ++componentIndex) { + final int[] outputData = this.outputData[componentIndex]; + final int previous; + switch (selection) { + case 2: + previous = getPreviousY(outputData); + break; + case 3: + previous = getPreviousXY(outputData); + break; + case 4: + previous = (getPreviousX(outputData) + getPreviousY(outputData)) - getPreviousXY(outputData); + break; + case 5: + previous = getPreviousX(outputData) + ((getPreviousY(outputData) - getPreviousXY(outputData)) >> 1); + break; + case 6: + previous = getPreviousY(outputData) + ((getPreviousX(outputData) - getPreviousXY(outputData)) >> 1); + break; + case 7: + previous = (int) (((long) getPreviousX(outputData) + getPreviousY(outputData)) / 2); + break; + default: + previous = getPreviousX(outputData); + break; + } + prev[componentIndex] = previous; + } + + return decode0(prev, temp, index); + } + + private int decode0(int[] prev, int[] temp, int[] index) throws IOException { int value, actab[], dctab[]; int qtab[]; @@ -692,14 +724,17 @@ final class JPEGLosslessDecoder { if (numComp == 1) { outputSingle(pred); } - else { + else if (numComp == 3) { outputRGB(pred); } + else { + outputAny(pred); + } } private void outputSingle(final int pred[]) { if ((xLoc < xDim) && (yLoc < yDim)) { - outputData[(yLoc * xDim) + xLoc] = mask & pred[0]; + outputData[0][(yLoc * xDim) + xLoc] = mask & pred[0]; xLoc++; if (xLoc >= xDim) { @@ -711,9 +746,25 @@ final class JPEGLosslessDecoder { private void outputRGB(final int pred[]) { if ((xLoc < xDim) && (yLoc < yDim)) { - outputRedData[(yLoc * xDim) + xLoc] = pred[0]; - outputGreenData[(yLoc * xDim) + xLoc] = pred[1]; - outputBlueData[(yLoc * xDim) + xLoc] = pred[2]; + final int index = (yLoc * xDim) + xLoc; + outputData[0][index] = pred[0]; + outputData[1][index] = pred[1]; + outputData[2][index] = pred[2]; + xLoc++; + + if (xLoc >= xDim) { + yLoc++; + xLoc = 0; + } + } + } + + private void outputAny(final int pred[]) { + if ((xLoc < xDim) && (yLoc < yDim)) { + final int index = (yLoc * xDim) + xLoc; + for (int componentIndex = 0; componentIndex < outputData.length; ++componentIndex) { + outputData[componentIndex][index] = pred[componentIndex]; + } xLoc++; if (xLoc >= xDim) { diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGLosslessDecoderWrapper.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGLosslessDecoderWrapper.java index 8c44f21c..47a6cf75 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGLosslessDecoderWrapper.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGLosslessDecoderWrapper.java @@ -86,6 +86,22 @@ final class JPEGLosslessDecoderWrapper { int width = decoder.getDimX(); int height = decoder.getDimY(); + if (SliceContext.isPresent()) { //QnD + final int[][] unsliced = new int[1][]; + final int componentCount = decoder.getNumComponents(); + final Slice slice = SliceContext.get(); + unsliced[0] = slice.unslice(decoded, componentCount, height); + switch (decoder.getPrecision()) { + case 8: + return to8Bit1ComponentGrayScale(unsliced, width * componentCount, height); + case 10: + case 12: + case 14: + case 16: + return to16Bit1ComponentGrayScale(unsliced, decoder.getPrecision(), width * componentCount, height); + } + } + // Single component, assumed to be Gray if (decoder.getNumComponents() == 1) { switch (decoder.getPrecision()) { diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Slice.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Slice.java new file mode 100644 index 00000000..93092202 --- /dev/null +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Slice.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2018, Oleg Ermolaev + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.jpeg; + +import javax.imageio.IIOException; +import java.util.Arrays; + +/** + * For CR2 RAW image. + * + * @author Oleg Ermolaev Date: 05.05.2018 2:04 + */ +public class Slice { + public static final int FIRST_WIDTH_COUNT_INDEX = 0; + public static final int FIRST_WIDTH_INDEX = 1; + public static final int LAST_WIDTH_INDEX = 2; + + private final int firstWidthCount; + private final int firstWidth; + private final int lastWidth; + + public Slice(int firstWidthCount, int firstWidth, int lastWidth) { + this.firstWidthCount = firstWidthCount; + this.firstWidth = firstWidth; + this.lastWidth = lastWidth; + } + + public static Slice createSlice(long[] values) throws IIOException { + if (values == null || values.length != 3) { + throw new IIOException("Unexpected slices array: " + Arrays.toString(values)); + } + final long firstWidthCount = values[FIRST_WIDTH_COUNT_INDEX]; + final long firstWidth = values[FIRST_WIDTH_INDEX]; + final long lastWidth = values[LAST_WIDTH_INDEX]; + if (!(0 < firstWidthCount && firstWidthCount <= Integer.MAX_VALUE) || + !(0 < firstWidth && firstWidth <= Integer.MAX_VALUE) || + !(0 < lastWidth && lastWidth <= Integer.MAX_VALUE) || + firstWidthCount * firstWidth + lastWidth > Integer.MAX_VALUE) { + throw new IIOException("Unexpected slices array: " + Arrays.toString(values)); + } + return new Slice((int) firstWidthCount, (int) firstWidth, (int) lastWidth); + } + + public int getFirstWidthCount() { + return firstWidthCount; + } + + public int getFirstWidth() { + return firstWidth; + } + + public int getLastWidth() { + return lastWidth; + } + + private int getWidth() { + return firstWidthCount * firstWidth + lastWidth; + } + + public int[] unslice(int[][] data, int componentCount, int height) throws IIOException { + final int width = getWidth(); + final int[] result = new int[width * height]; + + for (int componentIndex = 0; componentIndex < componentCount; componentIndex++) { + if (result.length != data[componentIndex].length * componentCount) { + throw new IIOException(String.format("Invalid array size for component #%d", componentIndex)); + } + } + + int position = 0; + int currentWidth = firstWidth / componentCount; + for (int sliceIndex = 0; sliceIndex < firstWidthCount + 1; ++sliceIndex) { + if (sliceIndex == firstWidthCount) { + currentWidth = lastWidth / componentCount; + } + final int sliceOffset = sliceIndex * firstWidth; + for (int y = 0; y < height; ++y) { + final int yOffset = y * width; + for (int x = 0; x < currentWidth; ++x) { + final int xOffset = x * componentCount; + for (int componentIndex = 0; componentIndex < componentCount; componentIndex++) { + result[sliceOffset + yOffset + xOffset + componentIndex] = data[componentIndex][position]; + } + position++; + } + } + } + + return result; + } +} diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/SliceContext.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/SliceContext.java new file mode 100644 index 00000000..b431335d --- /dev/null +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/SliceContext.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018, Oleg Ermolaev + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.jpeg; + +/** + * QnD + * + * @author Oleg Ermolaev Date: 05.05.2018 2:13 + */ +public class SliceContext { + private static final ThreadLocal CONTEXT = new ThreadLocal<>(); + + public static boolean isPresent() { + return get() != null; + } + + public static Slice get() { + return CONTEXT.get(); + } + + public static void set(Slice slice) { + CONTEXT.set(slice); + } + + public static void remove() { + CONTEXT.remove(); + } +} 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