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