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