533 lines · 22.1 KB · Java
RawDownload
/* * 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);

Lines 1–500 of 533.