/* * Copyright 2006-2017 ICEsoft Technologies Canada Corp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package org.icepdf.core.pobjects.graphics; import org.icepdf.core.pobjects.functions.Function; import org.icepdf.core.pobjects.graphics.batik.ext.awt.LinearGradientPaint; import org.icepdf.core.pobjects.graphics.batik.ext.awt.MultipleGradientPaint; import org.icepdf.core.util.Library; import java.awt.*; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.logging.Logger; /** * <p>Type 2 (axial) shadings define colour blend that varies along a linear * axis between two endpoints and extends indefinitely perpendicular to the * that axis.</p> * * @author ICEsoft Technologies Inc. * @since 2.7 */ public class ShadingType2Pattern extends ShadingPattern { private static final Logger logger = Logger.getLogger(ShadingType2Pattern.class.toString()); // An array of two numbers [t0, t1] specifying the limiting values of a // parametric variable t. The variable is considered to vary linearly between // these two values as the colour gradient varies between the starting and // ending points of the axis. The variable t becomes the argument to the // colour function(s). Default [0,1]. protected List<Number> domain; // An array of four numbers [x0, y0, x1, y1] specifying the starting and // ending coordinates of the axis, expressed in the shading's target // coordinate space. protected java.util.List coords; // An array of two Boolean values specifying whether to extend the shading // beyond the starting and ending points of the axis, Default [false, false]. protected List<Boolean> extend; // linear gradient paint describing the gradient. private LinearGradientPaint linearGradientPaint; public ShadingType2Pattern(Library library, HashMap entries) { super(library, entries); } @SuppressWarnings("unchecked") public synchronized void init(GraphicsState graphicsState) { if (inited) { return; } // shadingDictionary dictionary if (shadingDictionary == null) { shadingDictionary = library.getDictionary(entries, SHADING_KEY); } shadingType = library.getInt(shadingDictionary, SHADING_TYPE_KEY); bBox = library.getRectangle(shadingDictionary, BBOX_KEY); colorSpace = PColorSpace.getColorSpace(library, library.getObject(shadingDictionary, COLORSPACE_KEY)); Object tmp = library.getObject(shadingDictionary, BACKGROUND_KEY); if (tmp != null && tmp instanceof List) { background = (java.util.List) tmp; } antiAlias = library.getBoolean(shadingDictionary, ANTIALIAS_KEY); // get type 2 specific data. tmp = library.getObject(shadingDictionary, DOMAIN_KEY); if (tmp instanceof List) { domain = (List<Number>) tmp; } else { domain = new ArrayList<Number>(2); domain.add(0.0f); domain.add(1.0f); } tmp = library.getObject(shadingDictionary, COORDS_KEY); if (tmp instanceof List) { coords = (java.util.List) tmp; } tmp = library.getObject(shadingDictionary, EXTEND_KEY); if (tmp instanceof List) { extend = (List<Boolean>) tmp; } else { extend = new ArrayList<Boolean>(2); extend.add(false); extend.add(false); } tmp = library.getObject(shadingDictionary, FUNCTION_KEY); if (tmp != null) { if (!(tmp instanceof List)) { function = new Function[]{Function.getFunction(library, tmp)}; } else { List functionTemp = (List) tmp; function = new Function[functionTemp.size()]; for (int i = 0; i < functionTemp.size(); i++) { function[i] = Function.getFunction(library, functionTemp.get(i)); } } } // calculate the t's float t0 = domain.get(0).floatValue(); float t1 = domain.get(1).floatValue(); // first off, create the two needed start and end points of the line Point2D.Float startPoint = new Point2D.Float( ((Number) coords.get(0)).floatValue(), ((Number) coords.get(1)).floatValue()); Point2D.Float endPoint = new Point2D.Float( ((Number) coords.get(2)).floatValue(), ((Number) coords.get(3)).floatValue()); // corner case where a pdf engine give zero zero coords which batik // can't handle so we pad it slightly. if (startPoint.equals(endPoint)) { endPoint.x++; } // calculate colour based on points that make up the line, 10 is a good // number for speed and gradient quality. try { int numberOfPoints = 10; Color[] colors = calculateColorPoints(numberOfPoints, startPoint, endPoint, t0, t1); float[] dist = calculateDomainEntries(numberOfPoints, t0, t1); linearGradientPaint = new LinearGradientPaint( startPoint, endPoint, dist, colors, MultipleGradientPaint.NO_CYCLE, MultipleGradientPaint.LINEAR_RGB, matrix); inited = true; } catch (Exception e) { logger.finer("Failed ot initialize gradient paint type 2."); } } /** * Calculates x number of points on long the line defined by the start and * end point. * * @param numberOfPoints number of points to generate. * @param startPoint start of line segment. * @param endPoint end of line segment. * @return list of points found on line */ protected Color[] calculateColorPoints(int numberOfPoints, Point2D.Float startPoint, Point2D.Float endPoint, float t0, float t1) { // calculate the slope float m = (startPoint.y - endPoint.y) / (startPoint.x - endPoint.x); // calculate the y intercept float b = startPoint.y - (m * startPoint.x); // let calculate x points between startPoint.x and startPoint.y that // are on the line using y = mx + b. Color[] color; // if we don't have a y-axis line we can uses y=mx + b to get our points. if (!Float.isInfinite(m)) { float xDiff = (endPoint.x - startPoint.x) / numberOfPoints; float xOffset = startPoint.x; color = new Color[numberOfPoints + 1]; Point2D.Float point; for (int i = 0, max = color.length; i < max; i++) { point = new Point2D.Float(xOffset, (m * xOffset) + b); color[i] = calculateColour(colorSpace, point, startPoint, endPoint, t0, t1); xOffset += xDiff; } } // otherwise we have a infinite m and can just pick y values else { float yDiff = (endPoint.y - startPoint.y) / numberOfPoints; float yOffset = startPoint.y; color = new Color[numberOfPoints + 1]; Point2D.Float point; for (int i = 0, max = color.length; i < max; i++) { point = new Point2D.Float(0, yOffset); color[i] = calculateColour(colorSpace, point, startPoint, endPoint, t0, t1); yOffset += yDiff; } } return color; } /** * Calculate domain entries givent the number of point between t0 and t1 * * @param numberOfPoints number of points to calculate * @param t0 lower limit * @param t1 upper limit * @return array of floats the evenly divide t0 and t1, length is * numberOfPoints + 1 */ protected float[] calculateDomainEntries(int numberOfPoints, float t0, float t1) { float offset = 1.0f / numberOfPoints; float[] domainEntries = new float[numberOfPoints + 1]; domainEntries[0] = t0; for (int i = 1, max = domainEntries.length; i < max; i++) { domainEntries[i] = domainEntries[i - 1] + offset; } domainEntries[domainEntries.length - 1] = t1; return domainEntries; } /** * Calculate the colours value of the point xy on the line point1 and point2. * * @param colorSpace colour space to apply to the function output * @param xy point to calcualte the colour of. * @param point1 start of gradient line * @param point2 end of gradient line. * @param t0 domain min * @param t1 domain max * @return colour derived from the input parameters. */ private Color calculateColour(PColorSpace colorSpace, Point2D.Float xy, Point2D.Float point1, Point2D.Float point2, float t0, float t1) { // find colour at point 1 float xPrime = linearMapping(xy, point1, point2); float t = parametrixValue(xPrime, t0, t1, extend); // find colour at point 2 float[] input = new float[1]; input[0] = t; // apply the function to the given input if (function != null) { float[] output = calculateValues(input); if (output != null) { output = PColorSpace.reverse(output); return colorSpace.getColor(output, true); } else { return null; } } else { logger.fine("Error processing Shading Type 2 Pattern."); return null; } } /** * Colour blend function to be applied to a point on the line with endpoints * point1 and point1 for a given point x,y. * * @param xy point to linearize. * @param point1 end point of line * @param point2 end poitn of line. * @return linearized x' value. */ private float linearMapping(Point2D.Float xy, Point2D.Float point1, Point2D.Float point2) { float x = xy.x; float y = xy.y; float x0 = point1.x; float y0 = point1.y; float x1 = point2.x; float y1 = point2.y; float top = (((x1 - x0) * (x - x0)) + ((y1 - y0) * (y - y0))); float bottom = (((x1 - x0) * (x1 - x0)) + ((y1 - y0) * (y1 - y0))); // have a couple corner cases where 1.00000046 isn't actually 1.0 // so I'm going to tweak the calculation to have 3 decimals. int map = (int) ((top / bottom) * 100); return map / 100.0f; } /** * Parametric variable t calculation as defined in Section 4.6, Type 2 * (axial) shadings. * * @param linearMapping linear mapping of some point x' * @param t0 domain of axial shading, limit 1 * @param t1 domain of axial shading, limit 2 * @param extended 2 element vector, indicating line extension along domain * @return parametric value. */ private float parametrixValue(float linearMapping, float t0, float t1, List extended) { if (linearMapping < 0 && ((Boolean) extended.get(0))) { return t0; } else if (linearMapping > 1 && ((Boolean) extended.get(1))) { return t1; } else { return t0 + ((t1 - t0) * linearMapping); } } public Paint getPaint() { try { init(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); logger.fine("ShadingType2Pattern initialization interrupted"); } return linearGradientPaint; } public String toString() { return super.toString() + "\n domain: " + domain + "\n coords: " + coords + "\n extend: " + extend + "\n function: " + function; } }