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 }