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