// Copyright 2001-2006, FreeHEP. package org.freehep.postscript; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.io.IOException; /** * Painting Operators for PostScript Processor * * @author Mark Donszelmann * @version $Id: PaintingOperator.java 10178 2006-12-08 09:03:07Z duns $ */ public class PaintingOperator extends PSOperator { public static Class[] operators = { ErasePage.class, Stroke.class, Fill.class, EOFill.class, RectStroke.class, RectFill.class, UStroke.class, UFill.class, UEOFill.class, ShFill.class, PaintImage.class, ColorImage.class, ImageMask.class }; public boolean execute(OperandStack os) { throw new RuntimeException("Cannot execute class: "+getClass()); } } class ErasePage extends PaintingOperator { public boolean execute(OperandStack os) { os.gstate().erasePage(); return true; } } class Stroke extends PaintingOperator { public boolean execute(OperandStack os) { // FREEHEP-156: add special case for degenerate paths os.gstate().stroke(); os.gstate().newPath(); return true; } } class Fill extends PaintingOperator { public boolean execute(OperandStack os) { // FREEHEP-156: add special case for degenerate paths os.gstate().fill(); os.gstate().newPath(); return true; } } class EOFill extends PaintingOperator { public boolean execute(OperandStack os) { // FREEHEP-156: add special case for degenerate paths os.gstate().path().setWindingRule(GeneralPath.WIND_EVEN_ODD); os.gstate().fill(); os.gstate().newPath(); return true; } } class RectStroke extends PaintingOperator { { operandTypes = new Class[] {PSObject.class}; } private static void stroke(OperandStack os, double x, double y, double w, double h, AffineTransform m) { Rectangle2D r = new Rectangle2D.Double(x, y, w, h); os.gstate().stroke(r, m); } private static void stroke(OperandStack os, PSPackedArray a, AffineTransform m) { for (int i=0; i<a.size()/4; i++) { double x = ((PSNumber)a.get(i*4)).getDouble(); double y = ((PSNumber)a.get(i*4+1)).getDouble(); double w = ((PSNumber)a.get(i*4+2)).getDouble(); double h = ((PSNumber)a.get(i*4+3)).getDouble(); stroke(os, x, y, w, h, m); } } public boolean execute(OperandStack os) { if (os.checkType(PSNumber.class, PSNumber.class, PSNumber.class, PSNumber.class)) { double h = os.popNumber().getDouble(); double w = os.popNumber().getDouble(); double y = os.popNumber().getDouble(); double x = os.popNumber().getDouble(); stroke(os, x, y, w, h, null); } else if (os.checkType(PSNumber.class, PSNumber.class, PSNumber.class, PSNumber.class, PSPackedArray.class)) { AffineTransform m = new AffineTransform(os.popPackedArray().toDoubles()); double h = os.popNumber().getDouble(); double w = os.popNumber().getDouble(); double y = os.popNumber().getDouble(); double x = os.popNumber().getDouble(); stroke(os, x, y, w, h, m); } else if (os.checkType(PSPackedArray.class, PSPackedArray.class)) { AffineTransform m = new AffineTransform(os.popPackedArray().toDoubles()); PSPackedArray a = os.popPackedArray(); stroke(os, a, m); } else if (os.checkType(PSPackedArray.class)) { PSPackedArray a = os.popPackedArray(); stroke(os, a, null); } else { // FIXME: encoded strings not handled error(os, new TypeCheck()); } return true; } } class RectFill extends PaintingOperator { { operandTypes = new Class[] {PSObject.class}; } public boolean execute(OperandStack os) { if (os.checkType(PSNumber.class, PSNumber.class, PSNumber.class, PSNumber.class)) { double h = os.popNumber().getDouble(); double w = os.popNumber().getDouble(); double y = os.popNumber().getDouble(); double x = os.popNumber().getDouble(); Rectangle2D r = new Rectangle2D.Double(x, y, w, h); os.gstate().fill(r); } else if (os.checkType(PSPackedArray.class)) { PSPackedArray a = os.popPackedArray(); for (int i=0; i<a.size()/4; i++) { double x = ((PSNumber)a.get(i*4)).getDouble(); double y = ((PSNumber)a.get(i*4+1)).getDouble(); double w = ((PSNumber)a.get(i*4+2)).getDouble(); double h = ((PSNumber)a.get(i*4+3)).getDouble(); Rectangle2D r = new Rectangle2D.Double(x, y, w, h); os.gstate().fill(r); } } else { // FIXME: encoded strings not handled error(os, new TypeCheck()); } return true; } } class UStroke extends PaintingOperator { private boolean done; private AffineTransform matrix; private UStroke(boolean d, AffineTransform m) { done = d; matrix = m; } public UStroke() { } public boolean execute(OperandStack os) { if (!done) { if (!os.checkType(PSPackedArray.class)) { error(os, new TypeCheck()); return true; } AffineTransform matrix = null; PSPackedArray proc = os.popPackedArray(); if (proc.size() == 6) { try { matrix = new AffineTransform(proc.toDoubles()); if (!os.checkType(PSPackedArray.class)) { error(os, new TypeCheck()); return true; } proc = os.popPackedArray(); } catch (ClassCastException e) { // no matrix } } // gsave, newpath, systemdict, begin os.gsave(); os.gstate().newPath(); os.dictStack().push(os.dictStack().systemDictionary()); PSPackedArray upath = (PSPackedArray)proc.copy(); upath.setExecutable(); os.execStack().pop(); os.execStack().push(new UStroke(true, matrix)); os.execStack().push(upath); return false; } // upath was executed, end, stroke and grestore os.dictStack().pop(); if (matrix != null) { os.gstate().transform(matrix); } os.gstate().stroke(); os.grestore(); return true; } } class UFill extends PaintingOperator { private boolean done; private UFill(boolean d) { done = d; } public UFill() { } public boolean execute(OperandStack os) { if (!done) { if (!os.checkType(PSPackedArray.class)) { error(os, new TypeCheck()); return true; } PSPackedArray proc = os.popPackedArray(); // gsave, newpath, systemdict, begin os.gsave(); os.gstate().newPath(); os.dictStack().push(os.dictStack().systemDictionary()); PSPackedArray upath = (PSPackedArray)proc.copy(); upath.setExecutable(); os.execStack().pop(); os.execStack().push(new UFill(true)); os.execStack().push(upath); return false; } // upath was executed, end, fill and grestore os.dictStack().pop(); os.gstate().fill(); os.grestore(); return true; } } class UEOFill extends PaintingOperator { private boolean done; private UEOFill(boolean d) { done = d; } public UEOFill() { } public boolean execute(OperandStack os) { if (!done) { if (!os.checkType(PSPackedArray.class)) { error(os, new TypeCheck()); return true; } PSPackedArray proc = os.popPackedArray(); // gsave, newpath, systemdict, begin os.gsave(); os.gstate().newPath(); os.dictStack().push(os.dictStack().systemDictionary()); PSPackedArray upath = (PSPackedArray)proc.copy(); upath.setExecutable(); os.execStack().pop(); os.execStack().push(new UEOFill(true)); os.execStack().push(upath); return false; } // upath was executed, end, eofill and grestore os.dictStack().pop(); os.gstate().path().setWindingRule(GeneralPath.WIND_EVEN_ODD); os.gstate().fill(); os.grestore(); return true; } } class ShFill extends PaintingOperator { { operandTypes = new Class[] {PSDictionary.class}; } public boolean execute(OperandStack os) { // Level 3 PostScript error(os, new Unimplemented()); return true; } } class ImageOperator extends PaintingOperator { protected boolean imageMask; protected int width; protected int height; protected AffineTransform matrix; protected PSPackedArray[] proc; protected PSDataSource[] source; protected boolean multi; protected int bitsPerComponent; protected PSPackedArray decode; protected boolean interpolate; protected final int dataSize = 8; protected BufferedImage image; protected int pixelBitStride; protected int scanlineStride; protected int components; protected float[] color; protected int[] pixels = new int[4]; protected int x, y, c; protected boolean popString; protected ImageOperator() { } protected ImageOperator(boolean mask, int w, int h, int b, PSPackedArray m, PSObject[] ds, PSPackedArray d, boolean i) { imageMask = mask; width = w; height = h; switch(b) { case 1: case 2: case 4: case 8: // case 12: // FIXME: probably does not work bitsPerComponent = b; break; default: throw new IllegalArgumentException(); } matrix = new AffineTransform(m.toDoubles()); multi = (ds.length > 1); proc = new PSPackedArray[ds.length]; source = new PSDataSource[ds.length]; if (ds[0] instanceof PSPackedArray) { for (int k=0; k < ds.length; k++) { proc[k] = (PSPackedArray)ds[k]; } } else if (ds[0] instanceof PSDataSource) { for (int k=0; k < ds.length; k++) { source[k] = (PSDataSource)ds[k]; } } else { throw new IllegalArgumentException(); } decode = d; components = decode.size()/2; color = new float[components]; interpolate = i; image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); pixelBitStride = bitsPerComponent * components; scanlineStride = (int)Math.ceil(width * pixelBitStride / dataSize); popString = false; x = y = c = 0; } public boolean handleImageParameters(OperandStack os, boolean mask) { if (os.checkType(PSDictionary.class)) { PSDictionary dict = os.popDictionary(); try { int type = dict.getInteger("ImageType"); switch (type) { case 1: int w = dict.getInteger("Width"); int h = dict.getInteger("Height"); PSPackedArray m = dict.getPackedArray("ImageMatrix"); PSObject multi = dict.get("MultipleDataSources"); PSObject[] ds; if ((multi != null) && ((PSBoolean)multi).getValue()) { if (mask) { error(os, new RangeCheck()); return true; } ds = dict.getPackedArray("DataSource").toObjects(); } else { ds = new PSObject[1]; ds[0] = dict.get("DataSource"); } int b = dict.getInteger("BitsPerComponent"); if (mask && (b != 1)) { error(os, new RangeCheck()); return true; } PSPackedArray d = dict.getPackedArray("Decode"); if (mask) { // FIXME: check d being 1 0 or 0 1 } else { if (d.size() != os.gstate().getNumberOfColorSpaceComponents()*2) { error(os, new RangeCheck()); return true; } } PSBoolean inter = (PSBoolean)dict.get("Interpolate"); boolean i = (inter != null) ? inter.getValue() : false; ImageOperator imageOperator = new ImageOperator(mask, w, h, b, m, ds, d, i); os.execStack().pop(); os.execStack().push(imageOperator); return false; default: // Level 3 error(os, new Undefined()); break; } } catch (NullPointerException e) { e.printStackTrace(); error(os, new Undefined()); } catch (IllegalArgumentException e) { error(os, new Undefined()); } return true; } else if (os.checkType(PSInteger.class, PSInteger.class, PSObject.class, PSPackedArray.class, PSObject.class)) { PSObject[] ds = new PSObject[1]; ds[0] = os.popObject(); PSPackedArray m = os.popPackedArray(); int b; PSPackedArray d; if (mask) { b = 1; if (os.popBoolean().getValue()) { d = new PSPackedArray(new double[] {1, 0}); } else { d = new PSPackedArray(new double[] {0, 1}); } } else { // image is always DeviceGray b = os.popInteger().getValue(); d = new PSPackedArray(new double[] {0, 1}); } int h = os.popInteger().getValue(); int w = os.popInteger().getValue(); try { ImageOperator imageOperator = new ImageOperator(mask, w, h, b, m, ds, d, false); os.execStack().pop(); os.execStack().push(imageOperator); } catch (IllegalArgumentException e) { error(os, new TypeCheck()); return true; } catch (ClassCastException e) { error(os, new TypeCheck()); return true; } return false; } else { error(os, new TypeCheck()); return true; } } public boolean execute(OperandStack os) { boolean eod = false; // pop the prepared string if ((proc[c] != null) && popString) { // store string if (!os.checkType(PSString.class)) { error(os, new TypeCheck()); } PSString s = os.popString(); if (s.size() <= 0) { eod = true; } source[c] = s; } int b = -1; int[] prevIndex = new int[4]; for (int i=0; i<prevIndex.length; i++) { prevIndex[i] = -1; } // fill part of the image try { // file or string is filled while ((y < height) && !eod) { while ((x < width) && !eod) { while((c < components) && !eod) { int bitnum = x*pixelBitStride + c*bitsPerComponent; int index = y*scanlineStride + bitnum/dataSize; int shift = dataSize - (bitnum & (dataSize-1)) - bitsPerComponent; int s = (multi) ? c : 0; if (index > prevIndex[s]) { prevIndex[s] = index; if (proc[s] != null) { b = (source[s] != null) ? source[s].read() : -1; if (b < 0) { popString = true; PSPackedArray p = (PSPackedArray)proc[s].clone(); os.execStack().push(p); return false; } } else { b = source[s].read(); if (b < 0) { if (source[s] instanceof PSString) { source[s].reset(); b = source[s].read(); } else { eod = true; } } } } int pixel = (b >> shift) & ((1 << bitsPerComponent) - 1); float dMin = ((PSNumber)decode.get(c*2+0)).getFloat(); float dMax = ((PSNumber)decode.get(c*2+1)).getFloat(); color[c] = dMin + (pixel * (dMax - dMin) / ((1 << bitsPerComponent) - 1)); c++; } float[] rgb; float alpha; if (imageMask) { // image mask rgb = PSGState.toRGB(os.gstate().color(), os.gstate().colorSpace()); alpha = (color[0] == 0) ? 1.0f : 0.0f; } else { // normal image switch(components) { case 1: rgb = PSGState.toRGB(color, "DeviceGray"); break; case 3: rgb = PSGState.toRGB(color, "DeviceRGB"); break; case 4: rgb = PSGState.toRGB(color, "DeviceCMYK"); break; default: System.out.println("length="+components); error(os, new RangeCheck()); return true; } alpha = 1.0f; } pixels[0] = (int)(rgb[0] * 255); pixels[1] = (int)(rgb[1] * 255); pixels[2] = (int)(rgb[2] * 255); pixels[3] = (int)(alpha * 255); image.getRaster().setPixel(x, y, pixels); c = 0; x++; } x = 0; y++; } // FIXME: ignores interpolate parameter os.gstate().image(image, matrix); } catch (IOException e) { error(os, new IOError()); } catch (ClassCastException e) { error(os, new TypeCheck()); } return true; } } class PaintImage extends ImageOperator { public String getName() { return "image"; } public boolean execute(OperandStack os) { return handleImageParameters(os, false); } } class ColorImage extends ImageOperator { public boolean execute(OperandStack os) { if (!os.checkType(PSBoolean.class, PSInteger.class)) { error(os, new TypeCheck()); return true; } int ncomp = os.popInteger().getValue(); boolean multi = os.popBoolean().getValue(); PSPackedArray d; switch(ncomp) { case 1: d = new PSPackedArray( new float[] {0, 1} ); break; case 3: d = new PSPackedArray( new float[] {0, 1, 0, 1, 0, 1} ); break; case 4: d = new PSPackedArray( new float[] {0, 1, 0, 1, 0, 1, 0, 1} ); break; default: error(os, new RangeCheck()); return true; } PSObject[] ds; if (multi) { ds = new PSObject[ncomp]; for (int i=ncomp-1; i>=0; i--) { ds[i] = os.popObject(); } } else { ds = new PSObject[1]; ds[0] = os.popObject(); } if (!os.checkType(PSInteger.class, PSInteger.class, PSInteger.class, PSPackedArray.class)) { error(os, new TypeCheck()); return true; } PSPackedArray m = os.popPackedArray(); int b = os.popInteger().getValue(); int h = os.popInteger().getValue(); int w = os.popInteger().getValue(); try { ImageOperator ci = new ImageOperator(false, w, h, b, m, ds, d, false); os.execStack().pop(); os.execStack().push(ci); return false; } catch (IllegalArgumentException e) { error(os, new TypeCheck()); return true; } catch (ClassCastException e) { error(os, new TypeCheck()); return true; } } } class ImageMask extends ImageOperator { public boolean execute(OperandStack os) { return handleImageParameters(os, true); } }