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