/* * 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.NoninvertibleTransformException; 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.BaseWatchable; 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 radial shader based on a function. */ public class ShaderType3 extends PDFShader { /** the center of the first circle */ private Point2D center1; /** the center of the second circle */ private Point2D center2; /** the radius of the first circle */ private float radius1; /** the radius of the second circle */ private float radius2; /** 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 ShaderType3() { super(3); } /** * 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(); center1 = new Point2D.Float(coords[0].getFloatValue(), coords[1].getFloatValue()); center2 = new Point2D.Float(coords[3].getFloatValue(), coords[4].getFloatValue()); radius1 = coords[2].getFloatValue(); radius2 = coords[5].getFloatValue(); // 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 Type3Paint()); } /** * 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 Type3Paint implements Paint { public Type3Paint() { } /** 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); return new Type3PaintContext(model, xform); } @Override public int getTransparency() { return Transparency.TRANSLUCENT; } } /** * A simple paint context that uses an existing raster in device * space to generate pixels */ class Type3PaintContext implements PaintContext { /** the color model */ private ColorModel colorModel; /** the transformation */ private AffineTransform invXform; private final double dx1x0, dy1y0, dr1r0, sqr0, denom; /** * Create a paint context */ Type3PaintContext(ColorModel colorModel, AffineTransform xform) { this.colorModel = colorModel; //Precalculate some often needed values; dx1x0 = center2.getX() - center1.getX(); dy1y0 = center2.getY() - center1.getY(); dr1r0 = radius2 - radius1; sqr0 = radius1*radius1; denom = dx1x0*dx1x0 + dy1y0*dy1y0 - dr1r0*dr1r0; try { this.invXform = xform.createInverse(); } catch (NoninvertibleTransformException e) { BaseWatchable.getErrorHandler().publishException(e); } } @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[] c1 = new float[2]; 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)]; float lastInput = Float.POSITIVE_INFINITY; final float tol = TOLERANCE * (getMaxT() - getMinT()); final int advance = 1; // for each device coordinate for (int j = 0; j < h; j++) { for (int i = 0; i < w; i += advance) { //Get point in user space invXform.transform(new float[]{x + i, y + j}, 0, c1, 0, 1); boolean render = true; float[] s = calculateInputValues(c1[0], c1[1]); //s[0] <= s[1] holds //if (s[0] >= 0 && s[1] <= 1) s[1] = s[1]; if (s[1] >= 0 && s[1] <= 1) s[1] = s[1]; else if (extendEnd == true && s[1] >= 0 && radius1 + s[1]*dr1r0 >= 0) { s[1] = s[1]; } else if (s[0] >= 0 && s[0] <= 1) s[1] = s[0]; else if (extendStart == true && s[1] <= 0 && radius1 + s[1]*dr1r0 >= 0) { s[1] = s[1]; } else if (extendStart == true && s[0] <= 1 && radius1 + s[0]*dr1r0 >= 0) { s[1] = s[0]; } else render = false; if (render) { float t = (getMinT() + s[1]*(getMaxT() - getMinT())); // calculate the pixel values at t inputs[0] = t; if (Math.abs(lastInput - t) > tol) { 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 (!shadeCSpace.getColorSpace().isCS_sRGB()) { //Can be quite slow outputRBG = shadeCSpace.getColorSpace().toRGB(outputs); } else outputRBG = outputs; lastInput = t; } 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; } /** * From Adobe Technical Note #5600: * * Given a geometric coordinate position (x, y) in or along the gradient gradient fill, * the corresponding value of s can be determined by solving the quadratic * constraint equation: * * [x - xc(s)]2 + [y - yc(s)]2 = [r(s)]2 * * The following code calculates the 2 possible values of s. * * @return Two possible values of s with s[0] <= s[1] */ private float[] calculateInputValues(float x, float y) { double p = -(x - center1.getX())*dx1x0 -(y - center1.getY())*dy1y0 - radius1*dr1r0; double q = (Math.pow(x - center1.getX(), 2) + Math.pow(y - center1.getY(), 2) - sqr0); double root = Math.sqrt(p*p - denom*q); float root1 = (float) ((-p + root)/denom); float root2 = (float) ((-p - root)/denom); if (denom < 0) return new float[]{root1, root2}; else return new float[]{root2, root1}; } } }