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