#423: Finally fixed region parsing to Area (seems to work, but slow)!

Bonus: Implemented more transfer modes, better QT parsing/debug output.
This commit is contained in:
Harald Kuhr 2018-05-04 20:53:21 +02:00
parent 5c8b4e0edf
commit ce7fb1cb94
4 changed files with 498 additions and 274 deletions

View File

@ -332,7 +332,7 @@ public final class PICTImageReader extends ImageReaderBase {
* *
* @param pGraphics the graphics object to draw onto. * @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. * @throws IOException if an I/O error occurs while reading the image.
*/ */
private void drawOnto(Graphics2D pGraphics) throws IOException { private void drawOnto(Graphics2D pGraphics) throws IOException {
@ -351,8 +351,8 @@ public final class PICTImageReader extends ImageReaderBase {
* *
* @param pStream the stream to read from * @param pStream the stream to read from
* *
* @throws javax.imageio.IIOException if the data can not be read. * @throws IIOException if the data can not be read.
* @throws java.io.IOException if an I/O error occurs while reading the image. * @throws IOException if an I/O error occurs while reading the image.
*/ */
private void readPICTopcodes(ImageInputStream pStream) throws IOException { private void readPICTopcodes(ImageInputStream pStream) throws IOException {
pStream.seek(imageStartStreamPos); pStream.seek(imageStartStreamPos);
@ -371,7 +371,7 @@ public final class PICTImageReader extends ImageReaderBase {
String text; String text;
Rectangle bounds = new Rectangle(); Rectangle bounds = new Rectangle();
Polygon polygon = new Polygon(); Polygon polygon = new Polygon();
Polygon region = new Polygon(); Area region = new Area();
int pixmapCount = 0; int pixmapCount = 0;
try { try {
@ -1312,27 +1312,27 @@ public final class PICTImageReader extends ImageReaderBase {
case PICT.OP_INVERT_SAME_RGN:// OK, not tested case PICT.OP_INVERT_SAME_RGN:// OK, not tested
case PICT.OP_FILL_SAME_RGN:// OK, not tested case PICT.OP_FILL_SAME_RGN:// OK, not tested
// Draw // Draw
if (region != null && region.npoints > 1) { if (region != null && !region.getBounds().isEmpty()) {
switch (opCode) { switch (opCode) {
case PICT.OP_FRAME_RGN: case PICT.OP_FRAME_RGN:
case PICT.OP_FRAME_SAME_RGN: case PICT.OP_FRAME_SAME_RGN:
context.frameRegion(new Area(region)); context.frameRegion(region);
break; break;
case PICT.OP_PAINT_RGN: case PICT.OP_PAINT_RGN:
case PICT.OP_PAINT_SAME_RGN: case PICT.OP_PAINT_SAME_RGN:
context.paintRegion(new Area(region)); context.paintRegion(region);
break; break;
case PICT.OP_ERASE_RGN: case PICT.OP_ERASE_RGN:
case PICT.OP_ERASE_SAME_RGN: case PICT.OP_ERASE_SAME_RGN:
context.eraseRegion(new Area(region)); context.eraseRegion(region);
break; break;
case PICT.OP_INVERT_RGN: case PICT.OP_INVERT_RGN:
case PICT.OP_INVERT_SAME_RGN: case PICT.OP_INVERT_SAME_RGN:
context.invertRegion(new Area(region)); context.invertRegion(region);
break; break;
case PICT.OP_FILL_RGN: case PICT.OP_FILL_RGN:
case PICT.OP_FILL_SAME_RGN: case PICT.OP_FILL_SAME_RGN:
context.fillRegion(new Area(region), fill); context.fillRegion(region, fill);
break; 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. // 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 // [5] For opcodes $0090 (BitsRect) and $0091 (BitsRgn), the data is unpacked. These
// opcodes can be used only when rowBytes is less than 8. // 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); readOpBits(pStream, false);
break; break;
case PICT.OP_BITS_RGN: 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); readOpBits(pStream, true);
break; break;
@ -1486,10 +1467,6 @@ public final class PICTImageReader extends ImageReaderBase {
case PICT.OP_COMPRESSED_QUICKTIME: case PICT.OP_COMPRESSED_QUICKTIME:
// $8200: CompressedQuickTime Data length (Long), data (private to QuickTime) 4 + data length // $8200: CompressedQuickTime Data length (Long), data (private to QuickTime) 4 + data length
if (DEBUG) {
System.out.println("compressedQuickTime");
}
readCompressedQT(pStream); readCompressedQT(pStream);
break; break;
@ -1585,43 +1562,56 @@ public final class PICTImageReader extends ImageReaderBase {
int dataLength = pStream.readInt(); int dataLength = pStream.readInt();
long pos = pStream.getStreamPosition(); long pos = pStream.getStreamPosition();
if (DEBUG) { int version = pStream.readUnsignedShort();
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);
if (DEBUG) { if (DEBUG) {
System.out.println("..."); System.out.print("compressedQuickTime");
System.out.println(", size: " + dataLength + ", version: " + version);
} }
for (int i = 0; i < 2; i++) { // Matrix
int value = pStream.readInt(); int[] matrix = new int[9];
if (DEBUG) { for (int i = 0; i < matrix.length; i++) {
System.out.println(String.format("%2d: 0x%08x", (i + 15) * 4, value)); 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); BufferedImage image = QuickTime.decompress(pStream);
if (image != null) { 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 pStream.seek(pos + dataLength); // Might be word-align mismatch here
// Skip "QuickTime™ and a ... decompressor required" text // Skip "QuickTime™ and a ... decompressor required" text
// TODO: Verify that this is correct. It works with all my test data, but the algorithm is // NOTE: This algorithm is reverse-engineered by looking at various test inputs and not from any spec...
// reverse-engineered by looking at the input data and not from any spec I've seen...
int penSizeMagic = pStream.readInt(); int penSizeMagic = pStream.readInt();
if (penSizeMagic == 0x000700ae) { // OP_PN_SIZE + bogus x value..? if (penSizeMagic == 0x000700ae) { // OP_PN_SIZE + bogus x value..?
int skip = pStream.readUnsignedShort(); // bogus y value is the number of bytes to skip 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(); Rectangle regionBounds = new Rectangle();
Polygon region = hasRegion ? readRegion(pStream, regionBounds) : null; Area region = hasRegion ? readRegion(pStream, regionBounds) : null;
if (DEBUG) { 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 // Set up pixel buffer for the RGB values
@ -1882,8 +1877,7 @@ public final class PICTImageReader extends ImageReaderBase {
BufferedImage img = images.get(pPixmapCount); BufferedImage img = images.get(pPixmapCount);
if (img != null) { if (img != null) {
srcRect.setLocation(0, 0); // Raster always start at 0,0 srcRect.setLocation(0, 0); // Raster always start at 0,0
// dstRect.translate(-frame.x, -frame.y); context.copyBits(img, srcRect, dstRect, transferMode, region);
context.copyBits(img, srcRect, dstRect, transferMode, null);
} }
// Line break at the end // 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 * @param pPixmapCount the index of the bitmap in the PICT file, used for
* cahcing. * cahcing.
* *
* @throws javax.imageio.IIOException if the data can not be read. * @throws IIOException if the data can not be read.
* @throws java.io.IOException if an I/O error occurs while reading the image. * @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 { private void readOpDirectBits(final ImageInputStream pStream, final boolean hasRegion, final int pPixmapCount) throws IOException {
// Skip PixMap pointer (always 0x000000FF); // Skip PixMap pointer (always 0x000000FF);
@ -2039,7 +2033,7 @@ public final class PICTImageReader extends ImageReaderBase {
System.out.print(", mode: " + transferMode); System.out.print(", mode: " + transferMode);
} }
Polygon region = hasRegion ? readRegion(pStream, new Rectangle()) : null; Area region = hasRegion ? readRegion(pStream, new Rectangle()) : null;
if (DEBUG) { if (DEBUG) {
System.out.println(hasRegion ? ", region: " + region : ""); System.out.println(hasRegion ? ", region: " + region : "");
@ -2196,8 +2190,7 @@ public final class PICTImageReader extends ImageReaderBase {
BufferedImage img = images.get(pPixmapCount); BufferedImage img = images.get(pPixmapCount);
if (img != null) { if (img != null) {
srcRect.setLocation(0, 0); // Raster always starts at 0,0 srcRect.setLocation(0, 0); // Raster always starts at 0,0
// dstRect.translate(-frame.x, -frame.y); context.copyBits(img, srcRect, dstRect, transferMode, region);
context.copyBits(img, srcRect, dstRect, transferMode, null);
} }
// Line break at the end // 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 { private void readOpBits(ImageInputStream pStream, boolean hasRegion) throws IOException {
// Get rowBytes // Get rowBytes
int rowBytesRaw = pStream.readUnsignedShort(); int rowBytesRaw = pStream.readUnsignedShort();
@ -2323,7 +2326,7 @@ public final class PICTImageReader extends ImageReaderBase {
// Get transfer mode // Get transfer mode
int mode = pStream.readUnsignedShort(); int mode = pStream.readUnsignedShort();
Polygon region = hasRegion ? readRegion(pStream, new Rectangle()) : null; Area region = hasRegion ? readRegion(pStream, new Rectangle()) : null;
if (DEBUG) { if (DEBUG) {
System.out.print(", bounds: " + bounds); System.out.print(", bounds: " + bounds);
@ -2347,8 +2350,7 @@ public final class PICTImageReader extends ImageReaderBase {
// Draw pixel data // Draw pixel data
srcRect.setLocation(0, 0); // Raster always start at 0,0 srcRect.setLocation(0, 0); // Raster always start at 0,0
// dstRect.translate(-frame.x, -frame.y); context.copyBits(image, srcRect, dstRect, mode, region);
context.copyBits(image, srcRect, dstRect, mode, null);
} }
/** /**
@ -2368,84 +2370,90 @@ public final class PICTImageReader extends ImageReaderBase {
pDestRect.setLocation(getXPtCoord(x), getYPtCoord(y)); pDestRect.setLocation(getXPtCoord(x), getYPtCoord(y));
pDestRect.setSize(getXPtCoord(w - x), getYPtCoord(h - y)); pDestRect.setSize(getXPtCoord(w - x), getYPtCoord(h - y));
} }
/** /**
* Read in a region. The input stream should be positioned at the first byte * 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. * region bounds.
* The point array may therefore be empty if the region is just a rectangle. * The point array may therefore be empty if the region is just a rectangle.
* *
* @param pStream the stream to read from * @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. * region is a rectangle.
* *
* @throws IOException if an I/O error occurs while reading the image. * @throws IOException if an I/O error occurs while reading the image.
*/ */
private Polygon readRegion(DataInput pStream, Rectangle pBoundsRect) throws IOException { private Area readRegion(DataInput pStream, Rectangle pBounds) throws IOException {
// Get minimal region
// Get region data size // Get region data size
int size = pStream.readUnsignedShort(); int size = pStream.readUnsignedShort();
// Get region bounds // Get region bounds
int y = getYPtCoord(pStream.readShort()); readRectangle(pStream, pBounds);
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);
Polygon polygon = new Polygon();
int count = (size - 10) / 2; int count = (size - 10) / 2;
boolean nextIsV = true;
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();
boolean nextIsVertical = true;
int x, y = 0;
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
short point = pStream.readShort(); short point = pStream.readShort();
if (nextIsV) { if (nextIsVertical) {
if (point == 0x7fff) { if (point == 0x7fff) {
// Done // Done
break; break;
} }
y = point; y = point;
nextIsV = false; nextIsVertical = false;
} }
else { else {
if (point == 0x7fff) { if (point == 0x7fff) {
nextIsV = true; nextIsVertical = true;
continue; continue;
} }
x = point; x = point;
polygon.addPoint(x, y); area.exclusiveOr(new Area(new Rectangle(x, y, pBounds.x + pBounds.width - x, pBounds.y + pBounds.height - y)));
} }
} }
return polygon; 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 * Read in a polygon. The input stream should be positioned at the first byte
* of the polygon. * 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 // Get polygon data size
int size = pStream.readUnsignedShort(); int size = pStream.readUnsignedShort();
// Get poly bounds // Get poly bounds
int y = getYPtCoord(pStream.readShort()); readRectangle(pStream, pBounds);
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);
// Initialize the point array to the right size // Initialize the point array to the right size
int points = (size - 10) / 4; int points = (size - 10) / 4;
@ -2454,11 +2462,15 @@ public final class PICTImageReader extends ImageReaderBase {
Polygon polygon = new Polygon(); Polygon polygon = new Polygon();
for (int i = 0; i < points; i++) { for (int i = 0; i < points; i++) {
y = getYPtCoord(pStream.readShort()); int y = getYPtCoord(pStream.readShort());
x = getXPtCoord(pStream.readShort()); int x = getXPtCoord(pStream.readShort());
polygon.addPoint(x, y); polygon.addPoint(x, y);
} }
if (!pBounds.contains(polygon.getBounds())) {
processWarningOccurred("Bad poly, contains point(s) out of bounds " + pBounds + ": " + polygon);
}
return polygon; return polygon;
} }
@ -2540,15 +2552,15 @@ public final class PICTImageReader extends ImageReaderBase {
/* /*
* Write out region command, bounds and points. * 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.println(pCmd + ": " + pBounds);
System.out.print("Region points: "); // System.out.print("Region points: ");
if (pPolygon != null && pPolygon.npoints > 0) { // if (pPolygon != null && pPolygon.npoints > 0) {
System.out.print("(" + pPolygon.xpoints[0] + "," + pPolygon.ypoints[0] + ")"); // System.out.print("(" + pPolygon.xpoints[0] + "," + pPolygon.ypoints[0] + ")");
} // }
for (int i = 1; pPolygon != null && i < pPolygon.npoints; i++) { // for (int i = 1; pPolygon != null && i < pPolygon.npoints; i++) {
System.out.print(", (" + pPolygon.xpoints[i] + "," + pPolygon.ypoints[i] + ")"); // System.out.print(", (" + pPolygon.xpoints[i] + "," + pPolygon.ypoints[i] + ")");
} // }
System.out.println(); System.out.println();
} }

View File

@ -28,8 +28,9 @@
package com.twelvemonkeys.imageio.plugins.pict; package com.twelvemonkeys.imageio.plugins.pict;
import java.awt.image.*;
import java.awt.*; import java.awt.*;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
/** /**
* QuickDraw constants. * 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-196.html#HEADING196-2
// http://developer.apple.com/documentation/mac/quickdraw/QuickDraw-269.html#HEADING269-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! // 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_COPY = 0;
int SRC_OR = 1; int SRC_OR = 1;
int SRC_XOR = 2; int SRC_XOR = 2;
@ -84,16 +97,73 @@ interface QuickDraw {
// Arithmetic Transfer Modes // Arithmetic Transfer Modes
// http://developer.apple.com/documentation/mac/quickdraw/QuickDraw-199.html#HEADING199-112 // 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 BLEND = 32; // dest = source weight/65,535 + destination (1 - weight/65,535)
int ADD_PIN = 33; int ADD_PIN = 33;
int ADD_OVER = 34; int ADD_OVER = 34;
int SUB_PIN = 35; int SUB_PIN = 35;
int TRANSPARENT = 36; int TRANSPARENT = 36;
int AD_MAX = 37; int ADD_MAX = 37;
int SUB_OVER = 38; 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 GRAYISH_TEXT_OR = 49;
// int MASK = 64; // ?! From Käry's code..
/* /*
* Text face masks. * 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.*;
import java.awt.geom.*; import java.awt.geom.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.Raster; import static java.lang.Math.sqrt;
import java.awt.image.WritableRaster;
/** /**
* Emulates an Apple QuickDraw rendering context, backed by a Java {@link Graphics2D}. * Emulates an Apple QuickDraw rendering context, backed by a Java {@link Graphics2D}.
@ -308,9 +307,9 @@ class QuickDrawContext {
case QuickDraw.ADD_OVER: case QuickDraw.ADD_OVER:
case QuickDraw.SUB_PIN: case QuickDraw.SUB_PIN:
case QuickDraw.TRANSPARENT: case QuickDraw.TRANSPARENT:
case QuickDraw.AD_MAX: case QuickDraw.ADD_MAX:
case QuickDraw.SUB_OVER: case QuickDraw.SUB_OVER:
case QuickDraw.AD_MIN: case QuickDraw.ADD_MIN:
case QuickDraw.GRAYISH_TEXT_OR: case QuickDraw.GRAYISH_TEXT_OR:
penMode = pPenMode; penMode = pPenMode;
break; break;
@ -365,12 +364,11 @@ class QuickDrawContext {
case QuickDraw.SRC_BIC: case QuickDraw.SRC_BIC:
return AlphaComposite.Clear; return AlphaComposite.Clear;
case QuickDraw.NOT_SRC_XOR: case QuickDraw.NOT_SRC_XOR:
return new NotSrcXor(); return QuickDrawComposite.NotSrcXor;
case QuickDraw.NOT_SRC_COPY: case QuickDraw.NOT_SRC_COPY:
case QuickDraw.NOT_SRC_OR: case QuickDraw.NOT_SRC_OR:
case QuickDraw.NOT_SRC_BIC: case QuickDraw.NOT_SRC_BIC:
throw new UnsupportedOperationException("Not implemented for mode " + pMode); throw new UnsupportedOperationException("Not implemented for mode " + pMode);
// return null;
// Boolean pattern transfer modes // Boolean pattern transfer modes
case QuickDraw.PAT_COPY: case QuickDraw.PAT_COPY:
return AlphaComposite.Src; // Tested return AlphaComposite.Src; // Tested
@ -385,8 +383,22 @@ class QuickDrawContext {
case QuickDraw.NOT_PAT_XOR: case QuickDraw.NOT_PAT_XOR:
case QuickDraw.NOT_PAT_BIC: case QuickDraw.NOT_PAT_BIC:
throw new UnsupportedOperationException("Not implemented for mode " + pMode); throw new UnsupportedOperationException("Not implemented for mode " + pMode);
// return null; // Aritmetic transfer modes
// TODO: 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: default:
throw new IllegalArgumentException("Unknown pnMode: " + pMode); throw new IllegalArgumentException("Unknown pnMode: " + pMode);
@ -401,7 +413,6 @@ class QuickDrawContext {
graphics.setComposite(getCompositeFor(textMode)); graphics.setComposite(getCompositeFor(textMode));
} }
/** /**
* Sets up context for line drawing/painting. * Sets up context for line drawing/painting.
*/ */
@ -905,6 +916,7 @@ class QuickDrawContext {
// Copying Images (SKIP?): // Copying Images (SKIP?):
*/ */
/** /**
* CopyBits. * CopyBits.
* <p/> * <p/>
@ -941,14 +953,26 @@ class QuickDrawContext {
/** /**
* CopyMask * CopyMask
*/ */
public void copyMask(BufferedImage pSrcBitmap, BufferedImage pMaskBitmap, Rectangle pSrcRect, Rectangle pMaskRect, Rectangle pDstRect, int pSrcCopy, Shape pMaskRgn) { 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 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 * 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) { 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 throw new UnsupportedOperationException("Method copyDeepMask not implemented"); // TODO: Implement
} }
@ -1079,7 +1103,7 @@ class QuickDrawContext {
thisY = points[1]; thisY = points[1];
float dx = thisX - lastX; float dx = thisX - lastX;
float dy = thisY - lastY; float dy = thisY - lastY;
float distance = (float) Math.sqrt(dx * dx + dy * dy); float distance = (float) sqrt(dx * dx + dy * dy);
if (distance >= next) { if (distance >= next) {
float r = 1.0f / distance; float r = 1.0f / distance;
//float angle = (float) Math.atan2(dy, dx); //float angle = (float) Math.atan2(dy, dx);
@ -1106,43 +1130,5 @@ class QuickDrawContext {
return result; 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);
}
}
};
}
} }
} }