/* * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle, * Santa Clara, California 95054, U.S.A. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package com.sun.pdfview.pattern; import java.awt.Paint; import java.awt.PaintContext; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Transparency; import java.awt.color.ColorSpace; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.ColorModel; import java.awt.image.ComponentColorModel; import java.awt.image.DataBuffer; import java.awt.image.Raster; import java.awt.image.WritableRaster; import java.io.IOException; import com.sun.pdfview.PDFObject; import com.sun.pdfview.PDFPaint; import com.sun.pdfview.PDFParseException; import com.sun.pdfview.colorspace.PDFColorSpace; import com.sun.pdfview.function.PDFFunction; /** * A shader that performs axial shader based on a function. */ public class ShaderType2 extends PDFShader { /** the start of the axis */ private Point2D axisStart; /** the end of the axis */ private Point2D axisEnd; /** the domain minimum */ private float minT = 0f; /** the domain maximum */ private float maxT = 1f; /** whether to extend the start of the axis */ private boolean extendStart = false; /** whether to extend the end of the axis */ private boolean extendEnd = false; /** functions, as an array of either 1 or n functions */ private PDFFunction[] functions; /** Creates a new instance of ShaderType2 */ public ShaderType2() { super(2); } /** * Parse the shader-specific data */ @Override public void parse(PDFObject shaderObj) throws IOException { // read the axis coordinates (required) PDFObject coordsObj = shaderObj.getDictRef("Coords"); if (coordsObj == null) { throw new PDFParseException("No coordinates found!"); } PDFObject[] coords = coordsObj.getArray(); Point2D start = new Point2D.Float(coords[0].getFloatValue(), coords[1].getFloatValue()); Point2D end = new Point2D.Float(coords[2].getFloatValue(), coords[3].getFloatValue()); setAxisStart(start); setAxisEnd(end); // read the domain (optional) PDFObject domainObj = shaderObj.getDictRef("Domain"); if (domainObj != null) { PDFObject[] domain = domainObj.getArray(); setMinT(domain[0].getFloatValue()); setMaxT(domain[1].getFloatValue()); } // read the functions (required) PDFObject functionObj = shaderObj.getDictRef("Function"); if (functionObj == null) { throw new PDFParseException("No function defined for shader!"); } PDFObject[] functionArray = functionObj.getArray(); PDFFunction[] functions = new PDFFunction[functionArray.length]; for (int i = 0; i < functions.length; i++) { functions[i] = PDFFunction.getFunction(functionArray[i]); } setFunctions(functions); // read the extend array (optional) PDFObject extendObj = shaderObj.getDictRef("Extend"); if (extendObj != null) { PDFObject[] extendArray = extendObj.getArray(); setExtendStart(extendArray[0].getBooleanValue()); setExtendEnd(extendArray[1].getBooleanValue()); } } /** * Create a paint that paints this pattern */ @Override public PDFPaint getPaint() { return PDFPaint.getPaint(new Type2Paint()); } /** * Get the start of the axis */ public Point2D getAxisStart() { return this.axisStart; } /** * Set the start of the axis */ protected void setAxisStart(Point2D axisStart) { this.axisStart = axisStart; } /** * Get the end of the axis */ public Point2D getAxisEnd() { return this.axisEnd; } /** * Set the start of the axis */ protected void setAxisEnd(Point2D axisEnd) { this.axisEnd = axisEnd; } /** * Get the domain minimum */ public float getMinT() { return this.minT; } /** * Set the domain minimum */ protected void setMinT(float minT) { this.minT = minT; } /** * Get the domain maximum */ public float getMaxT() { return this.maxT; } /** * Set the domain maximum */ protected void setMaxT(float maxT) { this.maxT = maxT; } /** * Get whether to extend the start of the axis */ public boolean getExtendStart() { return this.extendStart; } /** * Set whether to extend the start of the axis */ protected void setExtendStart(boolean extendStart) { this.extendStart = extendStart; } /** * Get whether to extend the end of the axis */ public boolean getExtendEnd() { return this.extendEnd; } /** * Set whether to extend the end of the axis */ protected void setExtendEnd(boolean extendEnd) { this.extendEnd = extendEnd; } /** * Get the functions associated with this shader */ public PDFFunction[] getFunctions() { return this.functions; } /** * Set the functions associated with this shader */ protected void setFunctions(PDFFunction[] functions) { this.functions = functions; } /** * A subclass of paint that uses this shader to generate a paint */ class Type2Paint implements Paint { public Type2Paint() { } /** create a paint context */ @Override public PaintContext createContext(ColorModel cm, Rectangle deviceBounds, Rectangle2D userBounds, AffineTransform xform, RenderingHints hints) { ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); ColorModel model = new ComponentColorModel(cs, true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE); Point2D devStart = xform.transform(getAxisStart(), null); Point2D devEnd = xform.transform(getAxisEnd(), null); return new Type2PaintContext(model, devStart, devEnd); } @Override public int getTransparency() { return Transparency.TRANSLUCENT; } } /** * A simple paint context that uses an existing raster in device * space to generate pixels */ class Type2PaintContext implements PaintContext { /** the color model */ private ColorModel colorModel; /** the start of the axis */ private Point2D start; /** the end of the axis */ private Point2D end; private float dt1t0; private double dx1x0, dy1y0, sqdx1x0psqdy1y0; /** * Create a paint context */ Type2PaintContext(ColorModel colorModel, Point2D start, Point2D end) { this.colorModel = colorModel; this.start = start; this.end = end; //pre calculate some often used values dt1t0 = getMaxT() - getMinT(); dx1x0 = end.getX() - start.getX(); dy1y0 = end.getY() - start.getY(); sqdx1x0psqdy1y0 = dx1x0*dx1x0 + dy1y0*dy1y0; } @Override public void dispose() { this.colorModel = null; } @Override public ColorModel getColorModel() { return this.colorModel; } @Override public Raster getRaster(int x, int y, int w, int h) { ColorSpace cs = getColorModel().getColorSpace(); PDFColorSpace shadeCSpace = getColorSpace(); PDFFunction functions[] = getFunctions(); int numComponents = cs.getNumComponents(); float x0 = (float) this.start.getX(); float y0 = (float) this.start.getY(); float[] inputs = new float[1]; float[] outputs = new float[shadeCSpace.getNumComponents()]; float[] outputRBG = new float[numComponents]; // all the data, plus alpha channel int[] data = new int[w * h * (numComponents + 1)]; // for each device coordinate for (int j = 0; j < h; j++) { for (int i = 0; i < w; i += 1) { boolean render = true; // find t for that user coordinate float xp = getXPrime(i + x, j + y, x0, y0); float t = 0; if (xp >= 0 && xp <= 1) t = getMinT() + (dt1t0 * xp); else if (xp < 0 && extendStart) t = getMinT(); else if (xp > 1 && extendEnd) t = getMaxT(); else render = false; if (render) { // calculate the pixel values at t inputs[0] = t; if (functions.length == 1) { functions[0].calculate(inputs, 0, outputs, 0); } else { for (int c = 0; c < functions.length; c++) { functions[c].calculate(inputs, 0, outputs, c); } } if (functions[0].getNumOutputs() != numComponents) { //CMYK outputRBG = shadeCSpace.getColorSpace().toRGB(outputs); } else outputRBG = outputs; int base = (j * w + i) * (numComponents + 1); for (int c = 0; c < numComponents; c++) { data[base + c] = (int) (outputRBG[c] * 255); } data[base + numComponents] = 255; } } } WritableRaster raster = getColorModel().createCompatibleWritableRaster(w, h); raster.setPixels(0, 0, w, h, data); Raster child = raster.createTranslatedChild(x, y); return child; } /** * x' = (x1 - x0) * (x - x0) + (y1 - y0) * (y - y0) * ------------------------------------------- * (x1 - x0)^2 + (y1 - y0)^2 */ private float getXPrime(float x, float y, float x0, float y0) { double tp = ((dx1x0* (x - x0)) + (dy1y0 * (y - y0))) / sqdx1x0psqdy1y0; return (float) tp; } /** * t = t0 + (t1 - t0) x x' */ private float getT(float xp) { if (xp < 0) { return getMinT(); } else if (xp > 1) { return getMaxT(); } else { return getMinT() + (dt1t0 * xp); } } } }