1 /* 2 * Copyright (c) 2010, 2015, 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 com.sun.prism.j2d; 27 28 import java.awt.LinearGradientPaint; 29 import java.awt.font.GlyphVector; 30 import java.awt.geom.NoninvertibleTransformException; 31 import java.awt.geom.Rectangle2D; 32 import java.lang.ref.WeakReference; 33 import java.util.List; 34 import java.util.concurrent.ConcurrentHashMap; 35 import com.sun.glass.ui.Screen; 36 import com.sun.javafx.PlatformUtil; 37 import com.sun.javafx.font.CompositeGlyphMapper; 38 import com.sun.javafx.font.CompositeStrike; 39 import com.sun.javafx.font.FontResource; 40 import com.sun.javafx.font.FontStrike; 41 import com.sun.javafx.font.Metrics; 42 import com.sun.javafx.geom.PathIterator; 43 import com.sun.javafx.geom.RectBounds; 44 import com.sun.javafx.geom.Rectangle; 45 import com.sun.javafx.geom.Shape; 46 import com.sun.javafx.geom.transform.Affine2D; 47 import com.sun.javafx.geom.transform.BaseTransform; 48 import com.sun.javafx.scene.text.GlyphList; 49 import com.sun.javafx.sg.prism.NGCamera; 50 import com.sun.javafx.sg.prism.NGLightBase; 51 import com.sun.javafx.sg.prism.NodePath; 52 import com.sun.prism.BasicStroke; 53 import com.sun.prism.CompositeMode; 54 import com.sun.prism.MaskTextureGraphics; 55 import com.sun.prism.RTTexture; 56 import com.sun.prism.ReadbackGraphics; 57 import com.sun.prism.RenderTarget; 58 import com.sun.prism.ResourceFactory; 59 import com.sun.prism.Texture; 60 import com.sun.prism.Texture.WrapMode; 61 import com.sun.prism.impl.PrismSettings; 62 import com.sun.prism.j2d.paint.MultipleGradientPaint.ColorSpaceType; 63 import com.sun.prism.j2d.paint.RadialGradientPaint; 64 import com.sun.prism.paint.Color; 65 import com.sun.prism.paint.Gradient; 66 import com.sun.prism.paint.ImagePattern; 67 import com.sun.prism.paint.LinearGradient; 68 import com.sun.prism.paint.Paint; 69 import com.sun.prism.paint.RadialGradient; 70 import com.sun.prism.paint.Stop; 71 import static java.awt.RenderingHints.KEY_ANTIALIASING; 72 import static java.awt.RenderingHints.KEY_TEXT_ANTIALIASING; 73 import static java.awt.RenderingHints.VALUE_ANTIALIAS_OFF; 74 import static java.awt.RenderingHints.VALUE_ANTIALIAS_ON; 75 import static java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB; 76 import static java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_ON; 77 78 public class J2DPrismGraphics 79 // Do not subclass BaseGraphics without fixing drawTextureVO below... 80 implements ReadbackGraphics, MaskTextureGraphics 81 // Do not implement RectShadowGraphics without fixing RT-15016 (note that 82 // BaseGraphics implements RectShadowGraphics). 83 { 84 static { 85 // Assuming direct translation of BasicStroke enums: 86 assert(com.sun.prism.BasicStroke.CAP_BUTT == java.awt.BasicStroke.CAP_BUTT); 87 assert(com.sun.prism.BasicStroke.CAP_ROUND == java.awt.BasicStroke.CAP_ROUND); 88 assert(com.sun.prism.BasicStroke.CAP_SQUARE == java.awt.BasicStroke.CAP_SQUARE); 89 assert(com.sun.prism.BasicStroke.JOIN_BEVEL == java.awt.BasicStroke.JOIN_BEVEL); 90 assert(com.sun.prism.BasicStroke.JOIN_MITER == java.awt.BasicStroke.JOIN_MITER); 91 assert(com.sun.prism.BasicStroke.JOIN_ROUND == java.awt.BasicStroke.JOIN_ROUND); 92 // Assuming direct translation of PathIterator enums: 93 assert(com.sun.javafx.geom.PathIterator.WIND_EVEN_ODD == java.awt.geom.PathIterator.WIND_EVEN_ODD); 94 assert(com.sun.javafx.geom.PathIterator.WIND_NON_ZERO == java.awt.geom.PathIterator.WIND_NON_ZERO); 95 assert(com.sun.javafx.geom.PathIterator.SEG_MOVETO == java.awt.geom.PathIterator.SEG_MOVETO); 96 assert(com.sun.javafx.geom.PathIterator.SEG_LINETO == java.awt.geom.PathIterator.SEG_LINETO); 97 assert(com.sun.javafx.geom.PathIterator.SEG_QUADTO == java.awt.geom.PathIterator.SEG_QUADTO); 98 assert(com.sun.javafx.geom.PathIterator.SEG_CUBICTO == java.awt.geom.PathIterator.SEG_CUBICTO); 99 assert(com.sun.javafx.geom.PathIterator.SEG_CLOSE == java.awt.geom.PathIterator.SEG_CLOSE); 100 } 101 static final LinearGradientPaint.CycleMethod LGP_CYCLE_METHODS[] = { 102 LinearGradientPaint.CycleMethod.NO_CYCLE, 103 LinearGradientPaint.CycleMethod.REFLECT, 104 LinearGradientPaint.CycleMethod.REPEAT, 105 }; 106 static final RadialGradientPaint.CycleMethod RGP_CYCLE_METHODS[] = { 107 RadialGradientPaint.CycleMethod.NO_CYCLE, 108 RadialGradientPaint.CycleMethod.REFLECT, 109 RadialGradientPaint.CycleMethod.REPEAT, 110 }; 111 112 private static final BasicStroke DEFAULT_STROKE = 113 new BasicStroke(1.0f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10.0f); 114 private static final Paint DEFAULT_PAINT = Color.WHITE; 115 static java.awt.geom.AffineTransform J2D_IDENTITY = 116 new java.awt.geom.AffineTransform(); 117 private int clipRectIndex; 118 private boolean hasPreCullingBits = false; 119 private float pixelScale = 1.0f; 120 121 static java.awt.Color toJ2DColor(Color c) { 122 return new java.awt.Color(c.getRed(), 123 c.getGreen(), 124 c.getBlue(), 125 c.getAlpha()); 126 } 127 128 /* 129 * Ensure that no fractions are equal 130 * 131 * Note that the J2D objects reject equal fractions, but the FX versions 132 * allow them. 133 * 134 * The FX version treats values with equal fractions such that as you 135 * approach the fractional value from below it interpolates to the 136 * first color associated with that fraction and as you interpolate 137 * away from it from above it interpolates the last such color. 138 * 139 * To get the J2D version to exhibit the FX behavior we collapse all 140 * adjacent fractional values into a pair of values that are stored 141 * with a pair of immediately adjacent floating point values. This way 142 * they have unique fractions, but no fractional value can be generated 143 * which fits between them. Yet, as you approach from below it will 144 * interpolate to the first of the pair of colors and as you move away 145 * above it, the second value will take precedence for interpolation. 146 * 147 * Math.ulp() is used to generate an "immediately adjacent fp value". 148 */ 149 static int fixFractions(float fractions[], java.awt.Color colors[]) { 150 float fprev = fractions[0]; 151 int i = 1; // index of next incoming color/fractions we will examine 152 int n = 1; // index of next outgoing color/fraction we will store 153 while (i < fractions.length) { 154 float f = fractions[i]; 155 java.awt.Color c = colors[i++]; 156 if (f <= fprev) { 157 // If we find any duplicates after we reach 1.0 we can 158 // just ignore the rest of the array. Not only is there 159 // no more "fraction room" to assign them to, but we will 160 // never generate a fraction >1.0 to access them anyway 161 if (f >= 1.0f) break; 162 // Find all fractions that are either fprev or fprev+ulp 163 // and collapse them into two entries, the first at fprev 164 // which is already stored, and the last matching entry 165 // will be stored with fraction fprev+ulp 166 f = fprev + Math.ulp(fprev); 167 while (i < fractions.length) { 168 if (fractions[i] > f) break; 169 // We continue to remember the color of the last 170 // "matching" entry so it can be stored below 171 c = colors[i++]; 172 } 173 } 174 fractions[n] = fprev = f; 175 colors[n++] = c; 176 } 177 return n; 178 } 179 180 java.awt.Paint toJ2DPaint(Paint p, java.awt.geom.Rectangle2D b) { 181 if (p instanceof Color) { 182 return toJ2DColor((Color) p); 183 } else if (p instanceof Gradient) { 184 Gradient g = (Gradient) p; 185 if (g.isProportional()) { 186 if (b == null) { 187 return null; 188 } 189 } 190 List<Stop> stops = g.getStops(); 191 int n = stops.size(); 192 float fractions[] = new float[n]; 193 java.awt.Color colors[] = new java.awt.Color[n]; 194 float prevf = -1f; 195 boolean needsFix = false; 196 for (int i = 0; i < n; i++) { 197 Stop stop = stops.get(i); 198 float f = stop.getOffset(); 199 needsFix = (needsFix || f <= prevf); 200 fractions[i] = prevf = f; 201 colors[i] = toJ2DColor(stop.getColor()); 202 } 203 if (needsFix) { 204 n = fixFractions(fractions, colors); 205 if (n < fractions.length) { 206 float newf[] = new float[n]; 207 System.arraycopy(fractions, 0, newf, 0, n); 208 fractions = newf; 209 java.awt.Color newc[] = new java.awt.Color[n]; 210 System.arraycopy(colors, 0, newc, 0, n); 211 colors = newc; 212 } 213 } 214 if (g instanceof LinearGradient) { 215 LinearGradient lg = (LinearGradient) p; 216 float x1 = lg.getX1(); 217 float y1 = lg.getY1(); 218 float x2 = lg.getX2(); 219 float y2 = lg.getY2(); 220 if (g.isProportional()) { 221 float x = (float) b.getX(); 222 float y = (float) b.getY(); 223 float w = (float) b.getWidth(); 224 float h = (float) b.getHeight(); 225 x1 = x + w * x1; 226 y1 = y + h * y1; 227 x2 = x + w * x2; 228 y2 = y + h * y2; 229 } 230 if (x1 == x2 && y1 == y1) { 231 // Hardware pipelines use an inverse transform of 232 // all zeros to choose colors when the start and end 233 // point are the same so that the first color is 234 // always chosen... 235 return colors[0]; 236 } 237 java.awt.geom.Point2D p1 = 238 new java.awt.geom.Point2D.Float(x1, y1); 239 java.awt.geom.Point2D p2 = 240 new java.awt.geom.Point2D.Float(x2, y2); 241 LinearGradientPaint.CycleMethod method = 242 LGP_CYCLE_METHODS[g.getSpreadMethod()]; 243 return new LinearGradientPaint(p1, p2, fractions, colors, method); 244 } else if (g instanceof RadialGradient) { 245 RadialGradient rg = (RadialGradient) g; 246 float cx = rg.getCenterX(); 247 float cy = rg.getCenterY(); 248 float r = rg.getRadius(); 249 double fa = Math.toRadians(rg.getFocusAngle()); 250 float fd = rg.getFocusDistance(); 251 java.awt.geom.AffineTransform at = J2D_IDENTITY; 252 if (g.isProportional()) { 253 float x = (float) b.getX(); 254 float y = (float) b.getY(); 255 float w = (float) b.getWidth(); 256 float h = (float) b.getHeight(); 257 float dim = Math.min(w, h); 258 float bcx = x + w * 0.5f; 259 float bcy = y + h * 0.5f; 260 cx = bcx + (cx - 0.5f) * dim; 261 cy = bcy + (cy - 0.5f) * dim; 262 r *= dim; 263 if (w != h && w != 0.0 && h != 0.0) { 264 at = java.awt.geom.AffineTransform.getTranslateInstance(bcx, bcy); 265 at.scale(w / dim, h / dim); 266 at.translate(-bcx, -bcy); 267 } 268 } 269 java.awt.geom.Point2D center = 270 new java.awt.geom.Point2D.Float(cx, cy); 271 float fx = (float) (cx + fd * r * Math.cos(fa)); 272 float fy = (float) (cy + fd * r * Math.sin(fa)); 273 java.awt.geom.Point2D focus = 274 new java.awt.geom.Point2D.Float(fx, fy); 275 RadialGradientPaint.CycleMethod method = 276 RGP_CYCLE_METHODS[g.getSpreadMethod()]; 277 return new RadialGradientPaint(center, r, focus, fractions, colors, 278 method, ColorSpaceType.SRGB, at); 279 } 280 } else if (p instanceof ImagePattern) { 281 ImagePattern imgpat = (ImagePattern) p; 282 float x = imgpat.getX(); 283 float y = imgpat.getY(); 284 float w = imgpat.getWidth(); 285 float h = imgpat.getHeight(); 286 if (p.isProportional()) { 287 if (b == null) { 288 return null; 289 } 290 float bx = (float) b.getX(); 291 float by = (float) b.getY(); 292 float bw = (float) b.getWidth(); 293 float bh = (float) b.getHeight(); 294 w += x; 295 h += y; 296 x = bx + x * bw; 297 y = by + y * bh; 298 w = bx + w * bw; 299 h = by + h * bh; 300 w -= x; 301 h -= y; 302 } 303 Texture tex = 304 getResourceFactory().getCachedTexture(imgpat.getImage(), WrapMode.REPEAT); 305 java.awt.image.BufferedImage bimg = ((J2DTexture) tex).getBufferedImage(); 306 tex.unlock(); 307 return new java.awt.TexturePaint(bimg, tmpRect(x, y, w, h)); 308 } 309 throw new UnsupportedOperationException("Paint "+p+" not supported yet."); 310 } 311 312 static java.awt.Stroke toJ2DStroke(BasicStroke stroke) { 313 float lineWidth = stroke.getLineWidth(); 314 int type = stroke.getType(); 315 if (type != BasicStroke.TYPE_CENTERED) { 316 lineWidth *= 2; 317 } 318 java.awt.BasicStroke bs = 319 new java.awt.BasicStroke(lineWidth, 320 stroke.getEndCap(), 321 stroke.getLineJoin(), 322 stroke.getMiterLimit(), 323 stroke.getDashArray(), 324 stroke.getDashPhase()); 325 if (type == BasicStroke.TYPE_INNER) { 326 return new InnerStroke(bs); 327 } else if (type == BasicStroke.TYPE_OUTER) { 328 return new OuterStroke(bs); 329 } else { 330 return bs; 331 } 332 } 333 334 private static ConcurrentHashMap<java.awt.Font, 335 WeakReference<java.awt.Font>> 336 fontMap = new ConcurrentHashMap<java.awt.Font, 337 WeakReference<java.awt.Font>>(); 338 private static volatile int cleared = 0; 339 340 private static java.awt.Font toJ2DFont(FontStrike strike) { 341 FontResource fr = strike.getFontResource(); 342 java.awt.Font j2dfont; 343 Object peer = fr.getPeer(); 344 if (peer == null && fr.isEmbeddedFont()) { 345 J2DFontFactory.registerFont(fr); 346 peer = fr.getPeer(); 347 } 348 if (peer != null && peer instanceof java.awt.Font) { 349 j2dfont = (java.awt.Font)peer; 350 } else { 351 if (PlatformUtil.isMac()) { 352 // Looking up J2D fonts via full name is not reliable on the 353 // Mac, however using the PostScript font name is. The likely 354 // cause is Mac platform internals heavy reliance on PostScript 355 // names for font identification. 356 String psName = fr.getPSName(); 357 // dummy size 358 j2dfont = new java.awt.Font(psName, java.awt.Font.PLAIN, 12); 359 360 // REMIND: Due to bugs in j2d font lookup, these two workarounds 361 // are required to ensure the correct font is used. Once fixed 362 // in the jdk these workarounds should be removed. 363 if (!j2dfont.getPSName().equals(psName)) { 364 // 1. Lookup font via family and style. This covers the 365 // case when the J2D PostScript name does not match psName 366 // in font file. For example "HelveticaCYBold" has the 367 // psName "HelveticaCY-Bold" in j2d. 368 int style = fr.isBold() ? java.awt.Font.BOLD : 0; 369 style = style | (fr.isItalic() ? java.awt.Font.ITALIC : 0); 370 j2dfont = new java.awt.Font(fr.getFamilyName(), style, 12); 371 372 if(!j2dfont.getPSName().equals(psName)) { 373 // 2. J2D seems to be unable to find a few fonts where 374 // psName == familyName. Workaround is an exhaustive 375 // search of all fonts. 376 java.awt.Font[] allj2dFonts = 377 java.awt.GraphicsEnvironment. 378 getLocalGraphicsEnvironment().getAllFonts(); 379 for (java.awt.Font f : allj2dFonts) { 380 if (f.getPSName().equals(psName)) { 381 j2dfont = f; 382 break; 383 } 384 } 385 } 386 } 387 } else { 388 // dummy size 389 j2dfont = new java.awt.Font(fr.getFullName(), 390 java.awt.Font.PLAIN, 12); 391 } 392 393 // Adding j2dfont as peer is OK since fr is a decomposed 394 // FontResource. Thus preventing font lookup next time we render. 395 fr.setPeer(j2dfont); 396 } 397 // deriveFont(...) still has a bug and will cause #2 problem to occur 398 j2dfont = j2dfont.deriveFont(strike.getSize()); // exact float font size 399 java.awt.Font compFont = null; 400 WeakReference<java.awt.Font> ref = fontMap.get(j2dfont); 401 if (ref != null) { 402 compFont = ref.get(); 403 if (compFont == null) { 404 cleared++; 405 } 406 } 407 if (compFont == null) { 408 if (fontMap.size() > 100 && cleared > 10) { // purge the map. 409 for (java.awt.Font key : fontMap.keySet()) { 410 ref = fontMap.get(key); 411 if (ref == null || ref.get() == null) { 412 fontMap.remove(key); 413 } 414 } 415 cleared = 0; 416 } 417 compFont = J2DFontFactory.getCompositeFont(j2dfont); 418 ref = new WeakReference(compFont); 419 fontMap.put(j2dfont, ref); 420 } 421 return compFont; 422 } 423 424 public static java.awt.geom.AffineTransform 425 toJ2DTransform(BaseTransform t) 426 { 427 return new java.awt.geom.AffineTransform(t.getMxx(), t.getMyx(), 428 t.getMxy(), t.getMyy(), 429 t.getMxt(), t.getMyt()); 430 } 431 432 private static java.awt.geom.AffineTransform tmpAT = 433 new java.awt.geom.AffineTransform(); 434 static java.awt.geom.AffineTransform tmpJ2DTransform(BaseTransform t) 435 { 436 tmpAT.setTransform(t.getMxx(), t.getMyx(), 437 t.getMxy(), t.getMyy(), 438 t.getMxt(), t.getMyt()); 439 return tmpAT; 440 } 441 442 static BaseTransform toPrTransform(java.awt.geom.AffineTransform t) 443 { 444 return BaseTransform.getInstance(t.getScaleX(), t.getShearY(), 445 t.getShearX(), t.getScaleY(), 446 t.getTranslateX(), t.getTranslateY()); 447 } 448 449 static Rectangle toPrRect(java.awt.Rectangle r) 450 { 451 return new Rectangle(r.x, r.y, r.width, r.height); 452 } 453 454 private static java.awt.geom.Path2D tmpQuadShape = 455 new java.awt.geom.Path2D.Float(); 456 private static java.awt.Shape tmpQuad(float x1, float y1, 457 float x2, float y2) 458 { 459 tmpQuadShape.reset(); 460 tmpQuadShape.moveTo(x1, y1); 461 tmpQuadShape.lineTo(x2, y1); 462 tmpQuadShape.lineTo(x2, y2); 463 tmpQuadShape.lineTo(x1, y2); 464 tmpQuadShape.closePath(); 465 return tmpQuadShape; 466 } 467 468 private static java.awt.geom.Rectangle2D.Float tmpRect = 469 new java.awt.geom.Rectangle2D.Float(); 470 private static java.awt.geom.Rectangle2D tmpRect(float x, float y, float w, float h) { 471 tmpRect.setRect(x, y, w, h); 472 return tmpRect; 473 } 474 475 private static java.awt.geom.Ellipse2D tmpEllipse = 476 new java.awt.geom.Ellipse2D.Float(); 477 private static java.awt.Shape tmpEllipse(float x, float y, float w, float h) { 478 tmpEllipse.setFrame(x, y, w, h); 479 return tmpEllipse; 480 } 481 482 private static java.awt.geom.RoundRectangle2D tmpRRect = 483 new java.awt.geom.RoundRectangle2D.Float(); 484 private static java.awt.Shape tmpRRect(float x, float y, float w, float h, 485 float aw, float ah) 486 { 487 tmpRRect.setRoundRect(x, y, w, h, aw, ah); 488 return tmpRRect; 489 } 490 491 private static java.awt.geom.Line2D tmpLine = 492 new java.awt.geom.Line2D.Float(); 493 private static java.awt.Shape tmpLine(float x1, float y1, float x2, float y2) { 494 tmpLine.setLine(x1, y1, x2, y2); 495 return tmpLine; 496 } 497 498 private static AdaptorShape tmpAdaptor = new AdaptorShape(); 499 private static java.awt.Shape tmpShape(Shape s) { 500 tmpAdaptor.setShape(s); 501 return tmpAdaptor; 502 } 503 504 private boolean antialiasedShape = true; 505 J2DPresentable target; 506 java.awt.Graphics2D g2d; 507 Affine2D transform; 508 Rectangle clipRect; 509 RectBounds devClipRect; 510 RectBounds finalClipRect; 511 Paint paint; 512 boolean paintWasProportional; 513 BasicStroke stroke; 514 boolean cull; 515 516 J2DPrismGraphics(J2DPresentable target, java.awt.Graphics2D g2d) { 517 this(g2d, target.getContentWidth(), target.getContentHeight()); 518 this.target = target; 519 } 520 521 J2DPrismGraphics(java.awt.Graphics2D g2d, int width, int height) { 522 this.g2d = g2d; 523 captureTransform(g2d); 524 this.transform = new Affine2D(); 525 this.devClipRect = new RectBounds(0, 0, width, height); 526 this.finalClipRect = new RectBounds(0, 0, width, height); 527 this.cull = true; 528 529 g2d.setRenderingHint(java.awt.RenderingHints.KEY_STROKE_CONTROL, 530 java.awt.RenderingHints.VALUE_STROKE_PURE); 531 g2d.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING, 532 java.awt.RenderingHints.VALUE_ANTIALIAS_ON); 533 g2d.setRenderingHint(java.awt.RenderingHints.KEY_INTERPOLATION, 534 java.awt.RenderingHints.VALUE_INTERPOLATION_BILINEAR); 535 /* Set the text hints to those most equivalent to FX rendering. 536 * Will need to revisit this since its unlikely to be sufficient. 537 */ 538 g2d.setRenderingHint(java.awt.RenderingHints.KEY_FRACTIONALMETRICS, 539 java.awt.RenderingHints.VALUE_FRACTIONALMETRICS_ON); 540 g2d.setRenderingHint(java.awt.RenderingHints.KEY_TEXT_ANTIALIASING, 541 java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 542 543 544 setTransform(BaseTransform.IDENTITY_TRANSFORM); 545 setPaint(DEFAULT_PAINT); 546 setStroke(DEFAULT_STROKE); 547 } 548 549 public RenderTarget getRenderTarget() { 550 return target; 551 } 552 553 public Screen getAssociatedScreen() { 554 return target.getAssociatedScreen(); 555 } 556 557 public ResourceFactory getResourceFactory() { 558 return target.getResourceFactory(); 559 } 560 561 public void reset() { 562 } 563 564 public Rectangle getClipRect() { 565 return clipRect == null ? null : new Rectangle(clipRect); 566 } 567 568 public Rectangle getClipRectNoClone() { 569 return clipRect; 570 } 571 572 public RectBounds getFinalClipNoClone() { 573 return finalClipRect; 574 } 575 576 public void setClipRect(Rectangle clipRect) { 577 this.finalClipRect.setBounds(devClipRect); 578 if (clipRect == null) { 579 this.clipRect = null; 580 g2d.setClip(null); 581 } else { 582 this.clipRect = new Rectangle(clipRect); 583 this.finalClipRect.intersectWith(clipRect); 584 setTransformG2D(J2D_IDENTITY); 585 g2d.setClip(clipRect.x, clipRect.y, clipRect.width, clipRect.height); 586 setTransformG2D(tmpJ2DTransform(transform)); 587 } 588 } 589 590 private java.awt.AlphaComposite getAWTComposite() { 591 return (java.awt.AlphaComposite) g2d.getComposite(); 592 } 593 594 public float getExtraAlpha() { 595 return getAWTComposite().getAlpha(); 596 } 597 598 public void setExtraAlpha(float extraAlpha) { 599 g2d.setComposite(getAWTComposite().derive(extraAlpha)); 600 } 601 602 public CompositeMode getCompositeMode() { 603 int rule = getAWTComposite().getRule(); 604 switch (rule) { 605 case java.awt.AlphaComposite.CLEAR: 606 return CompositeMode.CLEAR; 607 case java.awt.AlphaComposite.SRC: 608 return CompositeMode.SRC; 609 case java.awt.AlphaComposite.SRC_OVER: 610 return CompositeMode.SRC_OVER; 611 default: 612 throw new InternalError("Unrecognized AlphaCompsite rule: "+rule); 613 } 614 } 615 616 public void setCompositeMode(CompositeMode mode) { 617 java.awt.AlphaComposite awtComp = getAWTComposite(); 618 switch (mode) { 619 case CLEAR: 620 awtComp = awtComp.derive(java.awt.AlphaComposite.CLEAR); 621 break; 622 case SRC: 623 awtComp = awtComp.derive(java.awt.AlphaComposite.SRC); 624 break; 625 case SRC_OVER: 626 awtComp = awtComp.derive(java.awt.AlphaComposite.SRC_OVER); 627 break; 628 default: 629 throw new InternalError("Unrecognized composite mode: "+mode); 630 } 631 g2d.setComposite(awtComp); 632 } 633 634 public Paint getPaint() { 635 return paint; 636 } 637 638 public void setPaint(Paint paint) { 639 this.paint = paint; 640 java.awt.Paint j2dpaint = toJ2DPaint(paint, null); 641 if (j2dpaint == null) { 642 paintWasProportional = true; 643 } else { 644 paintWasProportional = false; 645 g2d.setPaint(j2dpaint); 646 } 647 } 648 649 public BasicStroke getStroke() { 650 return stroke; 651 } 652 653 public void setStroke(BasicStroke stroke) { 654 this.stroke = stroke; 655 g2d.setStroke(toJ2DStroke(stroke)); 656 } 657 658 public BaseTransform getTransformNoClone() { 659 return transform; 660 } 661 662 public void translate(float tx, float ty) { 663 transform.translate(tx, ty); 664 g2d.translate(tx, ty); 665 } 666 667 public void scale(float sx, float sy) { 668 transform.scale(sx, sy); 669 g2d.scale(sx, sy); 670 } 671 672 public void transform(BaseTransform xform) { 673 if (!xform.is2D()) { 674 // No-op until we support 3D 675 return; 676 } 677 transform.concatenate(xform); 678 setTransformG2D(tmpJ2DTransform(transform)); 679 } 680 681 public void setTransform(BaseTransform xform) { 682 // TODO: Modify PrEffectHelper to not pass a null... (RT-27384) 683 if (xform == null) xform = BaseTransform.IDENTITY_TRANSFORM; 684 transform.setTransform(xform); 685 setTransformG2D(tmpJ2DTransform(transform)); 686 } 687 688 public void setTransform(double m00, double m10, 689 double m01, double m11, 690 double m02, double m12) 691 { 692 transform.setTransform(m00, m10, m01, m11, m02, m12); 693 setTransformG2D(tmpJ2DTransform(transform)); 694 } 695 696 public void clear() { 697 clear(Color.TRANSPARENT); 698 } 699 700 public void clear(Color color) { 701 this.getRenderTarget().setOpaque(color.isOpaque()); 702 clear(toJ2DColor(color)); 703 } 704 705 void clear(java.awt.Color c) { 706 java.awt.Graphics2D gtmp = (java.awt.Graphics2D) g2d.create(); 707 gtmp.setTransform(J2D_IDENTITY); 708 gtmp.setComposite(java.awt.AlphaComposite.Src); 709 gtmp.setColor(c); 710 gtmp.fillRect(0, 0, target.getContentWidth(), target.getContentHeight()); 711 gtmp.dispose(); 712 } 713 714 public void clearQuad(float x1, float y1, float x2, float y2) { 715 g2d.setComposite(java.awt.AlphaComposite.Clear); 716 g2d.fill(tmpQuad(x1, y1, x2, y2)); 717 } 718 719 void fill(java.awt.Shape shape) { 720 if (paintWasProportional) { 721 if (nodeBounds != null) { 722 g2d.setPaint(toJ2DPaint(paint, nodeBounds)); 723 } else { 724 g2d.setPaint(toJ2DPaint(paint, shape.getBounds2D())); 725 } 726 } 727 g2d.fill(shape); 728 } 729 730 public void fill(Shape shape) { 731 fill(tmpShape(shape)); 732 } 733 734 public void fillRect(float x, float y, float width, float height) { 735 fill(tmpRect(x, y, width, height)); 736 } 737 738 public void fillRoundRect(float x, float y, float width, float height, 739 float arcw, float arch) 740 { 741 fill(tmpRRect(x, y, width, height, arcw, arch)); 742 } 743 744 public void fillEllipse(float x, float y, float width, float height) { 745 fill(tmpEllipse(x, y, width, height)); 746 } 747 748 public void fillQuad(float x1, float y1, float x2, float y2) { 749 fill(tmpQuad(x1, y1, x2, y2)); 750 } 751 752 void draw(java.awt.Shape shape) { 753 if (paintWasProportional) { 754 if (nodeBounds != null) { 755 g2d.setPaint(toJ2DPaint(paint, nodeBounds)); 756 } else { 757 g2d.setPaint(toJ2DPaint(paint, shape.getBounds2D())); 758 } 759 } 760 try { 761 g2d.draw(shape); 762 } catch (Throwable t) { 763 // Workaround for JDK bug 6670624 764 // We may get a Ductus PRError (extends RuntimeException) 765 // or we may get an InternalError (extends Error) 766 // The only common superclass of the two is Throwable... 767 } 768 } 769 770 public void draw(Shape shape) { 771 draw(tmpShape(shape)); 772 } 773 774 public void drawLine(float x1, float y1, float x2, float y2) { 775 draw(tmpLine(x1, y1, x2, y2)); 776 } 777 778 public void drawRect(float x, float y, float width, float height) { 779 draw(tmpRect(x, y, width, height)); 780 } 781 782 public void drawRoundRect(float x, float y, float width, float height, float arcw, float arch) { 783 draw(tmpRRect(x, y, width, height, arcw, arch)); 784 } 785 786 public void drawEllipse(float x, float y, float width, float height) { 787 draw(tmpEllipse(x, y, width, height)); 788 } 789 790 Rectangle2D nodeBounds = null; 791 792 public void setNodeBounds(RectBounds bounds) { 793 nodeBounds = bounds != null ? 794 new Rectangle2D.Float(bounds.getMinX(), bounds.getMinY(), 795 bounds.getWidth(),bounds.getHeight()) : 796 null; 797 } 798 799 private void drawString(GlyphList gl, int start, int end, 800 FontStrike strike, float x, float y) { 801 if (start == end) return; 802 int count = end - start; 803 int[] glyphs = new int[count]; 804 for (int i = 0; i < count; i++) { 805 glyphs[i] = gl.getGlyphCode(start + i) & CompositeGlyphMapper.GLYPHMASK; 806 } 807 java.awt.Font j2dfont = toJ2DFont(strike); 808 GlyphVector gv = j2dfont.createGlyphVector(g2d.getFontRenderContext(), glyphs); 809 java.awt.geom.Point2D pt = new java.awt.geom.Point2D.Float(); 810 for (int i = 0; i < count; i++) { 811 pt.setLocation(gl.getPosX(start + i), gl.getPosY(start + i)); 812 gv.setGlyphPosition(i, pt); 813 } 814 g2d.drawGlyphVector(gv, x, y); 815 } 816 817 public void drawString(GlyphList gl, FontStrike strike, float x, float y, 818 Color selectColor, int start, int end) { 819 820 int count = gl.getGlyphCount(); 821 if (count == 0) return; 822 823 // In JDK6, setting graphics AA disables fast text loops 824 g2d.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_OFF); 825 826 // If the surface has Alpha, JDK will ignore the LCD loops. 827 // So for this to have any effect we need to fix JDK, or 828 // ensure an opaque surface type. 829 if (strike.getAAMode() == FontResource.AA_LCD) { 830 g2d.setRenderingHint(KEY_TEXT_ANTIALIASING, VALUE_TEXT_ANTIALIAS_LCD_HRGB); 831 } 832 833 if (paintWasProportional) { 834 Rectangle2D rectBounds = nodeBounds; 835 if (rectBounds == null) { 836 Metrics m = strike.getMetrics(); 837 rectBounds = new Rectangle2D.Float(0, 838 m.getAscent(), 839 gl.getWidth(), 840 m.getLineHeight()); 841 } 842 g2d.setPaint(toJ2DPaint(paint, rectBounds)); 843 } 844 845 CompositeStrike cStrike = null; 846 int slot = 0; 847 if (strike instanceof CompositeStrike) { 848 cStrike = (CompositeStrike)strike; 849 int glyphCode = gl.getGlyphCode(0); 850 slot = cStrike.getStrikeSlotForGlyph(glyphCode); 851 } 852 java.awt.Color sColor = null; 853 java.awt.Color tColor = null; 854 boolean selected = false; 855 if (selectColor != null) { 856 sColor = toJ2DColor(selectColor); 857 tColor = g2d.getColor(); 858 int offset = gl.getCharOffset(0); 859 selected = start <= offset && offset < end; 860 } 861 int index = 0; 862 if (sColor != null || cStrike != null) { 863 /* Draw a segment every time selection or font changes */ 864 for (int i = 1; i < count; i++) { 865 if (sColor != null) { 866 int offset = gl.getCharOffset(i); 867 boolean glyphSelected = start <= offset && offset < end; 868 if (selected != glyphSelected) { 869 if (cStrike != null) { 870 strike = cStrike.getStrikeSlot(slot); 871 } 872 g2d.setColor(selected ? sColor : tColor); 873 drawString(gl, index, i, strike, x, y); 874 index = i; 875 selected = glyphSelected; 876 } 877 } 878 if (cStrike != null) { 879 int glyphCode = gl.getGlyphCode(i); 880 int glyphSlot = cStrike.getStrikeSlotForGlyph(glyphCode); 881 if (slot != glyphSlot) { 882 strike = cStrike.getStrikeSlot(slot); 883 if (sColor != null) { 884 g2d.setColor(selected ? sColor : tColor); 885 } 886 drawString(gl, index, i, strike, x, y); 887 index = i; 888 slot = glyphSlot; 889 } 890 } 891 } 892 893 /* Set strike and color to draw the last segment */ 894 if (cStrike != null) { 895 strike = cStrike.getStrikeSlot(slot); 896 } 897 if (sColor != null) { 898 g2d.setColor(selected ? sColor : tColor); 899 } 900 } 901 drawString(gl, index, count, strike, x, y); 902 903 /* Always restore the graphics to its initial color */ 904 if (selectColor != null) { 905 g2d.setColor(tColor); 906 } 907 908 // Set hints back to the default. 909 g2d.setRenderingHint(KEY_TEXT_ANTIALIASING, VALUE_TEXT_ANTIALIAS_ON); 910 g2d.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON); 911 } 912 913 /** 914 * Overridden by printing subclass to preserve the printer graphics 915 * transform. 916 */ 917 protected void setTransformG2D(java.awt.geom.AffineTransform tx) { 918 g2d.setTransform(tx); 919 } 920 921 /** 922 * Needed only by printing subclass, which over-rides it. 923 */ 924 protected void captureTransform(java.awt.Graphics2D g2d) { 925 return; 926 } 927 928 public void drawMappedTextureRaw(Texture tex, 929 float dx1, float dy1, float dx2, float dy2, 930 float tx11, float ty11, float tx21, float ty21, 931 float tx12, float ty12, float tx22, float ty22) 932 { 933 java.awt.Image img = ((J2DTexture) tex).getBufferedImage(); 934 float mxx = tx21-tx11; 935 float myx = ty21-ty11; 936 float mxy = tx12-tx11; 937 float myy = ty12-ty11; 938 // assert(Math.abs(mxx - (tx22-tx12)) < .000001); 939 // assert(Math.abs(myx - (ty22-ty12)) < .000001); 940 // assert(Math.abs(mxy - (tx22-tx21)) < .000001); 941 // assert(Math.abs(myy - (ty22-ty21)) < .000001); 942 setTransformG2D(J2D_IDENTITY); 943 tmpAT.setTransform(mxx, myx, mxy, myy, tx11, ty11); 944 try { 945 tmpAT.invert(); 946 g2d.translate(dx1, dy1); 947 g2d.scale(dx2-dx1, dy2-dy1); 948 g2d.transform(tmpAT); 949 g2d.drawImage(img, 0, 0, 1, 1, null); 950 } catch (NoninvertibleTransformException e) { 951 } 952 setTransform(transform); 953 } 954 955 public void drawTexture(Texture tex, float x, float y, float w, float h) { 956 java.awt.Image img = ((J2DTexture) tex).getBufferedImage(); 957 g2d.drawImage(img, (int) x, (int) y, (int) (x+w), (int) (y+h), 0, 0, (int)w, (int) h, null); 958 } 959 960 public void drawTexture(Texture tex, 961 float dx1, float dy1, float dx2, float dy2, 962 float sx1, float sy1, float sx2, float sy2) 963 { 964 java.awt.Image img = ((J2DTexture) tex).getBufferedImage(); 965 // Simply casting the subimage coordinates to integers does not 966 // produce the same behavior as the Prism hw pipelines (see RT-19270). 967 g2d.drawImage(img, 968 (int) dx1, (int) dy1, (int) dx2, (int) dy2, 969 (int) sx1, (int) sy1, (int) sx2, (int) sy2, 970 null); 971 } 972 973 @Override 974 public void drawTexture3SliceH(Texture tex, 975 float dx1, float dy1, float dx2, float dy2, 976 float sx1, float sy1, float sx2, float sy2, 977 float dh1, float dh2, float sh1, float sh2) 978 { 979 // Workaround for problems in NGRegion which may pass zero-width 980 // source image area. 981 if (sh1 + 0.1f > sh2) sh2 += 1; 982 drawTexture(tex, dx1, dy1, dh1, dy2, sx1, sy1, sh1, sy2); 983 drawTexture(tex, dh1, dy1, dh2, dy2, sh1, sy1, sh2, sy2); 984 drawTexture(tex, dh2, dy1, dx2, dy2, sh2, sy1, sx2, sy2); 985 } 986 987 @Override 988 public void drawTexture3SliceV(Texture tex, 989 float dx1, float dy1, float dx2, float dy2, 990 float sx1, float sy1, float sx2, float sy2, 991 float dv1, float dv2, float sv1, float sv2) 992 { 993 // Workaround for problems in NGRegion which may pass zero-height 994 // source image area. 995 if (sv1 +0.1f > sv2) sv2 += 1; 996 drawTexture(tex, dx1, dy1, dx2, dv1, sx1, sy1, sx2, sv1); 997 drawTexture(tex, dx1, dv1, dx2, dv2, sx1, sv1, sx2, sv2); 998 drawTexture(tex, dx1, dv2, dx2, dy2, sx1, sv2, sx2, sy2); 999 } 1000 1001 @Override 1002 public void drawTexture9Slice(Texture tex, 1003 float dx1, float dy1, float dx2, float dy2, 1004 float sx1, float sy1, float sx2, float sy2, 1005 float dh1, float dv1, float dh2, float dv2, 1006 float sh1, float sv1, float sh2, float sv2) 1007 { 1008 // Workaround for problems in NGRegion which may pass zero-width 1009 // or zero height source image area. 1010 if (sh1 + 0.1f > sh2) sh2 += 1; 1011 if (sv1 + 0.1f > sv2) sv2 += 1; 1012 drawTexture(tex, dx1, dy1, dh1, dv1, sx1, sy1, sh1, sv1); 1013 drawTexture(tex, dh1, dy1, dh2, dv1, sh1, sy1, sh2, sv1); 1014 drawTexture(tex, dh2, dy1, dx2, dv1, sh2, sy1, sx2, sv1); 1015 1016 drawTexture(tex, dx1, dv1, dh1, dv2, sx1, sv1, sh1, sv2); 1017 drawTexture(tex, dh1, dv1, dh2, dv2, sh1, sv1, sh2, sv2); 1018 drawTexture(tex, dh2, dv1, dx2, dv2, sh2, sv1, sx2, sv2); 1019 1020 drawTexture(tex, dx1, dv2, dh1, dy2, sx1, sv2, sh1, sy2); 1021 drawTexture(tex, dh1, dv2, dh2, dy2, sh1, sv2, sh2, sy2); 1022 drawTexture(tex, dh2, dv2, dx2, dy2, sh2, sv2, sx2, sy2); 1023 } 1024 1025 public void drawTextureRaw(Texture tex, 1026 float dx1, float dy1, float dx2, float dy2, 1027 float tx1, float ty1, float tx2, float ty2) 1028 { 1029 int w = tex.getContentWidth(); 1030 int h = tex.getContentHeight(); 1031 tx1 *= w; 1032 ty1 *= h; 1033 tx2 *= w; 1034 ty2 *= h; 1035 drawTexture(tex, dx1, dy1, dx2, dy2, tx1, ty1, tx2, ty2); 1036 } 1037 1038 public void drawTextureVO(Texture tex, 1039 float topopacity, float botopacity, 1040 float dx1, float dy1, float dx2, float dy2, 1041 float sx1, float sy1, float sx2, float sy2) 1042 { 1043 // assert(caller is PrReflectionPeer and buffer is cleared to transparent) 1044 // NOTE: the assert conditions are true because that is the only 1045 // place where this method is used (unless we subclass BaseGraphics), 1046 // but there is no code here to verify that information. 1047 // The workarounds to do this for the general case would cost a lot 1048 // because they would involve creating a temporary intermediate buffer, 1049 // doing the operations below into the buffer, and then applying the 1050 // buffer to the destination. That is not hard, but it costs a lot 1051 // of buffer allocation (or caching) when it is not really necessary 1052 // given the way this method is called currently. 1053 // Note that isoEdgeMask is ignored here, but since this is only ever 1054 // called by PrReflectionPeer and that code always uses ISOLATE_NONE 1055 // then we would only need to support ISOLATE_NONE. The code below 1056 // does not yet verify if the results will be compatible with 1057 // ISOLATE_NONE, but given that the source coordinates are rounded to 1058 // integers in drawTexture() there is not much it can do to get exact 1059 // edge condition behavior until that deficiency is fixed (see 1060 // RT-19270 and RT-19271). 1061 java.awt.Paint savepaint = g2d.getPaint(); 1062 java.awt.Composite savecomp = g2d.getComposite(); 1063 java.awt.Color c1 = new java.awt.Color(1f, 1f, 1f, topopacity); 1064 java.awt.Color c2 = new java.awt.Color(1f, 1f, 1f, botopacity); 1065 g2d.setPaint(new java.awt.GradientPaint(0f, dy1, c1, 0f, dy2, c2, true)); 1066 g2d.setComposite(java.awt.AlphaComposite.Src); 1067 int x = (int) Math.floor(Math.min(dx1, dx2)); 1068 int y = (int) Math.floor(Math.min(dy1, dy2)); 1069 int w = (int) Math.ceil(Math.max(dx1, dx2)) - x; 1070 int h = (int) Math.ceil(Math.max(dy1, dy2)) - y; 1071 g2d.fillRect(x, y, w, h); 1072 g2d.setComposite(java.awt.AlphaComposite.SrcIn); 1073 drawTexture(tex, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2); 1074 g2d.setComposite(savecomp); 1075 g2d.setPaint(savepaint); 1076 } 1077 1078 public void drawPixelsMasked(RTTexture imgtex, RTTexture masktex, 1079 int dx, int dy, int dw, int dh, 1080 int ix, int iy, int mx, int my) 1081 { 1082 doDrawMaskTexture((J2DRTTexture) imgtex, (J2DRTTexture) masktex, 1083 dx, dy, dw, dh, 1084 ix, iy, mx, my, 1085 true); 1086 } 1087 1088 public void maskInterpolatePixels(RTTexture imgtex, RTTexture masktex, int dx, 1089 int dy, int dw, int dh, int ix, int iy, 1090 int mx, int my) { 1091 doDrawMaskTexture((J2DRTTexture) imgtex, (J2DRTTexture) masktex, 1092 dx, dy, dw, dh, 1093 ix, iy, mx, my, 1094 false); 1095 } 1096 1097 private void doDrawMaskTexture(J2DRTTexture imgtex, J2DRTTexture masktex, 1098 int dx, int dy, int dw, int dh, 1099 int ix, int iy, int mx, int my, 1100 boolean srcover) 1101 { 1102 int cx0 = clipRect.x; 1103 int cy0 = clipRect.y; 1104 int cx1 = cx0 + clipRect.width; 1105 int cy1 = cy0 + clipRect.height; 1106 1107 if (dw <= 0 || dh <= 0) return; 1108 if (dx < cx0) { 1109 int bump = cx0 - dx; 1110 if ((dw -= bump) <= 0) return; 1111 ix += bump; 1112 mx += bump; 1113 dx = cx0; 1114 } 1115 if (dy < cy0) { 1116 int bump = cy0 - dy; 1117 if ((dh -= bump) <= 0) return; 1118 iy += bump; 1119 my += bump; 1120 dy = cy0; 1121 } 1122 if (dx + dw > cx1 && (dw = cx1 - dx) <= 0) return; 1123 if (dy + dh > cy1 && (dh = cy1 - dy) <= 0) return; 1124 1125 int iw = imgtex.getContentWidth(); 1126 int ih = imgtex.getContentHeight(); 1127 if (ix < 0) { 1128 if ((dw += ix) <= 0) return; 1129 dx -= ix; 1130 mx -= ix; 1131 ix = 0; 1132 } 1133 if (iy < 0) { 1134 if ((dh += iy) <= 0) return; 1135 dy -= iy; 1136 my -= iy; 1137 iy = 0; 1138 } 1139 if (ix + dw > iw && (dw = iw - ix) <= 0) return; 1140 if (iy + dh > ih && (dh = ih - iy) <= 0) return; 1141 1142 int mw = masktex.getContentWidth(); 1143 int mh = masktex.getContentHeight(); 1144 if (mx < 0) { 1145 if ((dw += mx) <= 0) return; 1146 dx -= mx; 1147 ix -= mx; 1148 mx = 0; 1149 } 1150 if (my < 0) { 1151 if ((dh += my) <= 0) return; 1152 dy -= my; 1153 iy -= my; 1154 my = 0; 1155 } 1156 if (mx + dw > mw && (dw = mw - mx) <= 0) return; 1157 if (my + dh > mh && (dh = mh - my) <= 0) return; 1158 1159 int imgbuf[] = imgtex.getPixels(); 1160 int maskbuf[] = masktex.getPixels(); 1161 java.awt.image.DataBuffer db = target.getBackBuffer().getRaster().getDataBuffer(); 1162 int dstbuf[] = ((java.awt.image.DataBufferInt) db).getData(); 1163 int iscan = imgtex.getBufferedImage().getWidth(); 1164 int mscan = masktex.getBufferedImage().getWidth(); 1165 int dscan = target.getBackBuffer().getWidth(); 1166 int ioff = iy * iscan + ix; 1167 int moff = my * mscan + mx; 1168 int doff = dy * dscan + dx; 1169 if (srcover) { 1170 for (int y = 0; y < dh; y++) { 1171 for (int x = 0; x < dw; x++) { 1172 int a, r, g, b; 1173 int maskalpha = maskbuf[moff+x] >>> 24; 1174 if (maskalpha == 0) continue; 1175 int imgpix = imgbuf[ioff+x]; 1176 a = (imgpix >>> 24); 1177 if (a == 0) continue; 1178 if (maskalpha < 0xff) { 1179 maskalpha += (maskalpha >> 7); 1180 a *= maskalpha; 1181 r = ((imgpix >> 16) & 0xff) * maskalpha; 1182 g = ((imgpix >> 8) & 0xff) * maskalpha; 1183 b = ((imgpix ) & 0xff) * maskalpha; 1184 } else if (a < 0xff) { 1185 a <<= 8; 1186 r = ((imgpix >> 16) & 0xff) << 8; 1187 g = ((imgpix >> 8) & 0xff) << 8; 1188 b = ((imgpix ) & 0xff) << 8; 1189 } else { 1190 dstbuf[doff+x] = imgpix; 1191 continue; 1192 } 1193 maskalpha = ((a + 128) >> 8); 1194 maskalpha += (maskalpha >> 7); 1195 maskalpha = 256 - maskalpha; 1196 imgpix = dstbuf[doff+x]; 1197 a += ((imgpix >>> 24) ) * maskalpha + 128; 1198 r += ((imgpix >> 16) & 0xff) * maskalpha + 128; 1199 g += ((imgpix >> 8) & 0xff) * maskalpha + 128; 1200 b += ((imgpix ) & 0xff) * maskalpha + 128; 1201 imgpix = ((a >> 8) << 24) + 1202 ((r >> 8) << 16) + 1203 ((g >> 8) << 8) + 1204 ((b >> 8) ); 1205 dstbuf[doff+x] = imgpix; 1206 } 1207 ioff += iscan; 1208 moff += mscan; 1209 doff += dscan; 1210 } 1211 } else { 1212 for (int y = 0; y < dh; y++) { 1213 for (int x = 0; x < dw; x++) { 1214 int maskalpha = maskbuf[moff+x] >>> 24; 1215 if (maskalpha == 0) continue; 1216 int imgpix = imgbuf[ioff+x]; 1217 if (maskalpha < 0xff) { 1218 maskalpha += (maskalpha >> 7); 1219 int a = ((imgpix >>> 24) ) * maskalpha; 1220 int r = ((imgpix >> 16) & 0xff) * maskalpha; 1221 int g = ((imgpix >> 8) & 0xff) * maskalpha; 1222 int b = ((imgpix ) & 0xff) * maskalpha; 1223 maskalpha = 256 - maskalpha; 1224 imgpix = dstbuf[doff+x]; 1225 a += ((imgpix >>> 24) ) * maskalpha + 128; 1226 r += ((imgpix >> 16) & 0xff) * maskalpha + 128; 1227 g += ((imgpix >> 8) & 0xff) * maskalpha + 128; 1228 b += ((imgpix ) & 0xff) * maskalpha + 128; 1229 imgpix = ((a >> 8) << 24) + 1230 ((r >> 8) << 16) + 1231 ((g >> 8) << 8) + 1232 ((b >> 8) ); 1233 } 1234 dstbuf[doff+x] = imgpix; 1235 } 1236 ioff += iscan; 1237 moff += mscan; 1238 doff += dscan; 1239 } 1240 } 1241 } 1242 1243 public boolean canReadBack() { 1244 return true; 1245 } 1246 1247 public RTTexture readBack(Rectangle view) { 1248 J2DRTTexture rtt = target.getReadbackBuffer(); 1249 java.awt.Graphics2D rttg2d = rtt.createAWTGraphics2D(); 1250 rttg2d.setComposite(java.awt.AlphaComposite.Src); 1251 int x0 = view.x; 1252 int y0 = view.y; 1253 int w = view.width; 1254 int h = view.height; 1255 int x1 = x0 + w; 1256 int y1 = y0 + h; 1257 rttg2d.drawImage(target.getBackBuffer(), 1258 0, 0, w, h, 1259 x0, y0, x1, y1, null); 1260 rttg2d.dispose(); 1261 return rtt; 1262 } 1263 1264 public void releaseReadBackBuffer(RTTexture view) { 1265 // This will be needed when we track LCD buffer locks and uses. 1266 // (See RT-29488) 1267 // target.getReadbackBuffer().unlock(); 1268 } 1269 1270 public NGCamera getCameraNoClone() { 1271 throw new UnsupportedOperationException("Not supported yet."); 1272 } 1273 1274 public boolean isDepthBuffer() { 1275 return false; 1276 } 1277 1278 public boolean isDepthTest() { 1279 return false; 1280 } 1281 1282 public boolean isAlphaTestShader() { 1283 if (PrismSettings.verbose && PrismSettings.forceAlphaTestShader) { 1284 System.out.println("J2D pipe doesn't support shader with alpha testing"); 1285 } 1286 return false; 1287 } 1288 1289 public void setAntialiasedShape(boolean aa) { 1290 antialiasedShape = aa; 1291 g2d.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING, 1292 antialiasedShape ? java.awt.RenderingHints.VALUE_ANTIALIAS_ON 1293 : java.awt.RenderingHints.VALUE_ANTIALIAS_OFF); 1294 } 1295 1296 public boolean isAntialiasedShape() { 1297 return antialiasedShape; 1298 } 1299 1300 public void scale(float sx, float sy, float sz) { 1301 throw new UnsupportedOperationException("Not supported yet."); 1302 } 1303 1304 public void setTransform3D(double mxx, double mxy, double mxz, double mxt, 1305 double myx, double myy, double myz, double myt, 1306 double mzx, double mzy, double mzz, double mzt) 1307 { 1308 if (mxz != 0.0 || myz != 0.0 || 1309 mzx != 0.0 || mzy != 0.0 || mzz != 1.0 || mzt != 0.0) 1310 { 1311 throw new UnsupportedOperationException("3D transforms not supported."); 1312 } 1313 setTransform(mxx, myx, mxy, myy, mxt, myt); 1314 } 1315 1316 public void setCamera(NGCamera camera) { 1317 // No-op until we support 3D 1318 /* 1319 if (!(camera instanceof PrismParallelCameraImpl)) { 1320 1321 throw new UnsupportedOperationException(camera+" not supported."); 1322 } 1323 */ 1324 } 1325 1326 public void setDepthBuffer(boolean depthBuffer) { 1327 // No-op until we support 3D 1328 } 1329 1330 public void setDepthTest(boolean depthTest) { 1331 // No-op until we support 3D 1332 } 1333 1334 public void sync() { 1335 } 1336 1337 public void translate(float tx, float ty, float tz) { 1338 throw new UnsupportedOperationException("Not supported yet."); 1339 } 1340 1341 public void setCulling(boolean cull) { 1342 this.cull = cull; 1343 } 1344 1345 public boolean isCulling() { 1346 return this.cull; 1347 } 1348 1349 public void setClipRectIndex(int index) { 1350 this.clipRectIndex = index; 1351 } 1352 public int getClipRectIndex() { 1353 return this.clipRectIndex; 1354 } 1355 1356 public void setHasPreCullingBits(boolean hasBits) { 1357 this.hasPreCullingBits = hasBits; 1358 } 1359 1360 public boolean hasPreCullingBits() { 1361 return hasPreCullingBits; 1362 } 1363 1364 private NodePath renderRoot; 1365 @Override 1366 public void setRenderRoot(NodePath root) { 1367 this.renderRoot = root; 1368 } 1369 1370 @Override 1371 public NodePath getRenderRoot() { 1372 return renderRoot; 1373 } 1374 1375 public void setState3D(boolean flag) { 1376 } 1377 1378 public boolean isState3D() { 1379 return false; 1380 } 1381 1382 public void setup3DRendering() { 1383 } 1384 1385 @Override 1386 public void setPixelScaleFactor(float pixelScale) { 1387 this.pixelScale = pixelScale; 1388 } 1389 1390 @Override 1391 public float getPixelScaleFactor() { 1392 return pixelScale; 1393 } 1394 1395 @Override 1396 public void blit(RTTexture srcTex, RTTexture dstTex, 1397 int srcX0, int srcY0, int srcX1, int srcY1, 1398 int dstX0, int dstY0, int dstX1, int dstY1) { 1399 throw new UnsupportedOperationException("Not supported yet."); 1400 } 1401 1402 private static class AdaptorShape implements java.awt.Shape { 1403 private Shape prshape; 1404 1405 public void setShape(Shape prshape) { 1406 this.prshape = prshape; 1407 } 1408 1409 public boolean contains(double x, double y) { 1410 return prshape.contains((float) x, (float) y); 1411 } 1412 1413 public boolean contains(java.awt.geom.Point2D p) { 1414 return contains(p.getX(), p.getY()); 1415 } 1416 1417 public boolean contains(double x, double y, double w, double h) { 1418 return prshape.contains((float) x, (float) y, (float) w, (float) h); 1419 } 1420 1421 public boolean contains(java.awt.geom.Rectangle2D r) { 1422 return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight()); 1423 } 1424 1425 public boolean intersects(double x, double y, double w, double h) { 1426 return prshape.intersects((float) x, (float) y, (float) w, (float) h); 1427 } 1428 1429 public boolean intersects(java.awt.geom.Rectangle2D r) { 1430 return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight()); 1431 } 1432 1433 public java.awt.Rectangle getBounds() { 1434 return getBounds2D().getBounds(); 1435 } 1436 1437 public java.awt.geom.Rectangle2D getBounds2D() { 1438 RectBounds b = prshape.getBounds(); 1439 java.awt.geom.Rectangle2D r2d = 1440 new java.awt.geom.Rectangle2D.Float(); 1441 r2d.setFrameFromDiagonal(b.getMinX(), b.getMinY(), b.getMaxX(), b.getMaxY()); 1442 return r2d; 1443 } 1444 1445 private static AdaptorPathIterator tmpAdaptor = 1446 new AdaptorPathIterator(); 1447 private static java.awt.geom.PathIterator tmpAdaptor(PathIterator pi) { 1448 tmpAdaptor.setIterator(pi); 1449 return tmpAdaptor; 1450 } 1451 1452 public java.awt.geom.PathIterator 1453 getPathIterator(java.awt.geom.AffineTransform at) 1454 { 1455 BaseTransform tx = (at == null) ? null : toPrTransform(at); 1456 return tmpAdaptor(prshape.getPathIterator(tx)); 1457 } 1458 1459 public java.awt.geom.PathIterator 1460 getPathIterator(java.awt.geom.AffineTransform at, 1461 double flatness) 1462 { 1463 BaseTransform tx = (at == null) ? null : toPrTransform(at); 1464 return tmpAdaptor(prshape.getPathIterator(tx, (float) flatness)); 1465 } 1466 } 1467 1468 private static class AdaptorPathIterator 1469 implements java.awt.geom.PathIterator 1470 { 1471 private static int NUM_COORDS[] = { 2, 2, 4, 6, 0 }; 1472 PathIterator priterator; 1473 float tmpcoords[]; 1474 1475 public void setIterator(PathIterator priterator) { 1476 this.priterator = priterator; 1477 } 1478 1479 public int currentSegment(float[] coords) { 1480 return priterator.currentSegment(coords); 1481 } 1482 1483 public int currentSegment(double[] coords) { 1484 if (tmpcoords == null) { 1485 tmpcoords = new float[6]; 1486 } 1487 int ret = priterator.currentSegment(tmpcoords); 1488 for (int i = 0; i < NUM_COORDS[ret]; i++) { 1489 coords[i] = (double) tmpcoords[i]; 1490 } 1491 return ret; 1492 } 1493 1494 public int getWindingRule() { 1495 return priterator.getWindingRule(); 1496 } 1497 1498 public boolean isDone() { 1499 return priterator.isDone(); 1500 } 1501 1502 public void next() { 1503 priterator.next(); 1504 } 1505 } 1506 1507 static abstract class FilterStroke implements java.awt.Stroke { 1508 protected java.awt.BasicStroke stroke; 1509 1510 FilterStroke(java.awt.BasicStroke stroke) { 1511 this.stroke = stroke; 1512 } 1513 1514 abstract protected java.awt.Shape makeStrokedRect(java.awt.geom.Rectangle2D r); 1515 abstract protected java.awt.Shape makeStrokedShape(java.awt.Shape s); 1516 1517 public java.awt.Shape createStrokedShape(java.awt.Shape p) { 1518 if (p instanceof java.awt.geom.Rectangle2D) { 1519 java.awt.Shape s = makeStrokedRect((java.awt.geom.Rectangle2D) p); 1520 if (s != null) { 1521 return s; 1522 } 1523 } 1524 return makeStrokedShape(p); 1525 } 1526 1527 // ArcIterator.btan(Math.PI/2) 1528 static final double CtrlVal = 0.5522847498307933; 1529 1530 static java.awt.geom.Point2D cornerArc(java.awt.geom.GeneralPath gp, 1531 float x0, float y0, 1532 float xc, float yc, 1533 float x1, float y1) 1534 { 1535 return cornerArc(gp, x0, y0, xc, yc, x1, y1, 0.5f); 1536 } 1537 1538 static java.awt.geom.Point2D cornerArc(java.awt.geom.GeneralPath gp, 1539 float x0, float y0, 1540 float xc, float yc, 1541 float x1, float y1, float t) 1542 { 1543 float xc0 = (float) (x0 + CtrlVal * (xc - x0)); 1544 float yc0 = (float) (y0 + CtrlVal * (yc - y0)); 1545 float xc1 = (float) (x1 + CtrlVal * (xc - x1)); 1546 float yc1 = (float) (y1 + CtrlVal * (yc - y1)); 1547 gp.curveTo(xc0, yc0, xc1, yc1, x1, y1); 1548 1549 return new java.awt.geom.Point2D.Float(eval(x0, xc0, xc1, x1, t), 1550 eval(y0, yc0, yc1, y1, t)); 1551 } 1552 1553 static float eval(float c0, float c1, float c2, float c3, float t) { 1554 c0 = c0 + (c1-c0) * t; 1555 c1 = c1 + (c2-c1) * t; 1556 c2 = c2 + (c3-c2) * t; 1557 c0 = c0 + (c1-c0) * t; 1558 c1 = c1 + (c2-c1) * t; 1559 return c0 + (c1-c0) * t; 1560 } 1561 } 1562 1563 static class InnerStroke extends FilterStroke { 1564 InnerStroke(java.awt.BasicStroke stroke) { 1565 super(stroke); 1566 } 1567 1568 protected java.awt.Shape makeStrokedRect(java.awt.geom.Rectangle2D r) { 1569 if (stroke.getDashArray() != null) { 1570 return null; 1571 } 1572 float pad = stroke.getLineWidth() / 2f; 1573 if (pad >= r.getWidth() || pad >= r.getHeight()) { 1574 return r; 1575 } 1576 float rx0 = (float) r.getX(); 1577 float ry0 = (float) r.getY(); 1578 float rx1 = rx0 + (float) r.getWidth(); 1579 float ry1 = ry0 + (float) r.getHeight(); 1580 java.awt.geom.GeneralPath gp = new java.awt.geom.GeneralPath(); 1581 gp.moveTo(rx0, ry0); 1582 gp.lineTo(rx1, ry0); 1583 gp.lineTo(rx1, ry1); 1584 gp.lineTo(rx0, ry1); 1585 gp.closePath(); 1586 rx0 += pad; 1587 ry0 += pad; 1588 rx1 -= pad; 1589 ry1 -= pad; 1590 gp.moveTo(rx0, ry0); 1591 gp.lineTo(rx0, ry1); 1592 gp.lineTo(rx1, ry1); 1593 gp.lineTo(rx1, ry0); 1594 gp.closePath(); 1595 return gp; 1596 } 1597 1598 // NOTE: This is a work in progress, not used yet 1599 protected java.awt.Shape makeStrokedEllipse(java.awt.geom.Ellipse2D e) { 1600 if (stroke.getDashArray() != null) { 1601 return null; 1602 } 1603 float pad = stroke.getLineWidth() / 2f; 1604 float w = (float) e.getWidth(); 1605 float h = (float) e.getHeight(); 1606 if (w - 2*pad > h * 2 || h - 2*pad > w * 2) { 1607 // If the inner ellipse is too "squashed" then we can not 1608 // approximate it with just a single cubic per quadrant. 1609 // NOTE: measure so we can relax this restriction and 1610 // also consider modifying the code below to insert 1611 // more cubics in those cases. 1612 return null; 1613 } 1614 if (pad >= w || pad >= h) { 1615 return e; 1616 } 1617 float x0 = (float) e.getX(); 1618 float y0 = (float) e.getY(); 1619 float xc = x0 + w / 2f; 1620 float yc = y0 + h / 2f; 1621 float x1 = x0 + w; 1622 float y1 = y0 + h; 1623 java.awt.geom.GeneralPath gp = new java.awt.geom.GeneralPath(); 1624 gp.moveTo(xc, y0); 1625 cornerArc(gp, xc, y0, x1, y0, x1, yc); 1626 cornerArc(gp, x1, yc, x1, y1, xc, y1); 1627 cornerArc(gp, xc, y1, x0, y1, x0, yc); 1628 cornerArc(gp, x0, yc, x0, y0, xc, y0); 1629 gp.closePath(); 1630 x0 += pad; 1631 y0 += pad; 1632 x1 -= pad; 1633 y1 -= pad; 1634 gp.moveTo(xc, y0); 1635 cornerArc(gp, xc, y0, x0, y0, x0, yc); 1636 cornerArc(gp, x0, yc, x0, y1, xc, y1); 1637 cornerArc(gp, xc, y1, x1, y1, x1, yc); 1638 cornerArc(gp, x1, yc, x1, y0, xc, y0); 1639 gp.closePath(); 1640 return gp; 1641 } 1642 1643 @Override 1644 protected java.awt.Shape makeStrokedShape(java.awt.Shape s) { 1645 java.awt.Shape ss = stroke.createStrokedShape(s); 1646 java.awt.geom.Area b = new java.awt.geom.Area(ss); 1647 b.intersect(new java.awt.geom.Area(s)); 1648 return b; 1649 } 1650 } 1651 1652 static class OuterStroke extends FilterStroke { 1653 static double SQRT_2 = Math.sqrt(2); 1654 1655 OuterStroke(java.awt.BasicStroke stroke) { 1656 super(stroke); 1657 } 1658 1659 protected java.awt.Shape makeStrokedRect(java.awt.geom.Rectangle2D r) { 1660 if (stroke.getDashArray() != null) { 1661 return null; 1662 } 1663 float pad = stroke.getLineWidth() / 2f; 1664 float rx0 = (float) r.getX(); 1665 float ry0 = (float) r.getY(); 1666 float rx1 = rx0 + (float) r.getWidth(); 1667 float ry1 = ry0 + (float) r.getHeight(); 1668 java.awt.geom.GeneralPath gp = new java.awt.geom.GeneralPath(); 1669 // clockwise 1670 gp.moveTo(rx0, ry0); 1671 gp.lineTo(rx1, ry0); 1672 gp.lineTo(rx1, ry1); 1673 gp.lineTo(rx0, ry1); 1674 gp.closePath(); 1675 float ox0 = rx0 - pad; 1676 float oy0 = ry0 - pad; 1677 float ox1 = rx1 + pad; 1678 float oy1 = ry1 + pad; 1679 switch (stroke.getLineJoin()) { 1680 case BasicStroke.JOIN_MITER: 1681 // A miter limit of less than sqrt(2) bevels right angles... 1682 if (stroke.getMiterLimit() >= SQRT_2) { 1683 // counter-clockwise 1684 gp.moveTo(ox0, oy0); 1685 gp.lineTo(ox0, oy1); 1686 gp.lineTo(ox1, oy1); 1687 gp.lineTo(ox1, oy0); 1688 gp.closePath(); 1689 break; 1690 } 1691 // NO BREAK 1692 case BasicStroke.JOIN_BEVEL: 1693 // counter-clockwise 1694 gp.moveTo(ox0, ry0); 1695 gp.lineTo(ox0, ry1); // left edge 1696 gp.lineTo(rx0, oy1); // ll corner 1697 gp.lineTo(rx1, oy1); // bottom edge 1698 gp.lineTo(ox1, ry1); // lr corner 1699 gp.lineTo(ox1, ry0); // right edge 1700 gp.lineTo(rx1, oy0); // ur corner 1701 gp.lineTo(rx0, oy0); // top edge 1702 gp.closePath(); // ul corner 1703 break; 1704 case BasicStroke.JOIN_ROUND: 1705 // counter-clockwise 1706 gp.moveTo(ox0, ry0); 1707 gp.lineTo(ox0, ry1); // left edge 1708 cornerArc(gp, ox0, ry1, ox0, oy1, rx0, oy1); // ll corner 1709 gp.lineTo(rx1, oy1); // bottom edge 1710 cornerArc(gp, rx1, oy1, ox1, oy1, ox1, ry1); // lr corner 1711 gp.lineTo(ox1, ry0); // right edge 1712 cornerArc(gp, ox1, ry0, ox1, oy0, rx1, oy0); // ur corner 1713 gp.lineTo(rx0, oy0); // top edge 1714 cornerArc(gp, rx0, oy0, ox0, oy0, ox0, ry0); // ul corner 1715 gp.closePath(); 1716 break; 1717 default: 1718 throw new InternalError("Unrecognized line join style"); 1719 } 1720 return gp; 1721 } 1722 1723 // NOTE: This is a work in progress, not used yet 1724 protected java.awt.Shape makeStrokedEllipse(java.awt.geom.Ellipse2D e) { 1725 if (stroke.getDashArray() != null) { 1726 return null; 1727 } 1728 float pad = stroke.getLineWidth() / 2f; 1729 float w = (float) e.getWidth(); 1730 float h = (float) e.getHeight(); 1731 if (w > h * 2 || h > w * 2) { 1732 // If the inner ellipse is too "squashed" then we can not 1733 // approximate it with just a single cubic per quadrant. 1734 // NOTE: measure so we can relax this restriction and 1735 // also consider modifying the code below to insert 1736 // more cubics in those cases. 1737 return null; 1738 } 1739 float x0 = (float) e.getX(); 1740 float y0 = (float) e.getY(); 1741 float xc = x0 + w / 2f; 1742 float yc = y0 + h / 2f; 1743 float x1 = x0 + w; 1744 float y1 = y0 + h; 1745 java.awt.geom.GeneralPath gp = new java.awt.geom.GeneralPath(); 1746 gp.moveTo(xc, y0); 1747 cornerArc(gp, xc, y0, x1, y0, x1, yc); 1748 cornerArc(gp, x1, yc, x1, y1, xc, y1); 1749 cornerArc(gp, xc, y1, x0, y1, x0, yc); 1750 cornerArc(gp, x0, yc, x0, y0, xc, y0); 1751 gp.closePath(); 1752 x0 -= pad; 1753 y0 -= pad; 1754 x1 += pad; 1755 y1 += pad; 1756 gp.moveTo(xc, y0); 1757 cornerArc(gp, xc, y0, x0, y0, x0, yc); 1758 cornerArc(gp, x0, yc, x0, y1, xc, y1); 1759 cornerArc(gp, xc, y1, x1, y1, x1, yc); 1760 cornerArc(gp, x1, yc, x1, y0, xc, y0); 1761 gp.closePath(); 1762 return gp; 1763 } 1764 1765 @Override 1766 protected java.awt.Shape makeStrokedShape(java.awt.Shape s) { 1767 java.awt.Shape ss = stroke.createStrokedShape(s); 1768 java.awt.geom.Area b = new java.awt.geom.Area(ss); 1769 b.subtract(new java.awt.geom.Area(s)); 1770 return b; 1771 } 1772 } 1773 1774 @Override 1775 public void setLights(NGLightBase[] lights) { 1776 // Light are not supported by J2d 1777 } 1778 1779 @Override 1780 public NGLightBase[] getLights() { 1781 // Light are not supported by J2d 1782 return null; 1783 } 1784 }