/* * 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.batik.ext.awt; import java.awt.*; 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; /** * Provides the actual implementation for the LinearGradientPaint * This is where the pixel processing is done. * * @author Nicholas Talian, Vincent Hardy, Jim Graham, Jerry Evans * @author <a href="mailto:vincent.hardy@eng.sun.com">Vincent Hardy</a> * @version $Id: LinearGradientPaintContext.java,v 1.1 2008/09/30 20:44:16 patrickc Exp $ * @see java.awt.PaintContext * @see java.awt.Paint * @see java.awt.GradientPaint */ final class LinearGradientPaintContext extends MultipleGradientPaintContext { /** * The following invariants are used to process the gradient value from * a device space coordinate, (X, Y): * g(X, Y) = dgdX*X + dgdY*Y + gc */ private float dgdX, dgdY, gc, pixSz; private static final int DEFAULT_IMPL = 1; private static final int ANTI_ALIAS_IMPL = 3; private int fillMethod; /** * Constructor for LinearGradientPaintContext. * * @param cm {@link ColorModel} that receives * the <code>Paint</code> data. This is used only as a hint. * @param deviceBounds the device space bounding box of the * graphics primitive being rendered * @param userBounds the user space bounding box of the * graphics primitive being rendered * @param t the {@link AffineTransform} from user * space into device space (gradientTransform should be * concatenated with this) * @param hints the hints that the context object uses to choose * between rendering alternatives * @param dStart gradient start point, in user space * @param dEnd gradient end point, in user space * @param fractions the fractions specifying the gradient distribution * @param colors the gradient colors * @param cycleMethod either NO_CYCLE, REFLECT, or REPEAT * @param colorSpace which colorspace to use for interpolation, * either SRGB or LINEAR_RGB */ public LinearGradientPaintContext(ColorModel cm, Rectangle deviceBounds, Rectangle2D userBounds, AffineTransform t, RenderingHints hints, Point2D dStart, Point2D dEnd, float[] fractions, Color[] colors, MultipleGradientPaint.CycleMethodEnum cycleMethod, MultipleGradientPaint.ColorSpaceEnum colorSpace) throws NoninvertibleTransformException { super(cm, deviceBounds, userBounds, t, hints, fractions, colors, cycleMethod, colorSpace); // Use single precision floating points Point2D.Float start = new Point2D.Float((float) dStart.getX(), (float) dStart.getY()); Point2D.Float end = new Point2D.Float((float) dEnd.getX(), (float) dEnd.getY()); // A given point in the raster should take on the same color as its // projection onto the gradient vector. // Thus, we want the projection of the current position vector // onto the gradient vector, then normalized with respect to the // length of the gradient vector, giving a value which can be mapped into // the range 0-1. // projection = currentVector dot gradientVector / length(gradientVector) // normalized = projection / length(gradientVector) float dx = end.x - start.x; // change in x from start to end float dy = end.y - start.y; // change in y from start to end float dSq = dx * dx + dy * dy; // total distance squared //avoid repeated calculations by doing these divides once. float constX = dx / dSq; float constY = dy / dSq; //incremental change along gradient for +x dgdX = a00 * constX + a10 * constY; //incremental change along gradient for +y dgdY = a01 * constX + a11 * constY; float dgdXAbs = Math.abs(dgdX); float dgdYAbs = Math.abs(dgdY); if (dgdXAbs > dgdYAbs) pixSz = dgdXAbs; else pixSz = dgdYAbs; //constant, incorporates the translation components from the matrix gc = (a02 - start.x) * constX + (a12 - start.y) * constY; Object colorRend = hints.get(RenderingHints.KEY_COLOR_RENDERING); Object rend = hints.get(RenderingHints.KEY_RENDERING); fillMethod = DEFAULT_IMPL; if ((cycleMethod == MultipleGradientPaint.REPEAT) || hasDiscontinuity) { if (rend == RenderingHints.VALUE_RENDER_QUALITY) fillMethod = ANTI_ALIAS_IMPL; // ColorRend overrides rend. if (colorRend == RenderingHints.VALUE_COLOR_RENDER_SPEED) fillMethod = DEFAULT_IMPL; else if (colorRend == RenderingHints.VALUE_COLOR_RENDER_QUALITY) fillMethod = ANTI_ALIAS_IMPL; } } protected void fillHardNoCycle(int[] pixels, int off, int adjust, int x, int y, int w, int h) { //constant which can be pulled out of the inner loop final float initConst = (dgdX * x) + gc; for (int i = 0; i < h; i++) { //for every row //initialize current value to be start. float g = initConst + dgdY * (y + i); final int rowLimit = off + w; // end of row iteration if (dgdX == 0) { // System.out.println("In fillHard: " + g); final int val; if (g <= 0) val = gradientUnderflow; else if (g >= 1) val = gradientOverflow; else { // Could be a binary search... int gradIdx = 0; while (gradIdx < gradientsLength - 1) { if (g < fractions[gradIdx + 1]) break; gradIdx++; } float delta = (g - fractions[gradIdx]); float idx = ((delta * GRADIENT_SIZE_INDEX) / normalizedIntervals[gradIdx]) + 0.5f; val = gradients[gradIdx][(int) idx]; } while (off < rowLimit) { pixels[off++] = val; } } else { // System.out.println("In fillHard2: " + g); int gradSteps; int preGradSteps; final int preVal, postVal; float gradStepsF; float preGradStepsF; if (dgdX >= 0) { gradStepsF = ((1 - g) / dgdX); preGradStepsF = (float) Math.ceil((0 - g) / dgdX); preVal = gradientUnderflow; postVal = gradientOverflow; } else { // dgdX < 0 gradStepsF = ((0 - g) / dgdX); preGradStepsF = (float) Math.ceil((1 - g) / dgdX); preVal = gradientOverflow; postVal = gradientUnderflow; } if (gradStepsF > w) gradSteps = w; else gradSteps = (int) gradStepsF; if (preGradStepsF > w) preGradSteps = w; else preGradSteps = (int) preGradStepsF; final int gradLimit = off + gradSteps; if (preGradSteps > 0) { final int preGradLimit = off + preGradSteps; while (off < preGradLimit) { pixels[off++] = preVal; } g += dgdX * preGradSteps; } if (dgdX > 0) { // Could be a binary search... int gradIdx = 0; while (gradIdx < gradientsLength - 1) { if (g < fractions[gradIdx + 1]) break; gradIdx++; } while (off < gradLimit) { float delta = (g - fractions[gradIdx]); final int[] grad = gradients[gradIdx]; double stepsD = Math.ceil ((fractions[gradIdx + 1] - g) / dgdX); int steps; if (stepsD > w) steps = w; else steps = (int) stepsD; int subGradLimit = off + steps; if (subGradLimit > gradLimit) subGradLimit = gradLimit; int idx = (int) (((delta * GRADIENT_SIZE_INDEX) / normalizedIntervals[gradIdx]) * (1 << 16)) + (1 << 15); int step = (int) (((dgdX * GRADIENT_SIZE_INDEX) / normalizedIntervals[gradIdx]) * (1 << 16)); while (off < subGradLimit) { pixels[off++] = grad[idx >> 16]; idx += step; } g += dgdX * stepsD; gradIdx++; } } else { // Could be a binary search... int gradIdx = gradientsLength - 1; while (gradIdx > 0) { if (g > fractions[gradIdx]) break; gradIdx--; } while (off < gradLimit) { float delta = (g - fractions[gradIdx]); final int[] grad = gradients[gradIdx]; double stepsD = Math.ceil(delta / -dgdX); int steps; if (stepsD > w) steps = w; else steps = (int) stepsD; int subGradLimit = off + steps; if (subGradLimit > gradLimit) subGradLimit = gradLimit; int idx = (int) (((delta * GRADIENT_SIZE_INDEX) / normalizedIntervals[gradIdx]) * (1 << 16)) + (1 << 15); int step = (int) (((dgdX * GRADIENT_SIZE_INDEX) / normalizedIntervals[gradIdx]) * (1 << 16)); while (off < subGradLimit) { pixels[off++] = grad[idx >> 16]; idx += step; } g += dgdX * stepsD; gradIdx--; } } while (off < rowLimit) { pixels[off++] = postVal; } } off += adjust; //change in off from row to row } } protected void fillSimpleNoCycle(int[] pixels, int off, int adjust, int x, int y, int w, int h) { //constant which can be pulled out of the inner loop final float initConst = (dgdX * x) + gc; final float step = dgdX * fastGradientArraySize; final int fpStep = (int) (step * (1 << 16)); // fix point step final int[] grad = gradient; for (int i = 0; i < h; i++) { //for every row //initialize current value to be start. float g = initConst + dgdY * (y + i); g *= fastGradientArraySize; g += 0.5; // rounding factor... final int rowLimit = off + w; // end of row iteration float check = dgdX * fastGradientArraySize * w; if (check < 0) check = -check; if (check < .3) { // System.out.println("In fillSimpleNC: " + g); final int val; if (g <= 0) val = gradientUnderflow; else if (g >= fastGradientArraySize) val = gradientOverflow; else val = grad[(int) g]; while (off < rowLimit) { pixels[off++] = val; } } else { // System.out.println("In fillSimpleNC2: " + g); int gradSteps; int preGradSteps; final int preVal, postVal; if (dgdX > 0) { gradSteps = (int) ((fastGradientArraySize - g) / step); preGradSteps = (int) Math.ceil(0 - g / step); preVal = gradientUnderflow; postVal = gradientOverflow; } else { // dgdX < 0 gradSteps = (int) ((0 - g) / step); preGradSteps = (int) Math.ceil((fastGradientArraySize - g) / step); preVal = gradientOverflow; postVal = gradientUnderflow; } if (gradSteps > w) gradSteps = w; final int gradLimit = off + gradSteps; if (preGradSteps > 0) { if (preGradSteps > w) preGradSteps = w; final int preGradLimit = off + preGradSteps; while (off < preGradLimit) { pixels[off++] = preVal; } g += step * preGradSteps; } int fpG = (int) (g * (1 << 16)); while (off < gradLimit) { pixels[off++] = grad[fpG >> 16]; fpG += fpStep; } while (off < rowLimit) { pixels[off++] = postVal; } } off += adjust; //change in off from row to row } } protected void fillSimpleRepeat(int[] pixels, int off, int adjust, int x, int y, int w, int h) { final float initConst = (dgdX * x) + gc; // Limit step to fractional part of // fastGradientArraySize (the non fractional part has // no affect anyways, and would mess up lots of stuff // below). float step = (dgdX - (int) dgdX) * fastGradientArraySize; // Make it a Positive step (a small negative step is // the same as a positive step slightly less than // fastGradientArraySize. if (step < 0) step += fastGradientArraySize; final int[] grad = gradient; for (int i = 0; i < h; i++) { //for every row //initialize current value to be start. float g = initConst + dgdY * (y + i); // now Limited between -1 and 1. g = g - (int) g; // put in the positive side. if (g < 0) g += 1; // scale for gradient array... g *= fastGradientArraySize; g += 0.5; // rounding factor final int rowLimit = off + w; // end of row iteration while (off < rowLimit) { int idx = (int) g; if (idx >= fastGradientArraySize) { g -= fastGradientArraySize; idx -= fastGradientArraySize; } pixels[off++] = grad[idx]; g += step; } off += adjust; //change in off from row to row } } protected void fillSimpleReflect(int[] pixels, int off, int adjust, int x, int y, int w, int h) { final float initConst = (dgdX * x) + gc; final int[] grad = gradient; for (int i = 0; i < h; i++) { //for every row //initialize current value to be start. float g = initConst + dgdY * (y + i); // now limited g to -2<->2 g = g - 2 * ((int) (g / 2.0f)); float step = dgdX; // Pull it into the positive half if (g < 0) { g = -g; //take absolute value step = -step; // Change direction.. } // Now do the same for dgdX. This is safe because // any step that is a multiple of 2.0 has no // affect, hence we can remove it which the first // part does. The second part simply adds 2.0 // (which has no affect due to the cylcle) to move // all negative step values into the positive // side. step = step - 2 * ((int) step / 2.0f); if (step < 0) step += 2.0; final int reflectMax = 2 * fastGradientArraySize; // Scale for gradient array. g *= fastGradientArraySize; g += 0.5; step *= fastGradientArraySize; final int rowLimit = off + w; // end of row iteration while (off < rowLimit) { int idx = (int) g; if (idx >= reflectMax) { g -= reflectMax; idx -= reflectMax; } if (idx <= fastGradientArraySize) pixels[off++] = grad[idx]; else pixels[off++] = grad[reflectMax - idx]; g += step; } off += adjust; //change in off from row to row } } /** * Return a Raster containing the colors generated for the graphics * operation. This is where the area is filled with colors distributed * linearly. * * @param x The x coordinate of the area in device space for which colors * are generated. * @param y The y coordinate of the area in device space for which colors * are generated. * @param w The width of the area in device space for which colors * are generated. * @param h The height of the area in device space for which colors * are generated. */ protected void fillRaster(int[] pixels, int off, int adjust, int x, int y, int w, int h) { //constant which can be pulled out of the inner loop final float initConst = (dgdX * x) + gc; if (fillMethod == ANTI_ALIAS_IMPL) { //initialize current value to be start. for (int i = 0; i < h; i++) { //for every row float g = initConst + dgdY * (y + i); final int rowLimit = off + w; // end of row iteration while (off < rowLimit) { //for every pixel in this row. //get the color pixels[off++] = indexGradientAntiAlias(g, pixSz); g += dgdX; //incremental change in g } off += adjust; //change in off from row to row } } else if (!isSimpleLookup) { if (cycleMethod == MultipleGradientPaint.NO_CYCLE) { fillHardNoCycle(pixels, off, adjust, x, y, w, h); } else { //initialize current value to be start. for (int i = 0; i < h; i++) { //for every row float g = initConst + dgdY * (y + i); final int rowLimit = off + w; // end of row iteration while (off < rowLimit) { //for every pixel in this row. //get the color pixels[off++] = indexIntoGradientsArrays(g); g += dgdX; //incremental change in g } off += adjust; //change in off from row to row } } } else { // Simple implementations: just scale index by array size if (cycleMethod == MultipleGradientPaint.NO_CYCLE) fillSimpleNoCycle(pixels, off, adjust, x, y, w, h); else if (cycleMethod == MultipleGradientPaint.REPEAT) fillSimpleRepeat(pixels, off, adjust, x, y, w, h); else //cycleMethod == MultipleGradientPaint.REFLECT fillSimpleReflect(pixels, off, adjust, x, y, w, h); } } }