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