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