| /* |
| * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code 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 General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package sun.java2d.pipe; |
| |
| import java.awt.Color; |
| import java.awt.GradientPaint; |
| import java.awt.LinearGradientPaint; |
| import java.awt.MultipleGradientPaint; |
| import java.awt.MultipleGradientPaint.ColorSpaceType; |
| import java.awt.MultipleGradientPaint.CycleMethod; |
| import java.awt.Paint; |
| import java.awt.RadialGradientPaint; |
| import java.awt.TexturePaint; |
| import java.awt.geom.AffineTransform; |
| import java.awt.geom.Point2D; |
| import java.awt.geom.Rectangle2D; |
| import java.awt.image.AffineTransformOp; |
| import java.awt.image.BufferedImage; |
| import sun.awt.image.PixelConverter; |
| import sun.java2d.SunGraphics2D; |
| import sun.java2d.SurfaceData; |
| import sun.java2d.loops.CompositeType; |
| import sun.java2d.loops.SurfaceType; |
| import static sun.java2d.pipe.BufferedOpCodes.*; |
| |
| public class BufferedPaints { |
| |
| static void setPaint(RenderQueue rq, SunGraphics2D sg2d, |
| Paint paint, int ctxflags) |
| { |
| if (sg2d.paintState <= SunGraphics2D.PAINT_ALPHACOLOR) { |
| setColor(rq, sg2d.pixel); |
| } else { |
| boolean useMask = (ctxflags & BufferedContext.USE_MASK) != 0; |
| switch (sg2d.paintState) { |
| case SunGraphics2D.PAINT_GRADIENT: |
| setGradientPaint(rq, sg2d, |
| (GradientPaint)paint, useMask); |
| break; |
| case SunGraphics2D.PAINT_LIN_GRADIENT: |
| setLinearGradientPaint(rq, sg2d, |
| (LinearGradientPaint)paint, useMask); |
| break; |
| case SunGraphics2D.PAINT_RAD_GRADIENT: |
| setRadialGradientPaint(rq, sg2d, |
| (RadialGradientPaint)paint, useMask); |
| break; |
| case SunGraphics2D.PAINT_TEXTURE: |
| setTexturePaint(rq, sg2d, |
| (TexturePaint)paint, useMask); |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| |
| static void resetPaint(RenderQueue rq) { |
| // assert rq.lock.isHeldByCurrentThread(); |
| rq.ensureCapacity(4); |
| RenderBuffer buf = rq.getBuffer(); |
| buf.putInt(RESET_PAINT); |
| } |
| |
| /****************************** Color support *******************************/ |
| |
| private static void setColor(RenderQueue rq, int pixel) { |
| // assert rq.lock.isHeldByCurrentThread(); |
| rq.ensureCapacity(8); |
| RenderBuffer buf = rq.getBuffer(); |
| buf.putInt(SET_COLOR); |
| buf.putInt(pixel); |
| } |
| |
| /************************* GradientPaint support ****************************/ |
| |
| /** |
| * Note: This code is factored out into a separate static method |
| * so that it can be shared by both the Gradient and LinearGradient |
| * implementations. LinearGradient uses this code (for the |
| * two-color sRGB case only) because it can be much faster than the |
| * equivalent implementation that uses fragment shaders. |
| * |
| * We use OpenGL's texture coordinate generator to automatically |
| * apply a smooth gradient (either cyclic or acyclic) to the geometry |
| * being rendered. This technique is almost identical to the one |
| * described in the comments for BufferedPaints.setTexturePaint(), |
| * except the calculations take place in one dimension instead of two. |
| * Instead of an anchor rectangle in the TexturePaint case, we use |
| * the vector between the two GradientPaint end points in our |
| * calculations. The generator uses a single plane equation that |
| * takes the (x,y) location (in device space) of the fragment being |
| * rendered to calculate a (u) texture coordinate for that fragment: |
| * u = Ax + By + Cz + Dw |
| * |
| * The gradient renderer uses a two-pixel 1D texture where the first |
| * pixel contains the first GradientPaint color, and the second pixel |
| * contains the second GradientPaint color. (Note that we use the |
| * GL_CLAMP_TO_EDGE wrapping mode for acyclic gradients so that we |
| * clamp the colors properly at the extremes.) The following diagram |
| * attempts to show the layout of the texture containing the two |
| * GradientPaint colors (C1 and C2): |
| * |
| * +-----------------+ |
| * | C1 | C2 | |
| * | | | |
| * +-----------------+ |
| * u=0 .25 .5 .75 1 |
| * |
| * We calculate our plane equation constants (A,B,D) such that u=0.25 |
| * corresponds to the first GradientPaint end point in user space and |
| * u=0.75 corresponds to the second end point. This is somewhat |
| * non-obvious, but since the gradient colors are generated by |
| * interpolating between C1 and C2, we want the pure color at the |
| * end points, and we will get the pure color only when u correlates |
| * to the center of a texel. The following chart shows the expected |
| * color for some sample values of u (where C' is the color halfway |
| * between C1 and C2): |
| * |
| * u value acyclic (GL_CLAMP) cyclic (GL_REPEAT) |
| * ------- ------------------ ------------------ |
| * -0.25 C1 C2 |
| * 0.0 C1 C' |
| * 0.25 C1 C1 |
| * 0.5 C' C' |
| * 0.75 C2 C2 |
| * 1.0 C2 C' |
| * 1.25 C2 C1 |
| * |
| * Original inspiration for this technique came from UMD's Agile2D |
| * project (GradientManager.java). |
| */ |
| private static void setGradientPaint(RenderQueue rq, AffineTransform at, |
| Color c1, Color c2, |
| Point2D pt1, Point2D pt2, |
| boolean isCyclic, boolean useMask) |
| { |
| // convert gradient colors to IntArgbPre format |
| PixelConverter pc = PixelConverter.ArgbPre.instance; |
| int pixel1 = pc.rgbToPixel(c1.getRGB(), null); |
| int pixel2 = pc.rgbToPixel(c2.getRGB(), null); |
| |
| // calculate plane equation constants |
| double x = pt1.getX(); |
| double y = pt1.getY(); |
| at.translate(x, y); |
| // now gradient point 1 is at the origin |
| x = pt2.getX() - x; |
| y = pt2.getY() - y; |
| double len = Math.sqrt(x * x + y * y); |
| at.rotate(x, y); |
| // now gradient point 2 is on the positive x-axis |
| at.scale(2*len, 1); |
| // now gradient point 2 is at (0.5, 0) |
| at.translate(-0.25, 0); |
| // now gradient point 1 is at (0.25, 0), point 2 is at (0.75, 0) |
| |
| double p0, p1, p3; |
| try { |
| at.invert(); |
| p0 = at.getScaleX(); |
| p1 = at.getShearX(); |
| p3 = at.getTranslateX(); |
| } catch (java.awt.geom.NoninvertibleTransformException e) { |
| p0 = p1 = p3 = 0.0; |
| } |
| |
| // assert rq.lock.isHeldByCurrentThread(); |
| rq.ensureCapacityAndAlignment(44, 12); |
| RenderBuffer buf = rq.getBuffer(); |
| buf.putInt(SET_GRADIENT_PAINT); |
| buf.putInt(useMask ? 1 : 0); |
| buf.putInt(isCyclic ? 1 : 0); |
| buf.putDouble(p0).putDouble(p1).putDouble(p3); |
| buf.putInt(pixel1).putInt(pixel2); |
| } |
| |
| private static void setGradientPaint(RenderQueue rq, |
| SunGraphics2D sg2d, |
| GradientPaint paint, |
| boolean useMask) |
| { |
| setGradientPaint(rq, (AffineTransform)sg2d.transform.clone(), |
| paint.getColor1(), paint.getColor2(), |
| paint.getPoint1(), paint.getPoint2(), |
| paint.isCyclic(), useMask); |
| } |
| |
| /************************** TexturePaint support ****************************/ |
| |
| /** |
| * We use OpenGL's texture coordinate generator to automatically |
| * map the TexturePaint image to the geometry being rendered. The |
| * generator uses two separate plane equations that take the (x,y) |
| * location (in device space) of the fragment being rendered to |
| * calculate (u,v) texture coordinates for that fragment: |
| * u = Ax + By + Cz + Dw |
| * v = Ex + Fy + Gz + Hw |
| * |
| * Since we use a 2D orthographic projection, we can assume that z=0 |
| * and w=1 for any fragment. So we need to calculate appropriate |
| * values for the plane equation constants (A,B,D) and (E,F,H) such |
| * that {u,v}=0 for the top-left of the TexturePaint's anchor |
| * rectangle and {u,v}=1 for the bottom-right of the anchor rectangle. |
| * We can easily make the texture image repeat for {u,v} values |
| * outside the range [0,1] by specifying the GL_REPEAT texture wrap |
| * mode. |
| * |
| * Calculating the plane equation constants is surprisingly simple. |
| * We can think of it as an inverse matrix operation that takes |
| * device space coordinates and transforms them into user space |
| * coordinates that correspond to a location relative to the anchor |
| * rectangle. First, we translate and scale the current user space |
| * transform by applying the anchor rectangle bounds. We then take |
| * the inverse of this affine transform. The rows of the resulting |
| * inverse matrix correlate nicely to the plane equation constants |
| * we were seeking. |
| */ |
| private static void setTexturePaint(RenderQueue rq, |
| SunGraphics2D sg2d, |
| TexturePaint paint, |
| boolean useMask) |
| { |
| BufferedImage bi = paint.getImage(); |
| SurfaceData dstData = sg2d.surfaceData; |
| SurfaceData srcData = |
| dstData.getSourceSurfaceData(bi, sg2d.TRANSFORM_ISIDENT, |
| CompositeType.SrcOver, null); |
| boolean filter = |
| (sg2d.interpolationType != |
| AffineTransformOp.TYPE_NEAREST_NEIGHBOR); |
| |
| // calculate plane equation constants |
| AffineTransform at = (AffineTransform)sg2d.transform.clone(); |
| Rectangle2D anchor = paint.getAnchorRect(); |
| at.translate(anchor.getX(), anchor.getY()); |
| at.scale(anchor.getWidth(), anchor.getHeight()); |
| |
| double xp0, xp1, xp3, yp0, yp1, yp3; |
| try { |
| at.invert(); |
| xp0 = at.getScaleX(); |
| xp1 = at.getShearX(); |
| xp3 = at.getTranslateX(); |
| yp0 = at.getShearY(); |
| yp1 = at.getScaleY(); |
| yp3 = at.getTranslateY(); |
| } catch (java.awt.geom.NoninvertibleTransformException e) { |
| xp0 = xp1 = xp3 = yp0 = yp1 = yp3 = 0.0; |
| } |
| |
| // assert rq.lock.isHeldByCurrentThread(); |
| rq.ensureCapacityAndAlignment(68, 12); |
| RenderBuffer buf = rq.getBuffer(); |
| buf.putInt(SET_TEXTURE_PAINT); |
| buf.putInt(useMask ? 1 : 0); |
| buf.putInt(filter ? 1 : 0); |
| buf.putLong(srcData.getNativeOps()); |
| buf.putDouble(xp0).putDouble(xp1).putDouble(xp3); |
| buf.putDouble(yp0).putDouble(yp1).putDouble(yp3); |
| } |
| |
| /****************** Shared MultipleGradientPaint support ********************/ |
| |
| /** |
| * The maximum number of gradient "stops" supported by our native |
| * fragment shader implementations. |
| * |
| * This value has been empirically determined and capped to allow |
| * our native shaders to run on all shader-level graphics hardware, |
| * even on the older, more limited GPUs. Even the oldest Nvidia |
| * hardware could handle 16, or even 32 fractions without any problem. |
| * But the first-generation boards from ATI would fall back into |
| * software mode (which is unusably slow) for values larger than 12; |
| * it appears that those boards do not have enough native registers |
| * to support the number of array accesses required by our gradient |
| * shaders. So for now we will cap this value at 12, but we can |
| * re-evaluate this in the future as hardware becomes more capable. |
| */ |
| public static final int MULTI_MAX_FRACTIONS = 12; |
| |
| /** |
| * Helper function to convert a color component in sRGB space to |
| * linear RGB space. Copied directly from the |
| * MultipleGradientPaintContext class. |
| */ |
| public static int convertSRGBtoLinearRGB(int color) { |
| float input, output; |
| |
| input = color / 255.0f; |
| if (input <= 0.04045f) { |
| output = input / 12.92f; |
| } else { |
| output = (float)Math.pow((input + 0.055) / 1.055, 2.4); |
| } |
| |
| return Math.round(output * 255.0f); |
| } |
| |
| /** |
| * Helper function to convert a (non-premultiplied) Color in sRGB |
| * space to an IntArgbPre pixel value, optionally in linear RGB space. |
| * Based on the PixelConverter.ArgbPre.rgbToPixel() method. |
| */ |
| private static int colorToIntArgbPrePixel(Color c, boolean linear) { |
| int rgb = c.getRGB(); |
| if (!linear && ((rgb >> 24) == -1)) { |
| return rgb; |
| } |
| int a = rgb >>> 24; |
| int r = (rgb >> 16) & 0xff; |
| int g = (rgb >> 8) & 0xff; |
| int b = (rgb ) & 0xff; |
| if (linear) { |
| r = convertSRGBtoLinearRGB(r); |
| g = convertSRGBtoLinearRGB(g); |
| b = convertSRGBtoLinearRGB(b); |
| } |
| int a2 = a + (a >> 7); |
| r = (r * a2) >> 8; |
| g = (g * a2) >> 8; |
| b = (b * a2) >> 8; |
| return ((a << 24) | (r << 16) | (g << 8) | (b)); |
| } |
| |
| /** |
| * Converts the given array of Color objects into an int array |
| * containing IntArgbPre pixel values. If the linear parameter |
| * is true, the Color values will be converted into a linear RGB |
| * color space before being returned. |
| */ |
| private static int[] convertToIntArgbPrePixels(Color[] colors, |
| boolean linear) |
| { |
| int[] pixels = new int[colors.length]; |
| for (int i = 0; i < colors.length; i++) { |
| pixels[i] = colorToIntArgbPrePixel(colors[i], linear); |
| } |
| return pixels; |
| } |
| |
| /********************** LinearGradientPaint support *************************/ |
| |
| /** |
| * This method uses techniques that are nearly identical to those |
| * employed in setGradientPaint() above. The primary difference |
| * is that at the native level we use a fragment shader to manually |
| * apply the plane equation constants to the current fragment position |
| * to calculate the gradient position in the range [0,1] (the native |
| * code for GradientPaint does the same, except that it uses OpenGL's |
| * automatic texture coordinate generation facilities). |
| * |
| * One other minor difference worth mentioning is that |
| * setGradientPaint() calculates the plane equation constants |
| * such that the gradient end points are positioned at 0.25 and 0.75 |
| * (for reasons discussed in the comments for that method). In |
| * contrast, for LinearGradientPaint we setup the equation constants |
| * such that the gradient end points fall at 0.0 and 1.0. The |
| * reason for this difference is that in the fragment shader we |
| * have more control over how the gradient values are interpreted |
| * (depending on the paint's CycleMethod). |
| */ |
| private static void setLinearGradientPaint(RenderQueue rq, |
| SunGraphics2D sg2d, |
| LinearGradientPaint paint, |
| boolean useMask) |
| { |
| boolean linear = |
| (paint.getColorSpace() == ColorSpaceType.LINEAR_RGB); |
| Color[] colors = paint.getColors(); |
| int numStops = colors.length; |
| Point2D pt1 = paint.getStartPoint(); |
| Point2D pt2 = paint.getEndPoint(); |
| AffineTransform at = paint.getTransform(); |
| at.preConcatenate(sg2d.transform); |
| |
| if (!linear && numStops == 2 && |
| paint.getCycleMethod() != CycleMethod.REPEAT) |
| { |
| // delegate to the optimized two-color gradient codepath |
| boolean isCyclic = |
| (paint.getCycleMethod() != CycleMethod.NO_CYCLE); |
| setGradientPaint(rq, at, |
| colors[0], colors[1], |
| pt1, pt2, |
| isCyclic, useMask); |
| return; |
| } |
| |
| int cycleMethod = paint.getCycleMethod().ordinal(); |
| float[] fractions = paint.getFractions(); |
| int[] pixels = convertToIntArgbPrePixels(colors, linear); |
| |
| // calculate plane equation constants |
| double x = pt1.getX(); |
| double y = pt1.getY(); |
| at.translate(x, y); |
| // now gradient point 1 is at the origin |
| x = pt2.getX() - x; |
| y = pt2.getY() - y; |
| double len = Math.sqrt(x * x + y * y); |
| at.rotate(x, y); |
| // now gradient point 2 is on the positive x-axis |
| at.scale(len, 1); |
| // now gradient point 1 is at (0.0, 0), point 2 is at (1.0, 0) |
| |
| float p0, p1, p3; |
| try { |
| at.invert(); |
| p0 = (float)at.getScaleX(); |
| p1 = (float)at.getShearX(); |
| p3 = (float)at.getTranslateX(); |
| } catch (java.awt.geom.NoninvertibleTransformException e) { |
| p0 = p1 = p3 = 0.0f; |
| } |
| |
| // assert rq.lock.isHeldByCurrentThread(); |
| rq.ensureCapacity(20 + 12 + (numStops*4*2)); |
| RenderBuffer buf = rq.getBuffer(); |
| buf.putInt(SET_LINEAR_GRADIENT_PAINT); |
| buf.putInt(useMask ? 1 : 0); |
| buf.putInt(linear ? 1 : 0); |
| buf.putInt(cycleMethod); |
| buf.putInt(numStops); |
| buf.putFloat(p0); |
| buf.putFloat(p1); |
| buf.putFloat(p3); |
| buf.put(fractions); |
| buf.put(pixels); |
| } |
| |
| /********************** RadialGradientPaint support *************************/ |
| |
| /** |
| * This method calculates six m** values and a focusX value that |
| * are used by the native fragment shader. These techniques are |
| * based on a whitepaper by Daniel Rice on radial gradient performance |
| * (attached to the bug report for 6521533). One can refer to that |
| * document for the complete set of formulas and calculations, but |
| * the basic goal is to compose a transform that will convert an |
| * (x,y) position in device space into a "u" value that represents |
| * the relative distance to the gradient focus point. The resulting |
| * value can be used to look up the appropriate color by linearly |
| * interpolating between the two nearest colors in the gradient. |
| */ |
| private static void setRadialGradientPaint(RenderQueue rq, |
| SunGraphics2D sg2d, |
| RadialGradientPaint paint, |
| boolean useMask) |
| { |
| boolean linear = |
| (paint.getColorSpace() == ColorSpaceType.LINEAR_RGB); |
| int cycleMethod = paint.getCycleMethod().ordinal(); |
| float[] fractions = paint.getFractions(); |
| Color[] colors = paint.getColors(); |
| int numStops = colors.length; |
| int[] pixels = convertToIntArgbPrePixels(colors, linear); |
| Point2D center = paint.getCenterPoint(); |
| Point2D focus = paint.getFocusPoint(); |
| float radius = paint.getRadius(); |
| |
| // save original (untransformed) center and focus points |
| double cx = center.getX(); |
| double cy = center.getY(); |
| double fx = focus.getX(); |
| double fy = focus.getY(); |
| |
| // transform from gradient coords to device coords |
| AffineTransform at = paint.getTransform(); |
| at.preConcatenate(sg2d.transform); |
| focus = at.transform(focus, focus); |
| |
| // transform unit circle to gradient coords; we start with the |
| // unit circle (center=(0,0), focus on positive x-axis, radius=1) |
| // and then transform into gradient space |
| at.translate(cx, cy); |
| at.rotate(fx - cx, fy - cy); |
| at.scale(radius, radius); |
| |
| // invert to get mapping from device coords to unit circle |
| try { |
| at.invert(); |
| } catch (Exception e) { |
| at.setToScale(0.0, 0.0); |
| } |
| focus = at.transform(focus, focus); |
| |
| // clamp the focus point so that it does not rest on, or outside |
| // of, the circumference of the gradient circle |
| fx = Math.min(focus.getX(), 0.99); |
| |
| // assert rq.lock.isHeldByCurrentThread(); |
| rq.ensureCapacity(20 + 28 + (numStops*4*2)); |
| RenderBuffer buf = rq.getBuffer(); |
| buf.putInt(SET_RADIAL_GRADIENT_PAINT); |
| buf.putInt(useMask ? 1 : 0); |
| buf.putInt(linear ? 1 : 0); |
| buf.putInt(numStops); |
| buf.putInt(cycleMethod); |
| buf.putFloat((float)at.getScaleX()); |
| buf.putFloat((float)at.getShearX()); |
| buf.putFloat((float)at.getTranslateX()); |
| buf.putFloat((float)at.getShearY()); |
| buf.putFloat((float)at.getScaleY()); |
| buf.putFloat((float)at.getTranslateY()); |
| buf.putFloat((float)fx); |
| buf.put(fractions); |
| buf.put(pixels); |
| } |
| } |