| /* |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You 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.apache.batik.ext.awt; |
| |
| import java.awt.Color; |
| import java.awt.Rectangle; |
| import java.awt.RenderingHints; |
| 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$ |
| * @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); |
| } |
| } |
| } |