Merge pull request #426 from ckovorodkin/camera-raw-cr2-slice

CR2ImageReader: unslice result of JPEGLosslessDecoder
This commit is contained in:
Harald Kuhr 2018-08-21 17:50:57 +02:00 committed by GitHub
commit 7ff74465d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 779 additions and 312 deletions

View File

@ -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);

View File

@ -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) {

View File

@ -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()) {

View File

@ -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;
}
}

View File

@ -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<Slice> 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();
}
}

View File

@ -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();
}

View File

@ -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.

View File

@ -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);
}
}
};
}
}
}

View File

@ -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.
* <p/>
* 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.
* <p/>
* 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);
}
}
};
}
}
}
}