1 /*
   2  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.java2d.pipe;
  27 
  28 import java.awt.Color;
  29 import java.awt.GradientPaint;
  30 import java.awt.LinearGradientPaint;
  31 import java.awt.MultipleGradientPaint;
  32 import java.awt.MultipleGradientPaint.ColorSpaceType;
  33 import java.awt.MultipleGradientPaint.CycleMethod;
  34 import java.awt.Paint;
  35 import java.awt.RadialGradientPaint;
  36 import java.awt.TexturePaint;
  37 import java.awt.geom.AffineTransform;
  38 import java.awt.geom.Point2D;
  39 import java.awt.geom.Rectangle2D;
  40 import java.awt.image.AffineTransformOp;
  41 import java.awt.image.BufferedImage;
  42 import sun.awt.image.PixelConverter;
  43 import sun.java2d.SunGraphics2D;
  44 import sun.java2d.SurfaceData;
  45 import sun.java2d.loops.CompositeType;
  46 import sun.java2d.loops.SurfaceType;
  47 import static sun.java2d.pipe.BufferedOpCodes.*;
  48 
  49 public class BufferedPaints {
  50 
  51     static void setPaint(RenderQueue rq, SunGraphics2D sg2d,
  52                          Paint paint, int ctxflags)
  53     {
  54         if (sg2d.paintState <= SunGraphics2D.PAINT_ALPHACOLOR) {
  55             setColor(rq, sg2d.pixel);
  56         } else {
  57             boolean useMask = (ctxflags & BufferedContext.USE_MASK) != 0;
  58             switch (sg2d.paintState) {
  59             case SunGraphics2D.PAINT_GRADIENT:
  60                 setGradientPaint(rq, sg2d,
  61                                  (GradientPaint)paint, useMask);
  62                 break;
  63             case SunGraphics2D.PAINT_LIN_GRADIENT:
  64                 setLinearGradientPaint(rq, sg2d,
  65                                        (LinearGradientPaint)paint, useMask);
  66                 break;
  67             case SunGraphics2D.PAINT_RAD_GRADIENT:
  68                 setRadialGradientPaint(rq, sg2d,
  69                                        (RadialGradientPaint)paint, useMask);
  70                 break;
  71             case SunGraphics2D.PAINT_TEXTURE:
  72                 setTexturePaint(rq, sg2d,
  73                                 (TexturePaint)paint, useMask);
  74                 break;
  75             default:
  76                 break;
  77             }
  78         }
  79     }
  80 
  81     static void resetPaint(RenderQueue rq) {
  82         // assert rq.lock.isHeldByCurrentThread();
  83         rq.ensureCapacity(4);
  84         RenderBuffer buf = rq.getBuffer();
  85         buf.putInt(RESET_PAINT);
  86     }
  87 
  88 /****************************** Color support *******************************/
  89 
  90     private static void setColor(RenderQueue rq, int pixel) {
  91         // assert rq.lock.isHeldByCurrentThread();
  92         rq.ensureCapacity(8);
  93         RenderBuffer buf = rq.getBuffer();
  94         buf.putInt(SET_COLOR);
  95         buf.putInt(pixel);
  96     }
  97 
  98 /************************* GradientPaint support ****************************/
  99 
 100     /**
 101      * Note: This code is factored out into a separate static method
 102      * so that it can be shared by both the Gradient and LinearGradient
 103      * implementations.  LinearGradient uses this code (for the
 104      * two-color sRGB case only) because it can be much faster than the
 105      * equivalent implementation that uses fragment shaders.
 106      *
 107      * We use OpenGL's texture coordinate generator to automatically
 108      * apply a smooth gradient (either cyclic or acyclic) to the geometry
 109      * being rendered.  This technique is almost identical to the one
 110      * described in the comments for BufferedPaints.setTexturePaint(),
 111      * except the calculations take place in one dimension instead of two.
 112      * Instead of an anchor rectangle in the TexturePaint case, we use
 113      * the vector between the two GradientPaint end points in our
 114      * calculations.  The generator uses a single plane equation that
 115      * takes the (x,y) location (in device space) of the fragment being
 116      * rendered to calculate a (u) texture coordinate for that fragment:
 117      *     u = Ax + By + Cz + Dw
 118      *
 119      * The gradient renderer uses a two-pixel 1D texture where the first
 120      * pixel contains the first GradientPaint color, and the second pixel
 121      * contains the second GradientPaint color.  (Note that we use the
 122      * GL_CLAMP_TO_EDGE wrapping mode for acyclic gradients so that we
 123      * clamp the colors properly at the extremes.)  The following diagram
 124      * attempts to show the layout of the texture containing the two
 125      * GradientPaint colors (C1 and C2):
 126      *
 127      *                        +-----------------+
 128      *                        |   C1   |   C2   |
 129      *                        |        |        |
 130      *                        +-----------------+
 131      *                      u=0  .25  .5   .75  1
 132      *
 133      * We calculate our plane equation constants (A,B,D) such that u=0.25
 134      * corresponds to the first GradientPaint end point in user space and
 135      * u=0.75 corresponds to the second end point.  This is somewhat
 136      * non-obvious, but since the gradient colors are generated by
 137      * interpolating between C1 and C2, we want the pure color at the
 138      * end points, and we will get the pure color only when u correlates
 139      * to the center of a texel.  The following chart shows the expected
 140      * color for some sample values of u (where C' is the color halfway
 141      * between C1 and C2):
 142      *
 143      *       u value      acyclic (GL_CLAMP)      cyclic (GL_REPEAT)
 144      *       -------      ------------------      ------------------
 145      *        -0.25              C1                       C2
 146      *         0.0               C1                       C'
 147      *         0.25              C1                       C1
 148      *         0.5               C'                       C'
 149      *         0.75              C2                       C2
 150      *         1.0               C2                       C'
 151      *         1.25              C2                       C1
 152      *
 153      * Original inspiration for this technique came from UMD's Agile2D
 154      * project (GradientManager.java).
 155      */
 156     private static void setGradientPaint(RenderQueue rq, AffineTransform at,
 157                                          Color c1, Color c2,
 158                                          Point2D pt1, Point2D pt2,
 159                                          boolean isCyclic, boolean useMask)
 160     {
 161         // convert gradient colors to IntArgbPre format
 162         PixelConverter pc = PixelConverter.ArgbPre.instance;
 163         int pixel1 = pc.rgbToPixel(c1.getRGB(), null);
 164         int pixel2 = pc.rgbToPixel(c2.getRGB(), null);
 165 
 166         // calculate plane equation constants
 167         double x = pt1.getX();
 168         double y = pt1.getY();
 169         at.translate(x, y);
 170         // now gradient point 1 is at the origin
 171         x = pt2.getX() - x;
 172         y = pt2.getY() - y;
 173         double len = Math.sqrt(x * x + y * y);
 174         at.rotate(x, y);
 175         // now gradient point 2 is on the positive x-axis
 176         at.scale(2*len, 1);
 177         // now gradient point 2 is at (0.5, 0)
 178         at.translate(-0.25, 0);
 179         // now gradient point 1 is at (0.25, 0), point 2 is at (0.75, 0)
 180 
 181         double p0, p1, p3;
 182         try {
 183             at.invert();
 184             p0 = at.getScaleX();
 185             p1 = at.getShearX();
 186             p3 = at.getTranslateX();
 187         } catch (java.awt.geom.NoninvertibleTransformException e) {
 188             p0 = p1 = p3 = 0.0;
 189         }
 190 
 191         // assert rq.lock.isHeldByCurrentThread();
 192         rq.ensureCapacityAndAlignment(44, 12);
 193         RenderBuffer buf = rq.getBuffer();
 194         buf.putInt(SET_GRADIENT_PAINT);
 195         buf.putInt(useMask ? 1 : 0);
 196         buf.putInt(isCyclic ? 1 : 0);
 197         buf.putDouble(p0).putDouble(p1).putDouble(p3);
 198         buf.putInt(pixel1).putInt(pixel2);
 199     }
 200 
 201     private static void setGradientPaint(RenderQueue rq,
 202                                          SunGraphics2D sg2d,
 203                                          GradientPaint paint,
 204                                          boolean useMask)
 205     {
 206         setGradientPaint(rq, (AffineTransform)sg2d.transform.clone(),
 207                          paint.getColor1(), paint.getColor2(),
 208                          paint.getPoint1(), paint.getPoint2(),
 209                          paint.isCyclic(), useMask);
 210     }
 211 
 212 /************************** TexturePaint support ****************************/
 213 
 214     /**
 215      * We use OpenGL's texture coordinate generator to automatically
 216      * map the TexturePaint image to the geometry being rendered.  The
 217      * generator uses two separate plane equations that take the (x,y)
 218      * location (in device space) of the fragment being rendered to
 219      * calculate (u,v) texture coordinates for that fragment:
 220      *     u = Ax + By + Cz + Dw
 221      *     v = Ex + Fy + Gz + Hw
 222      *
 223      * Since we use a 2D orthographic projection, we can assume that z=0
 224      * and w=1 for any fragment.  So we need to calculate appropriate
 225      * values for the plane equation constants (A,B,D) and (E,F,H) such
 226      * that {u,v}=0 for the top-left of the TexturePaint's anchor
 227      * rectangle and {u,v}=1 for the bottom-right of the anchor rectangle.
 228      * We can easily make the texture image repeat for {u,v} values
 229      * outside the range [0,1] by specifying the GL_REPEAT texture wrap
 230      * mode.
 231      *
 232      * Calculating the plane equation constants is surprisingly simple.
 233      * We can think of it as an inverse matrix operation that takes
 234      * device space coordinates and transforms them into user space
 235      * coordinates that correspond to a location relative to the anchor
 236      * rectangle.  First, we translate and scale the current user space
 237      * transform by applying the anchor rectangle bounds.  We then take
 238      * the inverse of this affine transform.  The rows of the resulting
 239      * inverse matrix correlate nicely to the plane equation constants
 240      * we were seeking.
 241      */
 242     private static void setTexturePaint(RenderQueue rq,
 243                                         SunGraphics2D sg2d,
 244                                         TexturePaint paint,
 245                                         boolean useMask)
 246     {
 247         BufferedImage bi = paint.getImage();
 248         SurfaceData dstData = sg2d.surfaceData;
 249         SurfaceData srcData =
 250             dstData.getSourceSurfaceData(bi, sg2d.TRANSFORM_ISIDENT,
 251                                          CompositeType.SrcOver, null);
 252         boolean filter =
 253             (sg2d.interpolationType !=
 254              AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
 255 
 256         // calculate plane equation constants
 257         AffineTransform at = (AffineTransform)sg2d.transform.clone();
 258         Rectangle2D anchor = paint.getAnchorRect();
 259         at.translate(anchor.getX(), anchor.getY());
 260         at.scale(anchor.getWidth(), anchor.getHeight());
 261 
 262         double xp0, xp1, xp3, yp0, yp1, yp3;
 263         try {
 264             at.invert();
 265             xp0 = at.getScaleX();
 266             xp1 = at.getShearX();
 267             xp3 = at.getTranslateX();
 268             yp0 = at.getShearY();
 269             yp1 = at.getScaleY();
 270             yp3 = at.getTranslateY();
 271         } catch (java.awt.geom.NoninvertibleTransformException e) {
 272             xp0 = xp1 = xp3 = yp0 = yp1 = yp3 = 0.0;
 273         }
 274 
 275         // assert rq.lock.isHeldByCurrentThread();
 276         rq.ensureCapacityAndAlignment(68, 12);
 277         RenderBuffer buf = rq.getBuffer();
 278         buf.putInt(SET_TEXTURE_PAINT);
 279         buf.putInt(useMask ? 1 : 0);
 280         buf.putInt(filter ? 1 : 0);
 281         buf.putLong(srcData.getNativeOps());
 282         buf.putDouble(xp0).putDouble(xp1).putDouble(xp3);
 283         buf.putDouble(yp0).putDouble(yp1).putDouble(yp3);
 284     }
 285 
 286 /****************** Shared MultipleGradientPaint support ********************/
 287 
 288     /**
 289      * The maximum number of gradient "stops" supported by our native
 290      * fragment shader implementations.
 291      *
 292      * This value has been empirically determined and capped to allow
 293      * our native shaders to run on all shader-level graphics hardware,
 294      * even on the older, more limited GPUs.  Even the oldest Nvidia
 295      * hardware could handle 16, or even 32 fractions without any problem.
 296      * But the first-generation boards from ATI would fall back into
 297      * software mode (which is unusably slow) for values larger than 12;
 298      * it appears that those boards do not have enough native registers
 299      * to support the number of array accesses required by our gradient
 300      * shaders.  So for now we will cap this value at 12, but we can
 301      * re-evaluate this in the future as hardware becomes more capable.
 302      */
 303     public static final int MULTI_MAX_FRACTIONS = 12;
 304 
 305     /**
 306      * Helper function to convert a color component in sRGB space to
 307      * linear RGB space.  Copied directly from the
 308      * MultipleGradientPaintContext class.
 309      */
 310     public static int convertSRGBtoLinearRGB(int color) {
 311         float input, output;
 312 
 313         input = color / 255.0f;
 314         if (input <= 0.04045f) {
 315             output = input / 12.92f;
 316         } else {
 317             output = (float)Math.pow((input + 0.055) / 1.055, 2.4);
 318         }
 319 
 320         return Math.round(output * 255.0f);
 321     }
 322 
 323     /**
 324      * Helper function to convert a (non-premultiplied) Color in sRGB
 325      * space to an IntArgbPre pixel value, optionally in linear RGB space.
 326      * Based on the PixelConverter.ArgbPre.rgbToPixel() method.
 327      */
 328     private static int colorToIntArgbPrePixel(Color c, boolean linear) {
 329         int rgb = c.getRGB();
 330         if (!linear && ((rgb >> 24) == -1)) {
 331             return rgb;
 332         }
 333         int a = rgb >>> 24;
 334         int r = (rgb >> 16) & 0xff;
 335         int g = (rgb >>  8) & 0xff;
 336         int b = (rgb      ) & 0xff;
 337         if (linear) {
 338             r = convertSRGBtoLinearRGB(r);
 339             g = convertSRGBtoLinearRGB(g);
 340             b = convertSRGBtoLinearRGB(b);
 341         }
 342         int a2 = a + (a >> 7);
 343         r = (r * a2) >> 8;
 344         g = (g * a2) >> 8;
 345         b = (b * a2) >> 8;
 346         return ((a << 24) | (r << 16) | (g << 8) | (b));
 347     }
 348 
 349     /**
 350      * Converts the given array of Color objects into an int array
 351      * containing IntArgbPre pixel values.  If the linear parameter
 352      * is true, the Color values will be converted into a linear RGB
 353      * color space before being returned.
 354      */
 355     private static int[] convertToIntArgbPrePixels(Color[] colors,
 356                                                    boolean linear)
 357     {
 358         int[] pixels = new int[colors.length];
 359         for (int i = 0; i < colors.length; i++) {
 360             pixels[i] = colorToIntArgbPrePixel(colors[i], linear);
 361         }
 362         return pixels;
 363     }
 364 
 365 /********************** LinearGradientPaint support *************************/
 366 
 367     /**
 368      * This method uses techniques that are nearly identical to those
 369      * employed in setGradientPaint() above.  The primary difference
 370      * is that at the native level we use a fragment shader to manually
 371      * apply the plane equation constants to the current fragment position
 372      * to calculate the gradient position in the range [0,1] (the native
 373      * code for GradientPaint does the same, except that it uses OpenGL's
 374      * automatic texture coordinate generation facilities).
 375      *
 376      * One other minor difference worth mentioning is that
 377      * setGradientPaint() calculates the plane equation constants
 378      * such that the gradient end points are positioned at 0.25 and 0.75
 379      * (for reasons discussed in the comments for that method).  In
 380      * contrast, for LinearGradientPaint we setup the equation constants
 381      * such that the gradient end points fall at 0.0 and 1.0.  The
 382      * reason for this difference is that in the fragment shader we
 383      * have more control over how the gradient values are interpreted
 384      * (depending on the paint's CycleMethod).
 385      */
 386     private static void setLinearGradientPaint(RenderQueue rq,
 387                                                SunGraphics2D sg2d,
 388                                                LinearGradientPaint paint,
 389                                                boolean useMask)
 390     {
 391         boolean linear =
 392             (paint.getColorSpace() == ColorSpaceType.LINEAR_RGB);
 393         Color[] colors = paint.getColors();
 394         int numStops = colors.length;
 395         Point2D pt1 = paint.getStartPoint();
 396         Point2D pt2 = paint.getEndPoint();
 397         AffineTransform at = paint.getTransform();
 398         at.preConcatenate(sg2d.transform);
 399 
 400         if (!linear && numStops == 2 &&
 401             paint.getCycleMethod() != CycleMethod.REPEAT)
 402         {
 403             // delegate to the optimized two-color gradient codepath
 404             boolean isCyclic =
 405                 (paint.getCycleMethod() != CycleMethod.NO_CYCLE);
 406             setGradientPaint(rq, at,
 407                              colors[0], colors[1],
 408                              pt1, pt2,
 409                              isCyclic, useMask);
 410             return;
 411         }
 412 
 413         int cycleMethod = paint.getCycleMethod().ordinal();
 414         float[] fractions = paint.getFractions();
 415         int[] pixels = convertToIntArgbPrePixels(colors, linear);
 416 
 417         // calculate plane equation constants
 418         double x = pt1.getX();
 419         double y = pt1.getY();
 420         at.translate(x, y);
 421         // now gradient point 1 is at the origin
 422         x = pt2.getX() - x;
 423         y = pt2.getY() - y;
 424         double len = Math.sqrt(x * x + y * y);
 425         at.rotate(x, y);
 426         // now gradient point 2 is on the positive x-axis
 427         at.scale(len, 1);
 428         // now gradient point 1 is at (0.0, 0), point 2 is at (1.0, 0)
 429 
 430         float p0, p1, p3;
 431         try {
 432             at.invert();
 433             p0 = (float)at.getScaleX();
 434             p1 = (float)at.getShearX();
 435             p3 = (float)at.getTranslateX();
 436         } catch (java.awt.geom.NoninvertibleTransformException e) {
 437             p0 = p1 = p3 = 0.0f;
 438         }
 439 
 440         // assert rq.lock.isHeldByCurrentThread();
 441         rq.ensureCapacity(20 + 12 + (numStops*4*2));
 442         RenderBuffer buf = rq.getBuffer();
 443         buf.putInt(SET_LINEAR_GRADIENT_PAINT);
 444         buf.putInt(useMask ? 1 : 0);
 445         buf.putInt(linear  ? 1 : 0);
 446         buf.putInt(cycleMethod);
 447         buf.putInt(numStops);
 448         buf.putFloat(p0);
 449         buf.putFloat(p1);
 450         buf.putFloat(p3);
 451         buf.put(fractions);
 452         buf.put(pixels);
 453     }
 454 
 455 /********************** RadialGradientPaint support *************************/
 456 
 457     /**
 458      * This method calculates six m** values and a focusX value that
 459      * are used by the native fragment shader.  These techniques are
 460      * based on a whitepaper by Daniel Rice on radial gradient performance
 461      * (attached to the bug report for 6521533).  One can refer to that
 462      * document for the complete set of formulas and calculations, but
 463      * the basic goal is to compose a transform that will convert an
 464      * (x,y) position in device space into a "u" value that represents
 465      * the relative distance to the gradient focus point.  The resulting
 466      * value can be used to look up the appropriate color by linearly
 467      * interpolating between the two nearest colors in the gradient.
 468      */
 469     private static void setRadialGradientPaint(RenderQueue rq,
 470                                                SunGraphics2D sg2d,
 471                                                RadialGradientPaint paint,
 472                                                boolean useMask)
 473     {
 474         boolean linear =
 475             (paint.getColorSpace() == ColorSpaceType.LINEAR_RGB);
 476         int cycleMethod = paint.getCycleMethod().ordinal();
 477         float[] fractions = paint.getFractions();
 478         Color[] colors = paint.getColors();
 479         int numStops = colors.length;
 480         int[] pixels = convertToIntArgbPrePixels(colors, linear);
 481         Point2D center = paint.getCenterPoint();
 482         Point2D focus = paint.getFocusPoint();
 483         float radius = paint.getRadius();
 484 
 485         // save original (untransformed) center and focus points
 486         double cx = center.getX();
 487         double cy = center.getY();
 488         double fx = focus.getX();
 489         double fy = focus.getY();
 490 
 491         // transform from gradient coords to device coords
 492         AffineTransform at = paint.getTransform();
 493         at.preConcatenate(sg2d.transform);
 494         focus = at.transform(focus, focus);
 495 
 496         // transform unit circle to gradient coords; we start with the
 497         // unit circle (center=(0,0), focus on positive x-axis, radius=1)
 498         // and then transform into gradient space
 499         at.translate(cx, cy);
 500         at.rotate(fx - cx, fy - cy);
 501         at.scale(radius, radius);
 502 
 503         // invert to get mapping from device coords to unit circle
 504         try {
 505             at.invert();
 506         } catch (Exception e) {
 507             at.setToScale(0.0, 0.0);
 508         }
 509         focus = at.transform(focus, focus);
 510 
 511         // clamp the focus point so that it does not rest on, or outside
 512         // of, the circumference of the gradient circle
 513         fx = Math.min(focus.getX(), 0.99);
 514 
 515         // assert rq.lock.isHeldByCurrentThread();
 516         rq.ensureCapacity(20 + 28 + (numStops*4*2));
 517         RenderBuffer buf = rq.getBuffer();
 518         buf.putInt(SET_RADIAL_GRADIENT_PAINT);
 519         buf.putInt(useMask ? 1 : 0);
 520         buf.putInt(linear  ? 1 : 0);
 521         buf.putInt(numStops);
 522         buf.putInt(cycleMethod);
 523         buf.putFloat((float)at.getScaleX());
 524         buf.putFloat((float)at.getShearX());
 525         buf.putFloat((float)at.getTranslateX());
 526         buf.putFloat((float)at.getShearY());
 527         buf.putFloat((float)at.getScaleY());
 528         buf.putFloat((float)at.getTranslateY());
 529         buf.putFloat((float)fx);
 530         buf.put(fractions);
 531         buf.put(pixels);
 532     }
 533 }