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 }