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