TMI-106, TMI-118: PICT JDK 8 fix + cleanup

This commit is contained in:
Harald Kuhr
2015-03-19 14:49:52 +01:00
parent 1505aa651b
commit 4839c61f5c
16 changed files with 829 additions and 803 deletions
@@ -0,0 +1,11 @@
package com.twelvemonkeys.imageio.plugins.pict;
/**
* BitMap.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: BitMap.java,v 1.0 20/02/15 harald.kuhr Exp$
*/
final class BitMap {
}
@@ -29,10 +29,9 @@
package com.twelvemonkeys.imageio.plugins.pict;
import java.awt.*;
import java.awt.image.WritableRaster;
import java.awt.image.DataBufferByte;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.*;
/**
* BitMapPattern
@@ -43,22 +42,46 @@ import java.awt.image.Raster;
*/
final class BitMapPattern extends Pattern {
private final byte[] pattern;
BitMapPattern(final Paint pColor) {
super(pColor);
this(pColor, null);
}
public BitMapPattern(final byte[] pPattern) {
this(create8x8Pattern(pPattern));
this(create8x8Pattern(pPattern), pPattern);
}
private BitMapPattern(final Paint pColor, final byte[] pPattern) {
super(pColor);
pattern = pPattern;
}
// TODO: Refactor, don't need both BitMapPattern constructors and create8x8Pattern methods?
public BitMapPattern(final byte[] pPattern, Color fg, Color bg) {
this(create8x8Pattern(pPattern, fg, bg));
}
BitMapPattern(final int pPattern) {
this(create8x8Pattern(pPattern));
}
private static TexturePaint create8x8Pattern(final int pPattern) {
// TODO: Creating a special purpose Pattern might be faster than piggy-backing on TexturePaint
WritableRaster raster = QuickDraw.MONOCHROME.createCompatibleWritableRaster(8, 8);
byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
private static Paint create8x8Pattern(final int pPattern) {
// // TODO: Creating a special purpose Pattern might be faster than piggy-backing on TexturePaint
// WritableRaster raster = QuickDraw.MONOCHROME.createCompatibleWritableRaster(8, 8);
// byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
//
// for (int i = 0; i < data.length; i += 4) {
// data[i ] = (byte) ((pPattern >> 24) & 0xFF);
// data[i + 1] = (byte) ((pPattern >> 16) & 0xFF);
// data[i + 2] = (byte) ((pPattern >> 8) & 0xFF);
// data[i + 3] = (byte) ((pPattern ) & 0xFF);
// }
//
// BufferedImage img = new BufferedImage(QuickDraw.MONOCHROME, raster, false, null);
// return new TexturePaint(img, new Rectangle(8, 8));
byte[] data = new byte[8];
for (int i = 0; i < data.length; i += 4) {
data[i ] = (byte) ((pPattern >> 24) & 0xFF);
@@ -67,13 +90,57 @@ final class BitMapPattern extends Pattern {
data[i + 3] = (byte) ((pPattern ) & 0xFF);
}
BufferedImage img = new BufferedImage(QuickDraw.MONOCHROME, raster, false, null);
return new TexturePaint(img, new Rectangle(8, 8));
return create8x8Pattern(data);
}
private static TexturePaint create8x8Pattern(final byte[] pPattern) {
private static Paint create8x8Pattern(final byte[] pPattern) {
WritableRaster raster = Raster.createPackedRaster(new DataBufferByte(pPattern, 8), 8, 8, 1, new Point());
BufferedImage img = new BufferedImage(QuickDraw.MONOCHROME, raster, false, null);
return new TexturePaint(img, new Rectangle(8, 8));
}
private static Paint create8x8Pattern(final byte[] pPattern, Color fg, Color bg) {
switch (isSolid(pPattern)) {
case 0: // 0x00
return bg;
case -1: // 0xff
return fg;
default:
// Fall through
}
WritableRaster raster = Raster.createPackedRaster(new DataBufferByte(pPattern, 8), 8, 8, 1, new Point());
IndexColorModel cm = new IndexColorModel(1, 2, new int[] {bg.getRGB(), fg.getRGB()}, 0, false, -1, DataBuffer.TYPE_BYTE);
BufferedImage img = new BufferedImage(cm, raster, false, null);
return new TexturePaint(img, new Rectangle(8, 8));
}
private static int isSolid(byte[] pPattern) {
int prev = pPattern[0];
for (int i = 1; i < pPattern.length; i++) {
if (prev != pPattern[i]) {
return 1;
}
}
return prev;
}
@Override
public PaintContext createContext(ColorModel pModel, Rectangle pDeviceBounds, Rectangle2D pUserBounds, AffineTransform pTransform, RenderingHints pHints) {
// switch (isSolid(pattern)) {
// }
return super.createContext(pModel, pDeviceBounds, pUserBounds, pTransform, pHints);
}
@Override
public Pattern derive(final Color foreground, final Color background) {
if (paint instanceof Color) {
// TODO: This only holds for patterns that are already foregrounds...
return new BitMapPattern(foreground);
}
return null;
}
}
@@ -175,4 +175,41 @@ interface PICT {
int OP_UNCOMPRESSED_QUICKTIME = 0x8201;
String APPLE_USE_RESERVED_FIELD = "Reserved for Apple use.";
/*
* Picture comment 'kind' codes from: http://developer.apple.com/technotes/qd/qd_10.html
int TextBegin = 150;
int TextEnd = 151;
int StringBegin = 152;
int StringEnd = 153;
int TextCenter = 154;
int LineLayoutOff = 155;
int LineLayoutOn = 156;
int ClientLineLayout = 157;
int PolyBegin = 160;
int PolyEnd = 161;
int PolyIgnore = 163;
int PolySmooth = 164;
int PolyClose = 165;
int DashedLine = 180;
int DashedStop = 181;
int SetLineWidth = 182;
int PostScriptBegin = 190;
int PostScriptEnd = 191;
int PostScriptHandle = 192;
int PostScriptFile = 193;
int TextIsPostScript = 194;
int ResourcePS = 195;
int PSBeginNoSave = 196;
int SetGrayLevel = 197;
int RotateBegin = 200;
int RotateEnd = 201;
int RotateCenter = 202;
int FormsPrinting = 210;
int EndFormsPrinting = 211;
int ICC_Profile = 224;
int Photoshop_Data = 498;
int BitMapThinningOff = 1000;
int BitMapThinningOn = 1001;
*/
}
@@ -34,8 +34,8 @@ import com.twelvemonkeys.imageio.util.IIOUtil;
import javax.imageio.ImageReader;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.io.EOFException;
import java.io.IOException;
import java.util.Locale;
/**
@@ -85,7 +85,7 @@ public class PICTImageReaderSpi extends ImageReaderSpi {
else {
// Skip header 512 bytes for file-based streams
stream.reset();
PICTImageReader.skipNullHeader(stream);
skipNullHeader(stream);
}
return isPICT(stream);
@@ -98,6 +98,12 @@ public class PICTImageReaderSpi extends ImageReaderSpi {
}
}
static void skipNullHeader(final ImageInputStream pStream) throws IOException {
// NOTE: Only skip if FILE FORMAT, not needed for Mac OS DnD
// Spec says "platofrm dependent", may not be all nulls..
pStream.skipBytes(PICT.PICT_NULL_HEADER_SIZE);
}
private boolean isPICT(final ImageInputStream pStream) throws IOException {
// Size may be 0, so we can't use this for validation...
pStream.readUnsignedShort();
@@ -258,7 +258,7 @@ public class PICTImageWriter extends ImageWriterBase {
// Treat the scanline.
for (int j = 0; j < w; j++) {
if (model instanceof ComponentColorModel && model.getColorSpace().getType() == ColorSpace.TYPE_RGB) {
// NOTE: Assumes component order always (A)BGR
// NOTE: Assumes component order always (A)BGR and sRGB
// TODO: Alpha support
scanlineBytes[x + j] = pixels[off + i * scansize * components + components * j + components - 1];
scanlineBytes[x + w + j] = pixels[off + i * scansize * components + components * j + components - 2];
@@ -34,7 +34,8 @@ import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
import java.io.DataInput;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
/**
* PICTUtil
@@ -47,15 +48,14 @@ final class PICTUtil {
private static final String ENC_MAC_ROMAN = "MacRoman";
public static final String ENCODING = initEncoding();
public static final Charset ENCODING = initEncoding();
private static String initEncoding() {
private static Charset initEncoding() {
try {
new String("\uF8FF".getBytes(), ENC_MAC_ROMAN);
return ENC_MAC_ROMAN;
return Charset.forName(ENC_MAC_ROMAN);
}
catch (UnsupportedEncodingException e) {
return "ISO-8859-1";
catch (UnsupportedCharsetException e) {
return Charset.forName("ISO-8859-1");
}
}
@@ -86,9 +86,9 @@ final class PICTUtil {
* @throws java.io.IOException if an I/O error occurs during read
*/
public static Dimension readDimension(final DataInput pStream) throws IOException {
final int h = pStream.readShort() ;
final int v = pStream.readShort() ;
return new Dimension(h,v);
int h = pStream.readShort();
int v = pStream.readShort();
return new Dimension(h, v);
}
/**
@@ -102,8 +102,8 @@ final class PICTUtil {
* @throws IOException if an I/O exception occurs during reading
*/
public static String readStr31(final DataInput pStream) throws IOException {
String text = readPascalString(pStream);
int length = 31 - text.length();
String text = readPascalString(pStream);
int length = 31 - text.length();
if (length < 0) {
throw new IOException("String length exceeds maximum (31): " + text.length());
}
@@ -112,7 +112,7 @@ final class PICTUtil {
}
/**
* Reads a Pascal String from the given strean.
* Reads a Pascal String from the given stream.
* The input stream must be positioned at the length byte of the text,
* which can thus be a maximum of 255 characters long.
*
@@ -146,6 +146,14 @@ final class PICTUtil {
return new BitMapPattern(data);
}
// TODO: Refactor, don't need both readPattern methods
public static Pattern readPattern(final DataInput pStream, final Color fg, final Color bg) throws IOException {
// Get the data (8 bytes)
byte[] data = new byte[8];
pStream.readFully(data);
return new BitMapPattern(data, fg, bg);
}
/**
* Reads a variable width {@link Pattern color pattern} from the given stream
*
@@ -221,7 +229,7 @@ final class PICTUtil {
/**
* Reads a {@code ColorTable} data structure from the given stream.
*
* @param pStream the input stream
* @param pStream the input stream
* @param pPixelSize the pixel size
* @return the indexed color model created from the {@code ColorSpec} records read.
*
@@ -252,7 +260,7 @@ final class PICTUtil {
int[] colors = new int[size];
for (int i = 0; i < size ; i++) {
for (int i = 0; i < size; i++) {
// Read ColorSpec records
int index = pStream.readUnsignedShort();
Color color = readRGBColor(pStream);
@@ -29,9 +29,9 @@
package com.twelvemonkeys.imageio.plugins.pict;
import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.awt.geom.AffineTransform;
import java.awt.image.*;
import java.awt.geom.Rectangle2D;
import java.awt.image.ColorModel;
import java.util.Collections;
/**
@@ -42,7 +42,7 @@ import java.util.Collections;
* @version $Id: Pattern.java,v 1.0 Oct 9, 2007 1:21:38 AM haraldk Exp$
*/
abstract class Pattern implements Paint {
private final Paint paint;
protected final Paint paint;
Pattern(final Paint pPaint) {
paint = pPaint;
@@ -60,5 +60,7 @@ abstract class Pattern implements Paint {
public int getTransparency() {
return paint.getTransparency();
}
}
public abstract Pattern derive(Color foreground, Color background);
}
@@ -0,0 +1,11 @@
package com.twelvemonkeys.imageio.plugins.pict;
/**
* PixMap.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: PixMap.java,v 1.0 20/02/15 harald.kuhr Exp$
*/
final class PixMap {
}
@@ -48,7 +48,12 @@ final class PixMapPattern extends Pattern {
/**
* @return the fallback B/W pattern
*/
public Pattern getPattern() {
public Pattern getFallbackPattern() {
return fallback;
}
@Override
public Pattern derive(final Color foreground, final Color background) {
return getFallbackPattern().derive(foreground, background);
}
}
@@ -31,8 +31,11 @@ package com.twelvemonkeys.imageio.plugins.pict;
import com.twelvemonkeys.lang.Validate;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
/**
* Emulates an Apple QuickDraw rendering context, backed by a Java {@link Graphics2D}.
@@ -121,9 +124,20 @@ class QuickDrawContext {
private Dimension2D penSize = new Dimension();
private int penMode;
QuickDrawContext(Graphics2D pGraphics) {
// TODO: Make sure setting bgColor/fgColor does not reset pattern, and pattern not resetting bg/fg!
private Color bgColor = Color.WHITE;
private Color fgColor = Color.BLACK;
private int textMode;
private Pattern textPattern = new BitMapPattern(Color.BLACK);
private Pattern fillPattern;
QuickDrawContext(final Graphics2D pGraphics) {
graphics = Validate.notNull(pGraphics, "graphics");
graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
setPenNormal();
}
@@ -144,18 +158,34 @@ class QuickDrawContext {
// Font number (sic), integer
void setTextFont(int fontFamily) {
// ..?
System.err.println("QuickDrawContext.setTextFont");
System.err.println("QuickDrawContext.setTextFont: " + fontFamily);
}
public void setTextFont(final String fontName) {
// TODO: Need mapping between known QD font names and Java font names?
Font current = graphics.getFont();
graphics.setFont(Font.decode(fontName).deriveFont(current.getStyle(), (float) current.getSize()));
}
// Sets the text's font style (0..255)
void setTextFace(int face) {
// int?
System.err.println("QuickDrawContext.setTextFace");
void setTextFace(final int face) {
int style = 0;
if ((face & QuickDraw.TX_BOLD_MASK) > 0) {
style |= Font.BOLD;
}
if ((face & QuickDraw.TX_ITALIC_MASK) > 0) {
style |= Font.ITALIC;
}
// TODO: Other face options, like underline, shadow, etc...
graphics.setFont(graphics.getFont().deriveFont(style));
}
void setTextMode(int pSourceMode) {
// ..?
System.err.println("QuickDrawContext.setTextMode");
textMode = pSourceMode;
}
public void setTextSize(int pSize) {
@@ -175,15 +205,24 @@ class QuickDrawContext {
graphics.translate(pOrigin.getX(), pOrigin.getY());
}
public void setForeground(Color pColor) {
// TODO: Is this really correct? Or does it depend on pattern mode?
public void setForeground(final Color pColor) {
fgColor = pColor;
penPattern = new BitMapPattern(pColor);
}
public void setBackground(Color pColor) {
Color getForeground() {
return fgColor;
}
public void setBackground(final Color pColor) {
bgColor = pColor;
background = new BitMapPattern(pColor);
}
Color getBackground() {
return bgColor;
}
/*
// Pen management:
// NOTE: The HidePen procedure is called by the OpenRgn, OpenPicture, and OpenPoly routines so that you can create regions, pictures, and polygons without drawing on the screen.
@@ -306,10 +345,14 @@ class QuickDrawContext {
BackPat // Used by the Erase* methods
*BackPixPat
*/
public void setBackgroundPattern(Pattern pPaint) {
public void setBackgroundPattern(final Pattern pPaint) {
background = pPaint;
}
public void setFillPattern(final Pattern fillPattern) {
this.fillPattern = fillPattern;
}
private Composite getCompositeFor(final int pMode) {
switch (pMode & ~QuickDraw.DITHER_COPY) {
// Boolean source transfer modes
@@ -321,9 +364,10 @@ class QuickDrawContext {
return AlphaComposite.Xor;
case QuickDraw.SRC_BIC:
return AlphaComposite.Clear;
case QuickDraw.NOT_SRC_XOR:
return new NotSrcXor();
case QuickDraw.NOT_SRC_COPY:
case QuickDraw.NOT_SRC_OR:
case QuickDraw.NOT_SRC_XOR:
case QuickDraw.NOT_SRC_BIC:
throw new UnsupportedOperationException("Not implemented for mode " + pMode);
// return null;
@@ -349,6 +393,15 @@ class QuickDrawContext {
}
}
/**
* Sets up context for text drawing.
*/
protected void setupForText() {
graphics.setPaint(textPattern);
graphics.setComposite(getCompositeFor(textMode));
}
/**
* Sets up context for line drawing/painting.
*/
@@ -415,9 +468,7 @@ class QuickDrawContext {
if (isPenVisible()) {
// NOTE: Workaround for known Mac JDK bug: Paint, not frame
//graphics.setStroke(getStroke(penSize)); // Make sure we have correct stroke
paintShape(graphics.getStroke().createStrokedShape(line));
}
moveTo(pX, pY);
@@ -811,13 +862,18 @@ class QuickDrawContext {
// TODO: All other operations can delegate to these! :-)
private void frameShape(final Shape pShape) {
setupForPaint();
graphics.draw(pShape);
if (isPenVisible()) {
setupForPaint();
Stroke stroke = getStroke(penSize);
Shape shape = stroke.createStrokedShape(pShape);
graphics.draw(shape);
}
}
private void paintShape(final Shape pShape) {
setupForPaint();
graphics.fill(pShape);
graphics.fill(pShape); // Yes, fill
}
private void fillShape(final Shape pShape, final Pattern pPattern) {
@@ -878,20 +934,22 @@ class QuickDrawContext {
pSrcRect.y + pSrcRect.height,
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 copyBits 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
*/
public void copyDeepMask(BufferedImage pSrcBitmap, BufferedImage pMaskBitmap, Rectangle pSrcRect, Rectangle pMaskRect, Rectangle pDstRect, int pSrcCopy, Shape pMaskRgn) {
throw new UnsupportedOperationException("Method copyBits not implemented"); // TODO: Implement
throw new UnsupportedOperationException("Method copyDeepMask not implemented"); // TODO: Implement
}
/*
@@ -926,7 +984,8 @@ class QuickDrawContext {
* @param pString a Pascal string (a string of length less than or equal to 255 chars).
*/
public void drawString(String pString) {
graphics.drawString(pString, (float) getPenPosition().getX(), (float) getPenPosition().getY());
setupForText();
graphics.drawString(pString, (float) getPenPosition().getX(), (float) getPenPosition().getY());
}
/*
@@ -1049,4 +1108,41 @@ class QuickDrawContext {
}
}
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);
}
}
};
}
}
}