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