1 /* 2 * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.java2d; 27 28 import java.awt.Graphics; 29 import java.awt.Graphics2D; 30 import java.awt.RenderingHints; 31 import java.awt.RenderingHints.Key; 32 import java.awt.geom.Area; 33 import java.awt.geom.AffineTransform; 34 import java.awt.geom.NoninvertibleTransformException; 35 import java.awt.AlphaComposite; 36 import java.awt.BasicStroke; 37 import java.awt.image.BufferedImage; 38 import java.awt.image.BufferedImageOp; 39 import java.awt.image.RenderedImage; 40 import java.awt.image.renderable.RenderableImage; 41 import java.awt.image.renderable.RenderContext; 42 import java.awt.image.AffineTransformOp; 43 import java.awt.image.Raster; 44 import java.awt.image.WritableRaster; 45 import java.awt.Image; 46 import java.awt.Composite; 47 import java.awt.Color; 48 import java.awt.image.ColorModel; 49 import java.awt.GraphicsConfiguration; 50 import java.awt.Paint; 51 import java.awt.GradientPaint; 52 import java.awt.LinearGradientPaint; 53 import java.awt.RadialGradientPaint; 54 import java.awt.TexturePaint; 55 import java.awt.geom.Rectangle2D; 56 import java.awt.geom.PathIterator; 57 import java.awt.geom.GeneralPath; 58 import java.awt.Shape; 59 import java.awt.Stroke; 60 import java.awt.FontMetrics; 61 import java.awt.Rectangle; 62 import java.text.AttributedCharacterIterator; 63 import java.awt.Font; 64 import java.awt.Point; 65 import java.awt.image.ImageObserver; 66 import java.awt.Transparency; 67 import java.awt.font.GlyphVector; 68 import java.awt.font.TextLayout; 69 70 import sun.awt.image.SurfaceManager; 71 import sun.font.FontDesignMetrics; 72 import sun.font.FontUtilities; 73 import sun.java2d.pipe.PixelDrawPipe; 74 import sun.java2d.pipe.PixelFillPipe; 75 import sun.java2d.pipe.ShapeDrawPipe; 76 import sun.java2d.pipe.ValidatePipe; 77 import sun.java2d.pipe.ShapeSpanIterator; 78 import sun.java2d.pipe.Region; 79 import sun.java2d.pipe.TextPipe; 80 import sun.java2d.pipe.DrawImagePipe; 81 import sun.java2d.pipe.LoopPipe; 82 import sun.java2d.loops.FontInfo; 83 import sun.java2d.loops.RenderLoops; 84 import sun.java2d.loops.CompositeType; 85 import sun.java2d.loops.SurfaceType; 86 import sun.java2d.loops.Blit; 87 import sun.java2d.loops.MaskFill; 88 import java.awt.font.FontRenderContext; 89 import sun.java2d.loops.XORComposite; 90 import sun.awt.ConstrainableGraphics; 91 import sun.awt.SunHints; 92 import java.util.Map; 93 import java.util.Iterator; 94 import sun.misc.PerformanceLogger; 95 96 import java.lang.annotation.Native; 97 import sun.awt.image.MultiResolutionImage; 98 99 import static java.awt.geom.AffineTransform.TYPE_FLIP; 100 import static java.awt.geom.AffineTransform.TYPE_MASK_SCALE; 101 import static java.awt.geom.AffineTransform.TYPE_TRANSLATION; 102 import sun.awt.image.MultiResolutionToolkitImage; 103 import sun.awt.image.ToolkitImage; 104 105 /** 106 * This is a the master Graphics2D superclass for all of the Sun 107 * Graphics implementations. This class relies on subclasses to 108 * manage the various device information, but provides an overall 109 * general framework for performing all of the requests in the 110 * Graphics and Graphics2D APIs. 111 * 112 * @author Jim Graham 113 */ 114 public final class SunGraphics2D 115 extends Graphics2D 116 implements ConstrainableGraphics, Cloneable, DestSurfaceProvider 117 { 118 /* 119 * Attribute States 120 */ 121 /* Paint */ 122 @Native 123 public static final int PAINT_CUSTOM = 6; /* Any other Paint object */ 124 @Native 125 public static final int PAINT_TEXTURE = 5; /* Tiled Image */ 126 @Native 127 public static final int PAINT_RAD_GRADIENT = 4; /* Color RadialGradient */ 128 @Native 129 public static final int PAINT_LIN_GRADIENT = 3; /* Color LinearGradient */ 130 @Native 131 public static final int PAINT_GRADIENT = 2; /* Color Gradient */ 132 @Native 133 public static final int PAINT_ALPHACOLOR = 1; /* Non-opaque Color */ 134 @Native 135 public static final int PAINT_OPAQUECOLOR = 0; /* Opaque Color */ 136 137 /* Composite*/ 138 @Native 139 public static final int COMP_CUSTOM = 3;/* Custom Composite */ 140 @Native 141 public static final int COMP_XOR = 2;/* XOR Mode Composite */ 142 @Native 143 public static final int COMP_ALPHA = 1;/* AlphaComposite */ 144 @Native 145 public static final int COMP_ISCOPY = 0;/* simple stores into destination, 146 * i.e. Src, SrcOverNoEa, and other 147 * alpha modes which replace 148 * the destination. 149 */ 150 151 /* Stroke */ 152 @Native 153 public static final int STROKE_CUSTOM = 3; /* custom Stroke */ 154 @Native 155 public static final int STROKE_WIDE = 2; /* BasicStroke */ 156 @Native 157 public static final int STROKE_THINDASHED = 1; /* BasicStroke */ 158 @Native 159 public static final int STROKE_THIN = 0; /* BasicStroke */ 160 161 /* Transform */ 162 @Native 163 public static final int TRANSFORM_GENERIC = 4; /* any 3x2 */ 164 @Native 165 public static final int TRANSFORM_TRANSLATESCALE = 3; /* scale XY */ 166 @Native 167 public static final int TRANSFORM_ANY_TRANSLATE = 2; /* non-int translate */ 168 @Native 169 public static final int TRANSFORM_INT_TRANSLATE = 1; /* int translate */ 170 @Native 171 public static final int TRANSFORM_ISIDENT = 0; /* Identity */ 172 173 /* Clipping */ 174 @Native 175 public static final int CLIP_SHAPE = 2; /* arbitrary clip */ 176 @Native 177 public static final int CLIP_RECTANGULAR = 1; /* rectangular clip */ 178 @Native 179 public static final int CLIP_DEVICE = 0; /* no clipping set */ 180 181 /* The following fields are used when the current Paint is a Color. */ 182 public int eargb; // ARGB value with ExtraAlpha baked in 183 public int pixel; // pixel value for eargb 184 185 public SurfaceData surfaceData; 186 187 public PixelDrawPipe drawpipe; 188 public PixelFillPipe fillpipe; 189 public DrawImagePipe imagepipe; 190 public ShapeDrawPipe shapepipe; 191 public TextPipe textpipe; 192 public MaskFill alphafill; 193 194 public RenderLoops loops; 195 196 public CompositeType imageComp; /* Image Transparency checked on fly */ 197 198 public int paintState; 199 public int compositeState; 200 public int strokeState; 201 public int transformState; 202 public int clipState; 203 204 public Color foregroundColor; 205 public Color backgroundColor; 206 207 public AffineTransform transform; 208 public int transX; 209 public int transY; 210 211 protected static final Stroke defaultStroke = new BasicStroke(); 212 protected static final Composite defaultComposite = AlphaComposite.SrcOver; 213 private static final Font defaultFont = 214 new Font(Font.DIALOG, Font.PLAIN, 12); 215 216 public Paint paint; 217 public Stroke stroke; 218 public Composite composite; 219 protected Font font; 220 protected FontMetrics fontMetrics; 221 222 public int renderHint; 223 public int antialiasHint; 224 public int textAntialiasHint; 225 protected int fractionalMetricsHint; 226 227 /* A gamma adjustment to the colour used in lcd text blitting */ 228 public int lcdTextContrast; 229 private static int lcdTextContrastDefaultValue = 140; 230 231 private int interpolationHint; // raw value of rendering Hint 232 public int strokeHint; 233 234 public int interpolationType; // algorithm choice based on 235 // interpolation and render Hints 236 237 public RenderingHints hints; 238 239 public Region constrainClip; // lightweight bounds in pixels 240 public int constrainX; 241 public int constrainY; 242 243 public Region clipRegion; 244 public Shape usrClip; 245 protected Region devClip; // Actual physical drawable in pixels 246 247 private final int devScale; // Actual physical scale factor 248 private int resolutionVariantHint; 249 250 // cached state for text rendering 251 private boolean validFontInfo; 252 private FontInfo fontInfo; 253 private FontInfo glyphVectorFontInfo; 254 private FontRenderContext glyphVectorFRC; 255 256 private final static int slowTextTransformMask = 257 AffineTransform.TYPE_GENERAL_TRANSFORM 258 | AffineTransform.TYPE_MASK_ROTATION 259 | AffineTransform.TYPE_FLIP; 260 261 static { 262 if (PerformanceLogger.loggingEnabled()) { 263 PerformanceLogger.setTime("SunGraphics2D static initialization"); 264 } 265 } 266 SunGraphics2D(SurfaceData sd, Color fg, Color bg, Font f)267 public SunGraphics2D(SurfaceData sd, Color fg, Color bg, Font f) { 268 surfaceData = sd; 269 foregroundColor = fg; 270 backgroundColor = bg; 271 272 transform = new AffineTransform(); 273 stroke = defaultStroke; 274 composite = defaultComposite; 275 paint = foregroundColor; 276 277 imageComp = CompositeType.SrcOverNoEa; 278 279 renderHint = SunHints.INTVAL_RENDER_DEFAULT; 280 antialiasHint = SunHints.INTVAL_ANTIALIAS_OFF; 281 textAntialiasHint = SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT; 282 fractionalMetricsHint = SunHints.INTVAL_FRACTIONALMETRICS_OFF; 283 lcdTextContrast = lcdTextContrastDefaultValue; 284 interpolationHint = -1; 285 strokeHint = SunHints.INTVAL_STROKE_DEFAULT; 286 resolutionVariantHint = SunHints.INTVAL_RESOLUTION_VARIANT_DEFAULT; 287 288 interpolationType = AffineTransformOp.TYPE_NEAREST_NEIGHBOR; 289 290 validateColor(); 291 292 devScale = sd.getDefaultScale(); 293 if (devScale != 1) { 294 transform.setToScale(devScale, devScale); 295 invalidateTransform(); 296 } 297 298 font = f; 299 if (font == null) { 300 font = defaultFont; 301 } 302 303 setDevClip(sd.getBounds()); 304 invalidatePipe(); 305 } 306 clone()307 protected Object clone() { 308 try { 309 SunGraphics2D g = (SunGraphics2D) super.clone(); 310 g.transform = new AffineTransform(this.transform); 311 if (hints != null) { 312 g.hints = (RenderingHints) this.hints.clone(); 313 } 314 /* FontInfos are re-used, so must be cloned too, if they 315 * are valid, and be nulled out if invalid. 316 * The implied trade-off is that there is more to be gained 317 * from re-using these objects than is lost by having to 318 * clone them when the SG2D is cloned. 319 */ 320 if (this.fontInfo != null) { 321 if (this.validFontInfo) { 322 g.fontInfo = (FontInfo)this.fontInfo.clone(); 323 } else { 324 g.fontInfo = null; 325 } 326 } 327 if (this.glyphVectorFontInfo != null) { 328 g.glyphVectorFontInfo = 329 (FontInfo)this.glyphVectorFontInfo.clone(); 330 g.glyphVectorFRC = this.glyphVectorFRC; 331 } 332 //g.invalidatePipe(); 333 return g; 334 } catch (CloneNotSupportedException e) { 335 } 336 return null; 337 } 338 339 /** 340 * Create a new SunGraphics2D based on this one. 341 */ create()342 public Graphics create() { 343 return (Graphics) clone(); 344 } 345 setDevClip(int x, int y, int w, int h)346 public void setDevClip(int x, int y, int w, int h) { 347 Region c = constrainClip; 348 if (c == null) { 349 devClip = Region.getInstanceXYWH(x, y, w, h); 350 } else { 351 devClip = c.getIntersectionXYWH(x, y, w, h); 352 } 353 validateCompClip(); 354 } 355 setDevClip(Rectangle r)356 public void setDevClip(Rectangle r) { 357 setDevClip(r.x, r.y, r.width, r.height); 358 } 359 360 /** 361 * Constrain rendering for lightweight objects. 362 */ constrain(int x, int y, int w, int h, Region region)363 public void constrain(int x, int y, int w, int h, Region region) { 364 if ((x | y) != 0) { 365 translate(x, y); 366 } 367 if (transformState > TRANSFORM_TRANSLATESCALE) { 368 clipRect(0, 0, w, h); 369 return; 370 } 371 // changes parameters according to the current scale and translate. 372 final double scaleX = transform.getScaleX(); 373 final double scaleY = transform.getScaleY(); 374 x = constrainX = (int) transform.getTranslateX(); 375 y = constrainY = (int) transform.getTranslateY(); 376 w = Region.dimAdd(x, Region.clipScale(w, scaleX)); 377 h = Region.dimAdd(y, Region.clipScale(h, scaleY)); 378 379 Region c = constrainClip; 380 if (c == null) { 381 c = Region.getInstanceXYXY(x, y, w, h); 382 } else { 383 c = c.getIntersectionXYXY(x, y, w, h); 384 } 385 if (region != null) { 386 region = region.getScaledRegion(scaleX, scaleY); 387 region = region.getTranslatedRegion(x, y); 388 c = c.getIntersection(region); 389 } 390 391 if (c == constrainClip) { 392 // Common case to ignore 393 return; 394 } 395 396 constrainClip = c; 397 if (!devClip.isInsideQuickCheck(c)) { 398 devClip = devClip.getIntersection(c); 399 validateCompClip(); 400 } 401 } 402 403 /** 404 * Constrain rendering for lightweight objects. 405 * 406 * REMIND: This method will back off to the "workaround" 407 * of using translate and clipRect if the Graphics 408 * to be constrained has a complex transform. The 409 * drawback of the workaround is that the resulting 410 * clip and device origin cannot be "enforced". 411 * 412 * @exception IllegalStateException If the Graphics 413 * to be constrained has a complex transform. 414 */ 415 @Override constrain(int x, int y, int w, int h)416 public void constrain(int x, int y, int w, int h) { 417 constrain(x, y, w, h, null); 418 } 419 420 protected static ValidatePipe invalidpipe = new ValidatePipe(); 421 422 /* 423 * Invalidate the pipeline 424 */ invalidatePipe()425 protected void invalidatePipe() { 426 drawpipe = invalidpipe; 427 fillpipe = invalidpipe; 428 shapepipe = invalidpipe; 429 textpipe = invalidpipe; 430 imagepipe = invalidpipe; 431 loops = null; 432 } 433 validatePipe()434 public void validatePipe() { 435 /* This workaround is for the situation when we update the Pipelines 436 * for invalid SurfaceData and run further code when the current 437 * pipeline doesn't support the type of new SurfaceData created during 438 * the current pipeline's work (in place of the invalid SurfaceData). 439 * Usually SurfaceData and Pipelines are repaired (through revalidateAll) 440 * and called again in the exception handlers */ 441 442 if (!surfaceData.isValid()) { 443 throw new InvalidPipeException("attempt to validate Pipe with invalid SurfaceData"); 444 } 445 446 surfaceData.validatePipe(this); 447 } 448 449 /* 450 * Intersect two Shapes by the simplest method, attempting to produce 451 * a simplified result. 452 * The boolean arguments keep1 and keep2 specify whether or not 453 * the first or second shapes can be modified during the operation 454 * or whether that shape must be "kept" unmodified. 455 */ intersectShapes(Shape s1, Shape s2, boolean keep1, boolean keep2)456 Shape intersectShapes(Shape s1, Shape s2, boolean keep1, boolean keep2) { 457 if (s1 instanceof Rectangle && s2 instanceof Rectangle) { 458 return ((Rectangle) s1).intersection((Rectangle) s2); 459 } 460 if (s1 instanceof Rectangle2D) { 461 return intersectRectShape((Rectangle2D) s1, s2, keep1, keep2); 462 } else if (s2 instanceof Rectangle2D) { 463 return intersectRectShape((Rectangle2D) s2, s1, keep2, keep1); 464 } 465 return intersectByArea(s1, s2, keep1, keep2); 466 } 467 468 /* 469 * Intersect a Rectangle with a Shape by the simplest method, 470 * attempting to produce a simplified result. 471 * The boolean arguments keep1 and keep2 specify whether or not 472 * the first or second shapes can be modified during the operation 473 * or whether that shape must be "kept" unmodified. 474 */ intersectRectShape(Rectangle2D r, Shape s, boolean keep1, boolean keep2)475 Shape intersectRectShape(Rectangle2D r, Shape s, 476 boolean keep1, boolean keep2) { 477 if (s instanceof Rectangle2D) { 478 Rectangle2D r2 = (Rectangle2D) s; 479 Rectangle2D outrect; 480 if (!keep1) { 481 outrect = r; 482 } else if (!keep2) { 483 outrect = r2; 484 } else { 485 outrect = new Rectangle2D.Float(); 486 } 487 double x1 = Math.max(r.getX(), r2.getX()); 488 double x2 = Math.min(r.getX() + r.getWidth(), 489 r2.getX() + r2.getWidth()); 490 double y1 = Math.max(r.getY(), r2.getY()); 491 double y2 = Math.min(r.getY() + r.getHeight(), 492 r2.getY() + r2.getHeight()); 493 494 if (((x2 - x1) < 0) || ((y2 - y1) < 0)) 495 // Width or height is negative. No intersection. 496 outrect.setFrameFromDiagonal(0, 0, 0, 0); 497 else 498 outrect.setFrameFromDiagonal(x1, y1, x2, y2); 499 return outrect; 500 } 501 if (r.contains(s.getBounds2D())) { 502 if (keep2) { 503 s = cloneShape(s); 504 } 505 return s; 506 } 507 return intersectByArea(r, s, keep1, keep2); 508 } 509 cloneShape(Shape s)510 protected static Shape cloneShape(Shape s) { 511 return new GeneralPath(s); 512 } 513 514 /* 515 * Intersect two Shapes using the Area class. Presumably other 516 * attempts at simpler intersection methods proved fruitless. 517 * The boolean arguments keep1 and keep2 specify whether or not 518 * the first or second shapes can be modified during the operation 519 * or whether that shape must be "kept" unmodified. 520 * @see #intersectShapes 521 * @see #intersectRectShape 522 */ intersectByArea(Shape s1, Shape s2, boolean keep1, boolean keep2)523 Shape intersectByArea(Shape s1, Shape s2, boolean keep1, boolean keep2) { 524 Area a1, a2; 525 526 // First see if we can find an overwriteable source shape 527 // to use as our destination area to avoid duplication. 528 if (!keep1 && (s1 instanceof Area)) { 529 a1 = (Area) s1; 530 } else if (!keep2 && (s2 instanceof Area)) { 531 a1 = (Area) s2; 532 s2 = s1; 533 } else { 534 a1 = new Area(s1); 535 } 536 537 if (s2 instanceof Area) { 538 a2 = (Area) s2; 539 } else { 540 a2 = new Area(s2); 541 } 542 543 a1.intersect(a2); 544 if (a1.isRectangular()) { 545 return a1.getBounds(); 546 } 547 548 return a1; 549 } 550 551 /* 552 * Intersect usrClip bounds and device bounds to determine the composite 553 * rendering boundaries. 554 */ getCompClip()555 public Region getCompClip() { 556 if (!surfaceData.isValid()) { 557 // revalidateAll() implicitly recalculcates the composite clip 558 revalidateAll(); 559 } 560 561 return clipRegion; 562 } 563 getFont()564 public Font getFont() { 565 if (font == null) { 566 font = defaultFont; 567 } 568 return font; 569 } 570 571 private static final double[] IDENT_MATRIX = {1, 0, 0, 1}; 572 private static final AffineTransform IDENT_ATX = 573 new AffineTransform(); 574 575 private static final int MINALLOCATED = 8; 576 private static final int TEXTARRSIZE = 17; 577 private static double[][] textTxArr = new double[TEXTARRSIZE][]; 578 private static AffineTransform[] textAtArr = 579 new AffineTransform[TEXTARRSIZE]; 580 581 static { 582 for (int i=MINALLOCATED;i<TEXTARRSIZE;i++) { 583 textTxArr[i] = new double [] {i, 0, 0, i}; 584 textAtArr[i] = new AffineTransform( textTxArr[i]); 585 } 586 } 587 588 // cached state for various draw[String,Char,Byte] optimizations checkFontInfo(FontInfo info, Font font, FontRenderContext frc)589 public FontInfo checkFontInfo(FontInfo info, Font font, 590 FontRenderContext frc) { 591 /* Do not create a FontInfo object as part of construction of an 592 * SG2D as its possible it may never be needed - ie if no text 593 * is drawn using this SG2D. 594 */ 595 if (info == null) { 596 info = new FontInfo(); 597 } 598 599 float ptSize = font.getSize2D(); 600 int txFontType; 601 AffineTransform devAt, textAt=null; 602 if (font.isTransformed()) { 603 textAt = font.getTransform(); 604 textAt.scale(ptSize, ptSize); 605 txFontType = textAt.getType(); 606 info.originX = (float)textAt.getTranslateX(); 607 info.originY = (float)textAt.getTranslateY(); 608 textAt.translate(-info.originX, -info.originY); 609 if (transformState >= TRANSFORM_TRANSLATESCALE) { 610 transform.getMatrix(info.devTx = new double[4]); 611 devAt = new AffineTransform(info.devTx); 612 textAt.preConcatenate(devAt); 613 } else { 614 info.devTx = IDENT_MATRIX; 615 devAt = IDENT_ATX; 616 } 617 textAt.getMatrix(info.glyphTx = new double[4]); 618 double shearx = textAt.getShearX(); 619 double scaley = textAt.getScaleY(); 620 if (shearx != 0) { 621 scaley = Math.sqrt(shearx * shearx + scaley * scaley); 622 } 623 info.pixelHeight = (int)(Math.abs(scaley)+0.5); 624 } else { 625 txFontType = AffineTransform.TYPE_IDENTITY; 626 info.originX = info.originY = 0; 627 if (transformState >= TRANSFORM_TRANSLATESCALE) { 628 transform.getMatrix(info.devTx = new double[4]); 629 devAt = new AffineTransform(info.devTx); 630 info.glyphTx = new double[4]; 631 for (int i = 0; i < 4; i++) { 632 info.glyphTx[i] = info.devTx[i] * ptSize; 633 } 634 textAt = new AffineTransform(info.glyphTx); 635 double shearx = transform.getShearX(); 636 double scaley = transform.getScaleY(); 637 if (shearx != 0) { 638 scaley = Math.sqrt(shearx * shearx + scaley * scaley); 639 } 640 info.pixelHeight = (int)(Math.abs(scaley * ptSize)+0.5); 641 } else { 642 /* If the double represents a common integral, we 643 * may have pre-allocated objects. 644 * A "sparse" array be seems to be as fast as a switch 645 * even for 3 or 4 pt sizes, and is more flexible. 646 * This should perform comparably in single-threaded 647 * rendering to the old code which synchronized on the 648 * class and scale better on MP systems. 649 */ 650 int pszInt = (int)ptSize; 651 if (ptSize == pszInt && 652 pszInt >= MINALLOCATED && pszInt < TEXTARRSIZE) { 653 info.glyphTx = textTxArr[pszInt]; 654 textAt = textAtArr[pszInt]; 655 info.pixelHeight = pszInt; 656 } else { 657 info.pixelHeight = (int)(ptSize+0.5); 658 } 659 if (textAt == null) { 660 info.glyphTx = new double[] {ptSize, 0, 0, ptSize}; 661 textAt = new AffineTransform(info.glyphTx); 662 } 663 664 info.devTx = IDENT_MATRIX; 665 devAt = IDENT_ATX; 666 } 667 } 668 669 info.font2D = FontUtilities.getFont2D(font); 670 671 int fmhint = fractionalMetricsHint; 672 if (fmhint == SunHints.INTVAL_FRACTIONALMETRICS_DEFAULT) { 673 fmhint = SunHints.INTVAL_FRACTIONALMETRICS_OFF; 674 } 675 info.lcdSubPixPos = false; // conditionally set true in LCD mode. 676 677 /* The text anti-aliasing hints that are set by the client need 678 * to be interpreted for the current state and stored in the 679 * FontInfo.aahint which is what will actually be used and 680 * will be one of OFF, ON, LCD_HRGB or LCD_VRGB. 681 * This is what pipe selection code should typically refer to, not 682 * textAntialiasHint. This means we are now evaluating the meaning 683 * of "default" here. Any pipe that really cares about that will 684 * also need to consult that variable. 685 * Otherwise these are being used only as args to getStrike, 686 * and are encapsulated in that object which is part of the 687 * FontInfo, so we do not need to store them directly as fields 688 * in the FontInfo object. 689 * That could change if FontInfo's were more selectively 690 * revalidated when graphics state changed. Presently this 691 * method re-evaluates all fields in the fontInfo. 692 * The strike doesn't need to know the RGB subpixel order. Just 693 * if its H or V orientation, so if an LCD option is specified we 694 * always pass in the RGB hint to the strike. 695 * frc is non-null only if this is a GlyphVector. For reasons 696 * which are probably a historical mistake the AA hint in a GV 697 * is honoured when we render, overriding the Graphics setting. 698 */ 699 int aahint; 700 if (frc == null) { 701 aahint = textAntialiasHint; 702 } else { 703 aahint = ((SunHints.Value)frc.getAntiAliasingHint()).getIndex(); 704 } 705 if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT) { 706 if (antialiasHint == SunHints.INTVAL_ANTIALIAS_ON) { 707 aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON; 708 } else { 709 aahint = SunHints.INTVAL_TEXT_ANTIALIAS_OFF; 710 } 711 } else { 712 /* If we are in checkFontInfo because a rendering hint has been 713 * set then all pipes are revalidated. But we can also 714 * be here because setFont() has been called when the 'gasp' 715 * hint is set, as then the font size determines the text pipe. 716 * See comments in SunGraphics2d.setFont(Font). 717 */ 718 if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_GASP) { 719 if (info.font2D.useAAForPtSize(info.pixelHeight)) { 720 aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON; 721 } else { 722 aahint = SunHints.INTVAL_TEXT_ANTIALIAS_OFF; 723 } 724 } else if (aahint >= SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HRGB) { 725 /* loops for default rendering modes are installed in the SG2D 726 * constructor. If there are none this will be null. 727 * Not all compositing modes update the render loops, so 728 * we also test that this is a mode we know should support 729 * this. One minor issue is that the loops aren't necessarily 730 * installed for a new rendering mode until after this 731 * method is called during pipeline validation. So it is 732 * theoretically possible that it was set to null for a 733 * compositing mode, the composite is then set back to Src, 734 * but the loop is still null when this is called and AA=ON 735 * is installed instead of an LCD mode. 736 * However this is done in the right order in SurfaceData.java 737 * so this is not likely to be a problem - but not 738 * guaranteed. 739 */ 740 if ( 741 !surfaceData.canRenderLCDText(this) 742 // loops.drawGlyphListLCDLoop == null || 743 // compositeState > COMP_ISCOPY || 744 // paintState > PAINT_ALPHACOLOR 745 ) { 746 aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON; 747 } else { 748 info.lcdRGBOrder = true; 749 /* Collapse these into just HRGB or VRGB. 750 * Pipe selection code needs only to test for these two. 751 * Since these both select the same pipe anyway its 752 * tempting to collapse into one value. But they are 753 * different strikes (glyph caches) so the distinction 754 * needs to be made for that purpose. 755 */ 756 if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HBGR) { 757 aahint = SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HRGB; 758 info.lcdRGBOrder = false; 759 } else if 760 (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_LCD_VBGR) { 761 aahint = SunHints.INTVAL_TEXT_ANTIALIAS_LCD_VRGB; 762 info.lcdRGBOrder = false; 763 } 764 /* Support subpixel positioning only for the case in 765 * which the horizontal resolution is increased 766 */ 767 info.lcdSubPixPos = 768 fmhint == SunHints.INTVAL_FRACTIONALMETRICS_ON && 769 aahint == SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HRGB; 770 } 771 } 772 } 773 info.aaHint = aahint; 774 info.fontStrike = info.font2D.getStrike(font, devAt, textAt, 775 aahint, fmhint); 776 return info; 777 } 778 isRotated(double [] mtx)779 public static boolean isRotated(double [] mtx) { 780 if ((mtx[0] == mtx[3]) && 781 (mtx[1] == 0.0) && 782 (mtx[2] == 0.0) && 783 (mtx[0] > 0.0)) 784 { 785 return false; 786 } 787 788 return true; 789 } 790 setFont(Font font)791 public void setFont(Font font) { 792 /* replacing the reference equality test font != this.font with 793 * !font.equals(this.font) did not yield any measurable difference 794 * in testing, but there may be yet to be identified cases where it 795 * is beneficial. 796 */ 797 if (font != null && font!=this.font/*!font.equals(this.font)*/) { 798 /* In the GASP AA case the textpipe depends on the glyph size 799 * as determined by graphics and font transforms as well as the 800 * font size, and information in the font. But we may invalidate 801 * the pipe only to find that it made no difference. 802 * Deferring pipe invalidation to checkFontInfo won't work because 803 * when called we may already be rendering to the wrong pipe. 804 * So, if the font is transformed, or the graphics has more than 805 * a simple scale, we'll take that as enough of a hint to 806 * revalidate everything. But if they aren't we will 807 * use the font's point size to query the gasp table and see if 808 * what it says matches what's currently being used, in which 809 * case there's no need to invalidate the textpipe. 810 * This should be sufficient for all typical uses cases. 811 */ 812 if (textAntialiasHint == SunHints.INTVAL_TEXT_ANTIALIAS_GASP && 813 textpipe != invalidpipe && 814 (transformState > TRANSFORM_ANY_TRANSLATE || 815 font.isTransformed() || 816 fontInfo == null || // Precaution, if true shouldn't get here 817 (fontInfo.aaHint == SunHints.INTVAL_TEXT_ANTIALIAS_ON) != 818 FontUtilities.getFont2D(font). 819 useAAForPtSize(font.getSize()))) { 820 textpipe = invalidpipe; 821 } 822 this.font = font; 823 this.fontMetrics = null; 824 this.validFontInfo = false; 825 } 826 } 827 getFontInfo()828 public FontInfo getFontInfo() { 829 if (!validFontInfo) { 830 this.fontInfo = checkFontInfo(this.fontInfo, font, null); 831 validFontInfo = true; 832 } 833 return this.fontInfo; 834 } 835 836 /* Used by drawGlyphVector which specifies its own font. */ getGVFontInfo(Font font, FontRenderContext frc)837 public FontInfo getGVFontInfo(Font font, FontRenderContext frc) { 838 if (glyphVectorFontInfo != null && 839 glyphVectorFontInfo.font == font && 840 glyphVectorFRC == frc) { 841 return glyphVectorFontInfo; 842 } else { 843 glyphVectorFRC = frc; 844 return glyphVectorFontInfo = 845 checkFontInfo(glyphVectorFontInfo, font, frc); 846 } 847 } 848 getFontMetrics()849 public FontMetrics getFontMetrics() { 850 if (this.fontMetrics != null) { 851 return this.fontMetrics; 852 } 853 /* NB the constructor and the setter disallow "font" being null */ 854 return this.fontMetrics = 855 FontDesignMetrics.getMetrics(font, getFontRenderContext()); 856 } 857 getFontMetrics(Font font)858 public FontMetrics getFontMetrics(Font font) { 859 if ((this.fontMetrics != null) && (font == this.font)) { 860 return this.fontMetrics; 861 } 862 FontMetrics fm = 863 FontDesignMetrics.getMetrics(font, getFontRenderContext()); 864 865 if (this.font == font) { 866 this.fontMetrics = fm; 867 } 868 return fm; 869 } 870 871 /** 872 * Checks to see if a Path intersects the specified Rectangle in device 873 * space. The rendering attributes taken into account include the 874 * clip, transform, and stroke attributes. 875 * @param rect The area in device space to check for a hit. 876 * @param p The path to check for a hit. 877 * @param onStroke Flag to choose between testing the stroked or 878 * the filled path. 879 * @return True if there is a hit, false otherwise. 880 * @see #setStroke 881 * @see #fillPath 882 * @see #drawPath 883 * @see #transform 884 * @see #setTransform 885 * @see #clip 886 * @see #setClip 887 */ hit(Rectangle rect, Shape s, boolean onStroke)888 public boolean hit(Rectangle rect, Shape s, boolean onStroke) { 889 if (onStroke) { 890 s = stroke.createStrokedShape(s); 891 } 892 893 s = transformShape(s); 894 if ((constrainX|constrainY) != 0) { 895 rect = new Rectangle(rect); 896 rect.translate(constrainX, constrainY); 897 } 898 899 return s.intersects(rect); 900 } 901 902 /** 903 * Return the ColorModel associated with this Graphics2D. 904 */ getDeviceColorModel()905 public ColorModel getDeviceColorModel() { 906 return surfaceData.getColorModel(); 907 } 908 909 /** 910 * Return the device configuration associated with this Graphics2D. 911 */ getDeviceConfiguration()912 public GraphicsConfiguration getDeviceConfiguration() { 913 return surfaceData.getDeviceConfiguration(); 914 } 915 916 /** 917 * Return the SurfaceData object assigned to manage the destination 918 * drawable surface of this Graphics2D. 919 */ getSurfaceData()920 public final SurfaceData getSurfaceData() { 921 return surfaceData; 922 } 923 924 /** 925 * Sets the Composite in the current graphics state. Composite is used 926 * in all drawing methods such as drawImage, drawString, drawPath, 927 * and fillPath. It specifies how new pixels are to be combined with 928 * the existing pixels on the graphics device in the rendering process. 929 * @param comp The Composite object to be used for drawing. 930 * @see java.awt.Graphics#setXORMode 931 * @see java.awt.Graphics#setPaintMode 932 * @see AlphaComposite 933 */ setComposite(Composite comp)934 public void setComposite(Composite comp) { 935 if (composite == comp) { 936 return; 937 } 938 int newCompState; 939 CompositeType newCompType; 940 if (comp instanceof AlphaComposite) { 941 AlphaComposite alphacomp = (AlphaComposite) comp; 942 newCompType = CompositeType.forAlphaComposite(alphacomp); 943 if (newCompType == CompositeType.SrcOverNoEa) { 944 if (paintState == PAINT_OPAQUECOLOR || 945 (paintState > PAINT_ALPHACOLOR && 946 paint.getTransparency() == Transparency.OPAQUE)) 947 { 948 newCompState = COMP_ISCOPY; 949 } else { 950 newCompState = COMP_ALPHA; 951 } 952 } else if (newCompType == CompositeType.SrcNoEa || 953 newCompType == CompositeType.Src || 954 newCompType == CompositeType.Clear) 955 { 956 newCompState = COMP_ISCOPY; 957 } else if (surfaceData.getTransparency() == Transparency.OPAQUE && 958 newCompType == CompositeType.SrcIn) 959 { 960 newCompState = COMP_ISCOPY; 961 } else { 962 newCompState = COMP_ALPHA; 963 } 964 } else if (comp instanceof XORComposite) { 965 newCompState = COMP_XOR; 966 newCompType = CompositeType.Xor; 967 } else if (comp == null) { 968 throw new IllegalArgumentException("null Composite"); 969 } else { 970 surfaceData.checkCustomComposite(); 971 newCompState = COMP_CUSTOM; 972 newCompType = CompositeType.General; 973 } 974 if (compositeState != newCompState || 975 imageComp != newCompType) 976 { 977 compositeState = newCompState; 978 imageComp = newCompType; 979 invalidatePipe(); 980 validFontInfo = false; 981 } 982 composite = comp; 983 if (paintState <= PAINT_ALPHACOLOR) { 984 validateColor(); 985 } 986 } 987 988 /** 989 * Sets the Paint in the current graphics state. 990 * @param paint The Paint object to be used to generate color in 991 * the rendering process. 992 * @see java.awt.Graphics#setColor 993 * @see GradientPaint 994 * @see TexturePaint 995 */ setPaint(Paint paint)996 public void setPaint(Paint paint) { 997 if (paint instanceof Color) { 998 setColor((Color) paint); 999 return; 1000 } 1001 if (paint == null || this.paint == paint) { 1002 return; 1003 } 1004 this.paint = paint; 1005 if (imageComp == CompositeType.SrcOverNoEa) { 1006 // special case where compState depends on opacity of paint 1007 if (paint.getTransparency() == Transparency.OPAQUE) { 1008 if (compositeState != COMP_ISCOPY) { 1009 compositeState = COMP_ISCOPY; 1010 } 1011 } else { 1012 if (compositeState == COMP_ISCOPY) { 1013 compositeState = COMP_ALPHA; 1014 } 1015 } 1016 } 1017 Class<? extends Paint> paintClass = paint.getClass(); 1018 if (paintClass == GradientPaint.class) { 1019 paintState = PAINT_GRADIENT; 1020 } else if (paintClass == LinearGradientPaint.class) { 1021 paintState = PAINT_LIN_GRADIENT; 1022 } else if (paintClass == RadialGradientPaint.class) { 1023 paintState = PAINT_RAD_GRADIENT; 1024 } else if (paintClass == TexturePaint.class) { 1025 paintState = PAINT_TEXTURE; 1026 } else { 1027 paintState = PAINT_CUSTOM; 1028 } 1029 validFontInfo = false; 1030 invalidatePipe(); 1031 } 1032 1033 static final int NON_UNIFORM_SCALE_MASK = 1034 (AffineTransform.TYPE_GENERAL_TRANSFORM | 1035 AffineTransform.TYPE_GENERAL_SCALE); 1036 public static final double MinPenSizeAA = 1037 sun.java2d.pipe.RenderingEngine.getInstance().getMinimumAAPenSize(); 1038 public static final double MinPenSizeAASquared = 1039 (MinPenSizeAA * MinPenSizeAA); 1040 // Since inaccuracies in the trig package can cause us to 1041 // calculated a rotated pen width of just slightly greater 1042 // than 1.0, we add a fudge factor to our comparison value 1043 // here so that we do not misclassify single width lines as 1044 // wide lines under certain rotations. 1045 public static final double MinPenSizeSquared = 1.000000001; 1046 validateBasicStroke(BasicStroke bs)1047 private void validateBasicStroke(BasicStroke bs) { 1048 boolean aa = (antialiasHint == SunHints.INTVAL_ANTIALIAS_ON); 1049 if (transformState < TRANSFORM_TRANSLATESCALE) { 1050 if (aa) { 1051 if (bs.getLineWidth() <= MinPenSizeAA) { 1052 if (bs.getDashArray() == null) { 1053 strokeState = STROKE_THIN; 1054 } else { 1055 strokeState = STROKE_THINDASHED; 1056 } 1057 } else { 1058 strokeState = STROKE_WIDE; 1059 } 1060 } else { 1061 if (bs == defaultStroke) { 1062 strokeState = STROKE_THIN; 1063 } else if (bs.getLineWidth() <= 1.0f) { 1064 if (bs.getDashArray() == null) { 1065 strokeState = STROKE_THIN; 1066 } else { 1067 strokeState = STROKE_THINDASHED; 1068 } 1069 } else { 1070 strokeState = STROKE_WIDE; 1071 } 1072 } 1073 } else { 1074 double widthsquared; 1075 if ((transform.getType() & NON_UNIFORM_SCALE_MASK) == 0) { 1076 /* sqrt omitted, compare to squared limits below. */ 1077 widthsquared = Math.abs(transform.getDeterminant()); 1078 } else { 1079 /* First calculate the "maximum scale" of this transform. */ 1080 double A = transform.getScaleX(); // m00 1081 double C = transform.getShearX(); // m01 1082 double B = transform.getShearY(); // m10 1083 double D = transform.getScaleY(); // m11 1084 1085 /* 1086 * Given a 2 x 2 affine matrix [ A B ] such that 1087 * [ C D ] 1088 * v' = [x' y'] = [Ax + Cy, Bx + Dy], we want to 1089 * find the maximum magnitude (norm) of the vector v' 1090 * with the constraint (x^2 + y^2 = 1). 1091 * The equation to maximize is 1092 * |v'| = sqrt((Ax+Cy)^2+(Bx+Dy)^2) 1093 * or |v'| = sqrt((AA+BB)x^2 + 2(AC+BD)xy + (CC+DD)y^2). 1094 * Since sqrt is monotonic we can maximize |v'|^2 1095 * instead and plug in the substitution y = sqrt(1 - x^2). 1096 * Trigonometric equalities can then be used to get 1097 * rid of most of the sqrt terms. 1098 */ 1099 double EA = A*A + B*B; // x^2 coefficient 1100 double EB = 2*(A*C + B*D); // xy coefficient 1101 double EC = C*C + D*D; // y^2 coefficient 1102 1103 /* 1104 * There is a lot of calculus omitted here. 1105 * 1106 * Conceptually, in the interests of understanding the 1107 * terms that the calculus produced we can consider 1108 * that EA and EC end up providing the lengths along 1109 * the major axes and the hypot term ends up being an 1110 * adjustment for the additional length along the off-axis 1111 * angle of rotated or sheared ellipses as well as an 1112 * adjustment for the fact that the equation below 1113 * averages the two major axis lengths. (Notice that 1114 * the hypot term contains a part which resolves to the 1115 * difference of these two axis lengths in the absence 1116 * of rotation.) 1117 * 1118 * In the calculus, the ratio of the EB and (EA-EC) terms 1119 * ends up being the tangent of 2*theta where theta is 1120 * the angle that the long axis of the ellipse makes 1121 * with the horizontal axis. Thus, this equation is 1122 * calculating the length of the hypotenuse of a triangle 1123 * along that axis. 1124 */ 1125 double hypot = Math.sqrt(EB*EB + (EA-EC)*(EA-EC)); 1126 1127 /* sqrt omitted, compare to squared limits below. */ 1128 widthsquared = ((EA + EC + hypot)/2.0); 1129 } 1130 if (bs != defaultStroke) { 1131 widthsquared *= bs.getLineWidth() * bs.getLineWidth(); 1132 } 1133 if (widthsquared <= 1134 (aa ? MinPenSizeAASquared : MinPenSizeSquared)) 1135 { 1136 if (bs.getDashArray() == null) { 1137 strokeState = STROKE_THIN; 1138 } else { 1139 strokeState = STROKE_THINDASHED; 1140 } 1141 } else { 1142 strokeState = STROKE_WIDE; 1143 } 1144 } 1145 } 1146 1147 /* 1148 * Sets the Stroke in the current graphics state. 1149 * @param s The Stroke object to be used to stroke a Path in 1150 * the rendering process. 1151 * @see BasicStroke 1152 */ setStroke(Stroke s)1153 public void setStroke(Stroke s) { 1154 if (s == null) { 1155 throw new IllegalArgumentException("null Stroke"); 1156 } 1157 int saveStrokeState = strokeState; 1158 stroke = s; 1159 if (s instanceof BasicStroke) { 1160 validateBasicStroke((BasicStroke) s); 1161 } else { 1162 strokeState = STROKE_CUSTOM; 1163 } 1164 if (strokeState != saveStrokeState) { 1165 invalidatePipe(); 1166 } 1167 } 1168 1169 /** 1170 * Sets the preferences for the rendering algorithms. 1171 * Hint categories include controls for rendering quality and 1172 * overall time/quality trade-off in the rendering process. 1173 * @param hintKey The key of hint to be set. The strings are 1174 * defined in the RenderingHints class. 1175 * @param hintValue The value indicating preferences for the specified 1176 * hint category. These strings are defined in the RenderingHints 1177 * class. 1178 * @see RenderingHints 1179 */ setRenderingHint(Key hintKey, Object hintValue)1180 public void setRenderingHint(Key hintKey, Object hintValue) { 1181 // If we recognize the key, we must recognize the value 1182 // otherwise throw an IllegalArgumentException 1183 // and do not change the Hints object 1184 // If we do not recognize the key, just pass it through 1185 // to the Hints object untouched 1186 if (!hintKey.isCompatibleValue(hintValue)) { 1187 throw new IllegalArgumentException 1188 (hintValue+" is not compatible with "+hintKey); 1189 } 1190 if (hintKey instanceof SunHints.Key) { 1191 boolean stateChanged; 1192 boolean textStateChanged = false; 1193 boolean recognized = true; 1194 SunHints.Key sunKey = (SunHints.Key) hintKey; 1195 int newHint; 1196 if (sunKey == SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST) { 1197 newHint = ((Integer)hintValue).intValue(); 1198 } else { 1199 newHint = ((SunHints.Value) hintValue).getIndex(); 1200 } 1201 switch (sunKey.getIndex()) { 1202 case SunHints.INTKEY_RENDERING: 1203 stateChanged = (renderHint != newHint); 1204 if (stateChanged) { 1205 renderHint = newHint; 1206 if (interpolationHint == -1) { 1207 interpolationType = 1208 (newHint == SunHints.INTVAL_RENDER_QUALITY 1209 ? AffineTransformOp.TYPE_BILINEAR 1210 : AffineTransformOp.TYPE_NEAREST_NEIGHBOR); 1211 } 1212 } 1213 break; 1214 case SunHints.INTKEY_ANTIALIASING: 1215 stateChanged = (antialiasHint != newHint); 1216 antialiasHint = newHint; 1217 if (stateChanged) { 1218 textStateChanged = 1219 (textAntialiasHint == 1220 SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT); 1221 if (strokeState != STROKE_CUSTOM) { 1222 validateBasicStroke((BasicStroke) stroke); 1223 } 1224 } 1225 break; 1226 case SunHints.INTKEY_TEXT_ANTIALIASING: 1227 stateChanged = (textAntialiasHint != newHint); 1228 textStateChanged = stateChanged; 1229 textAntialiasHint = newHint; 1230 break; 1231 case SunHints.INTKEY_FRACTIONALMETRICS: 1232 stateChanged = (fractionalMetricsHint != newHint); 1233 textStateChanged = stateChanged; 1234 fractionalMetricsHint = newHint; 1235 break; 1236 case SunHints.INTKEY_AATEXT_LCD_CONTRAST: 1237 stateChanged = false; 1238 /* Already have validated it is an int 100 <= newHint <= 250 */ 1239 lcdTextContrast = newHint; 1240 break; 1241 case SunHints.INTKEY_INTERPOLATION: 1242 interpolationHint = newHint; 1243 switch (newHint) { 1244 case SunHints.INTVAL_INTERPOLATION_BICUBIC: 1245 newHint = AffineTransformOp.TYPE_BICUBIC; 1246 break; 1247 case SunHints.INTVAL_INTERPOLATION_BILINEAR: 1248 newHint = AffineTransformOp.TYPE_BILINEAR; 1249 break; 1250 default: 1251 case SunHints.INTVAL_INTERPOLATION_NEAREST_NEIGHBOR: 1252 newHint = AffineTransformOp.TYPE_NEAREST_NEIGHBOR; 1253 break; 1254 } 1255 stateChanged = (interpolationType != newHint); 1256 interpolationType = newHint; 1257 break; 1258 case SunHints.INTKEY_STROKE_CONTROL: 1259 stateChanged = (strokeHint != newHint); 1260 strokeHint = newHint; 1261 break; 1262 case SunHints.INTKEY_RESOLUTION_VARIANT: 1263 stateChanged = (resolutionVariantHint != newHint); 1264 resolutionVariantHint = newHint; 1265 break; 1266 default: 1267 recognized = false; 1268 stateChanged = false; 1269 break; 1270 } 1271 if (recognized) { 1272 if (stateChanged) { 1273 invalidatePipe(); 1274 if (textStateChanged) { 1275 fontMetrics = null; 1276 this.cachedFRC = null; 1277 validFontInfo = false; 1278 this.glyphVectorFontInfo = null; 1279 } 1280 } 1281 if (hints != null) { 1282 hints.put(hintKey, hintValue); 1283 } 1284 return; 1285 } 1286 } 1287 // Nothing we recognize so none of "our state" has changed 1288 if (hints == null) { 1289 hints = makeHints(null); 1290 } 1291 hints.put(hintKey, hintValue); 1292 } 1293 1294 1295 /** 1296 * Returns the preferences for the rendering algorithms. 1297 * @param hintCategory The category of hint to be set. The strings 1298 * are defined in the RenderingHints class. 1299 * @return The preferences for rendering algorithms. The strings 1300 * are defined in the RenderingHints class. 1301 * @see RenderingHints 1302 */ getRenderingHint(Key hintKey)1303 public Object getRenderingHint(Key hintKey) { 1304 if (hints != null) { 1305 return hints.get(hintKey); 1306 } 1307 if (!(hintKey instanceof SunHints.Key)) { 1308 return null; 1309 } 1310 int keyindex = ((SunHints.Key)hintKey).getIndex(); 1311 switch (keyindex) { 1312 case SunHints.INTKEY_RENDERING: 1313 return SunHints.Value.get(SunHints.INTKEY_RENDERING, 1314 renderHint); 1315 case SunHints.INTKEY_ANTIALIASING: 1316 return SunHints.Value.get(SunHints.INTKEY_ANTIALIASING, 1317 antialiasHint); 1318 case SunHints.INTKEY_TEXT_ANTIALIASING: 1319 return SunHints.Value.get(SunHints.INTKEY_TEXT_ANTIALIASING, 1320 textAntialiasHint); 1321 case SunHints.INTKEY_FRACTIONALMETRICS: 1322 return SunHints.Value.get(SunHints.INTKEY_FRACTIONALMETRICS, 1323 fractionalMetricsHint); 1324 case SunHints.INTKEY_AATEXT_LCD_CONTRAST: 1325 return new Integer(lcdTextContrast); 1326 case SunHints.INTKEY_INTERPOLATION: 1327 switch (interpolationHint) { 1328 case SunHints.INTVAL_INTERPOLATION_NEAREST_NEIGHBOR: 1329 return SunHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR; 1330 case SunHints.INTVAL_INTERPOLATION_BILINEAR: 1331 return SunHints.VALUE_INTERPOLATION_BILINEAR; 1332 case SunHints.INTVAL_INTERPOLATION_BICUBIC: 1333 return SunHints.VALUE_INTERPOLATION_BICUBIC; 1334 } 1335 return null; 1336 case SunHints.INTKEY_STROKE_CONTROL: 1337 return SunHints.Value.get(SunHints.INTKEY_STROKE_CONTROL, 1338 strokeHint); 1339 case SunHints.INTKEY_RESOLUTION_VARIANT: 1340 return SunHints.Value.get(SunHints.INTKEY_RESOLUTION_VARIANT, 1341 resolutionVariantHint); 1342 } 1343 return null; 1344 } 1345 1346 /** 1347 * Sets the preferences for the rendering algorithms. 1348 * Hint categories include controls for rendering quality and 1349 * overall time/quality trade-off in the rendering process. 1350 * @param hints The rendering hints to be set 1351 * @see RenderingHints 1352 */ setRenderingHints(Map<?,?> hints)1353 public void setRenderingHints(Map<?,?> hints) { 1354 this.hints = null; 1355 renderHint = SunHints.INTVAL_RENDER_DEFAULT; 1356 antialiasHint = SunHints.INTVAL_ANTIALIAS_OFF; 1357 textAntialiasHint = SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT; 1358 fractionalMetricsHint = SunHints.INTVAL_FRACTIONALMETRICS_OFF; 1359 lcdTextContrast = lcdTextContrastDefaultValue; 1360 interpolationHint = -1; 1361 interpolationType = AffineTransformOp.TYPE_NEAREST_NEIGHBOR; 1362 boolean customHintPresent = false; 1363 Iterator<?> iter = hints.keySet().iterator(); 1364 while (iter.hasNext()) { 1365 Object key = iter.next(); 1366 if (key == SunHints.KEY_RENDERING || 1367 key == SunHints.KEY_ANTIALIASING || 1368 key == SunHints.KEY_TEXT_ANTIALIASING || 1369 key == SunHints.KEY_FRACTIONALMETRICS || 1370 key == SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST || 1371 key == SunHints.KEY_STROKE_CONTROL || 1372 key == SunHints.KEY_INTERPOLATION) 1373 { 1374 setRenderingHint((Key) key, hints.get(key)); 1375 } else { 1376 customHintPresent = true; 1377 } 1378 } 1379 if (customHintPresent) { 1380 this.hints = makeHints(hints); 1381 } 1382 invalidatePipe(); 1383 } 1384 1385 /** 1386 * Adds a number of preferences for the rendering algorithms. 1387 * Hint categories include controls for rendering quality and 1388 * overall time/quality trade-off in the rendering process. 1389 * @param hints The rendering hints to be set 1390 * @see RenderingHints 1391 */ addRenderingHints(Map<?,?> hints)1392 public void addRenderingHints(Map<?,?> hints) { 1393 boolean customHintPresent = false; 1394 Iterator<?> iter = hints.keySet().iterator(); 1395 while (iter.hasNext()) { 1396 Object key = iter.next(); 1397 if (key == SunHints.KEY_RENDERING || 1398 key == SunHints.KEY_ANTIALIASING || 1399 key == SunHints.KEY_TEXT_ANTIALIASING || 1400 key == SunHints.KEY_FRACTIONALMETRICS || 1401 key == SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST || 1402 key == SunHints.KEY_STROKE_CONTROL || 1403 key == SunHints.KEY_INTERPOLATION) 1404 { 1405 setRenderingHint((Key) key, hints.get(key)); 1406 } else { 1407 customHintPresent = true; 1408 } 1409 } 1410 if (customHintPresent) { 1411 if (this.hints == null) { 1412 this.hints = makeHints(hints); 1413 } else { 1414 this.hints.putAll(hints); 1415 } 1416 } 1417 } 1418 1419 /** 1420 * Gets the preferences for the rendering algorithms. 1421 * Hint categories include controls for rendering quality and 1422 * overall time/quality trade-off in the rendering process. 1423 * @see RenderingHints 1424 */ getRenderingHints()1425 public RenderingHints getRenderingHints() { 1426 if (hints == null) { 1427 return makeHints(null); 1428 } else { 1429 return (RenderingHints) hints.clone(); 1430 } 1431 } 1432 makeHints(Map hints)1433 RenderingHints makeHints(Map hints) { 1434 RenderingHints model = new RenderingHints(hints); 1435 model.put(SunHints.KEY_RENDERING, 1436 SunHints.Value.get(SunHints.INTKEY_RENDERING, 1437 renderHint)); 1438 model.put(SunHints.KEY_ANTIALIASING, 1439 SunHints.Value.get(SunHints.INTKEY_ANTIALIASING, 1440 antialiasHint)); 1441 model.put(SunHints.KEY_TEXT_ANTIALIASING, 1442 SunHints.Value.get(SunHints.INTKEY_TEXT_ANTIALIASING, 1443 textAntialiasHint)); 1444 model.put(SunHints.KEY_FRACTIONALMETRICS, 1445 SunHints.Value.get(SunHints.INTKEY_FRACTIONALMETRICS, 1446 fractionalMetricsHint)); 1447 model.put(SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST, 1448 Integer.valueOf(lcdTextContrast)); 1449 Object value; 1450 switch (interpolationHint) { 1451 case SunHints.INTVAL_INTERPOLATION_NEAREST_NEIGHBOR: 1452 value = SunHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR; 1453 break; 1454 case SunHints.INTVAL_INTERPOLATION_BILINEAR: 1455 value = SunHints.VALUE_INTERPOLATION_BILINEAR; 1456 break; 1457 case SunHints.INTVAL_INTERPOLATION_BICUBIC: 1458 value = SunHints.VALUE_INTERPOLATION_BICUBIC; 1459 break; 1460 default: 1461 value = null; 1462 break; 1463 } 1464 if (value != null) { 1465 model.put(SunHints.KEY_INTERPOLATION, value); 1466 } 1467 model.put(SunHints.KEY_STROKE_CONTROL, 1468 SunHints.Value.get(SunHints.INTKEY_STROKE_CONTROL, 1469 strokeHint)); 1470 return model; 1471 } 1472 1473 /** 1474 * Concatenates the current transform of this Graphics2D with a 1475 * translation transformation. 1476 * This is equivalent to calling transform(T), where T is an 1477 * AffineTransform represented by the following matrix: 1478 * <pre> 1479 * [ 1 0 tx ] 1480 * [ 0 1 ty ] 1481 * [ 0 0 1 ] 1482 * </pre> 1483 */ translate(double tx, double ty)1484 public void translate(double tx, double ty) { 1485 transform.translate(tx, ty); 1486 invalidateTransform(); 1487 } 1488 1489 /** 1490 * Concatenates the current transform of this Graphics2D with a 1491 * rotation transformation. 1492 * This is equivalent to calling transform(R), where R is an 1493 * AffineTransform represented by the following matrix: 1494 * <pre> 1495 * [ cos(theta) -sin(theta) 0 ] 1496 * [ sin(theta) cos(theta) 0 ] 1497 * [ 0 0 1 ] 1498 * </pre> 1499 * Rotating with a positive angle theta rotates points on the positive 1500 * x axis toward the positive y axis. 1501 * @param theta The angle of rotation in radians. 1502 */ rotate(double theta)1503 public void rotate(double theta) { 1504 transform.rotate(theta); 1505 invalidateTransform(); 1506 } 1507 1508 /** 1509 * Concatenates the current transform of this Graphics2D with a 1510 * translated rotation transformation. 1511 * This is equivalent to the following sequence of calls: 1512 * <pre> 1513 * translate(x, y); 1514 * rotate(theta); 1515 * translate(-x, -y); 1516 * </pre> 1517 * Rotating with a positive angle theta rotates points on the positive 1518 * x axis toward the positive y axis. 1519 * @param theta The angle of rotation in radians. 1520 * @param x The x coordinate of the origin of the rotation 1521 * @param y The x coordinate of the origin of the rotation 1522 */ rotate(double theta, double x, double y)1523 public void rotate(double theta, double x, double y) { 1524 transform.rotate(theta, x, y); 1525 invalidateTransform(); 1526 } 1527 1528 /** 1529 * Concatenates the current transform of this Graphics2D with a 1530 * scaling transformation. 1531 * This is equivalent to calling transform(S), where S is an 1532 * AffineTransform represented by the following matrix: 1533 * <pre> 1534 * [ sx 0 0 ] 1535 * [ 0 sy 0 ] 1536 * [ 0 0 1 ] 1537 * </pre> 1538 */ scale(double sx, double sy)1539 public void scale(double sx, double sy) { 1540 transform.scale(sx, sy); 1541 invalidateTransform(); 1542 } 1543 1544 /** 1545 * Concatenates the current transform of this Graphics2D with a 1546 * shearing transformation. 1547 * This is equivalent to calling transform(SH), where SH is an 1548 * AffineTransform represented by the following matrix: 1549 * <pre> 1550 * [ 1 shx 0 ] 1551 * [ shy 1 0 ] 1552 * [ 0 0 1 ] 1553 * </pre> 1554 * @param shx The factor by which coordinates are shifted towards the 1555 * positive X axis direction according to their Y coordinate 1556 * @param shy The factor by which coordinates are shifted towards the 1557 * positive Y axis direction according to their X coordinate 1558 */ shear(double shx, double shy)1559 public void shear(double shx, double shy) { 1560 transform.shear(shx, shy); 1561 invalidateTransform(); 1562 } 1563 1564 /** 1565 * Composes a Transform object with the transform in this 1566 * Graphics2D according to the rule last-specified-first-applied. 1567 * If the currrent transform is Cx, the result of composition 1568 * with Tx is a new transform Cx'. Cx' becomes the current 1569 * transform for this Graphics2D. 1570 * Transforming a point p by the updated transform Cx' is 1571 * equivalent to first transforming p by Tx and then transforming 1572 * the result by the original transform Cx. In other words, 1573 * Cx'(p) = Cx(Tx(p)). 1574 * A copy of the Tx is made, if necessary, so further 1575 * modifications to Tx do not affect rendering. 1576 * @param Tx The Transform object to be composed with the current 1577 * transform. 1578 * @see #setTransform 1579 * @see AffineTransform 1580 */ transform(AffineTransform xform)1581 public void transform(AffineTransform xform) { 1582 this.transform.concatenate(xform); 1583 invalidateTransform(); 1584 } 1585 1586 /** 1587 * Translate 1588 */ translate(int x, int y)1589 public void translate(int x, int y) { 1590 transform.translate(x, y); 1591 if (transformState <= TRANSFORM_INT_TRANSLATE) { 1592 transX += x; 1593 transY += y; 1594 transformState = (((transX | transY) == 0) ? 1595 TRANSFORM_ISIDENT : TRANSFORM_INT_TRANSLATE); 1596 } else { 1597 invalidateTransform(); 1598 } 1599 } 1600 1601 /** 1602 * Sets the Transform in the current graphics state. 1603 * @param Tx The Transform object to be used in the rendering process. 1604 * @see #transform 1605 * @see TransformChain 1606 * @see AffineTransform 1607 */ 1608 @Override setTransform(AffineTransform Tx)1609 public void setTransform(AffineTransform Tx) { 1610 if ((constrainX | constrainY) == 0 && devScale == 1) { 1611 transform.setTransform(Tx); 1612 } else { 1613 transform.setTransform(devScale, 0, 0, devScale, constrainX, 1614 constrainY); 1615 transform.concatenate(Tx); 1616 } 1617 invalidateTransform(); 1618 } 1619 invalidateTransform()1620 protected void invalidateTransform() { 1621 int type = transform.getType(); 1622 int origTransformState = transformState; 1623 if (type == AffineTransform.TYPE_IDENTITY) { 1624 transformState = TRANSFORM_ISIDENT; 1625 transX = transY = 0; 1626 } else if (type == AffineTransform.TYPE_TRANSLATION) { 1627 double dtx = transform.getTranslateX(); 1628 double dty = transform.getTranslateY(); 1629 transX = (int) Math.floor(dtx + 0.5); 1630 transY = (int) Math.floor(dty + 0.5); 1631 if (dtx == transX && dty == transY) { 1632 transformState = TRANSFORM_INT_TRANSLATE; 1633 } else { 1634 transformState = TRANSFORM_ANY_TRANSLATE; 1635 } 1636 } else if ((type & (AffineTransform.TYPE_FLIP | 1637 AffineTransform.TYPE_MASK_ROTATION | 1638 AffineTransform.TYPE_GENERAL_TRANSFORM)) == 0) 1639 { 1640 transformState = TRANSFORM_TRANSLATESCALE; 1641 transX = transY = 0; 1642 } else { 1643 transformState = TRANSFORM_GENERIC; 1644 transX = transY = 0; 1645 } 1646 1647 if (transformState >= TRANSFORM_TRANSLATESCALE || 1648 origTransformState >= TRANSFORM_TRANSLATESCALE) 1649 { 1650 /* Its only in this case that the previous or current transform 1651 * was more than a translate that font info is invalidated 1652 */ 1653 cachedFRC = null; 1654 this.validFontInfo = false; 1655 this.fontMetrics = null; 1656 this.glyphVectorFontInfo = null; 1657 1658 if (transformState != origTransformState) { 1659 invalidatePipe(); 1660 } 1661 } 1662 if (strokeState != STROKE_CUSTOM) { 1663 validateBasicStroke((BasicStroke) stroke); 1664 } 1665 } 1666 1667 /** 1668 * Returns the current Transform in the Graphics2D state. 1669 * @see #transform 1670 * @see #setTransform 1671 */ 1672 @Override getTransform()1673 public AffineTransform getTransform() { 1674 if ((constrainX | constrainY) == 0 && devScale == 1) { 1675 return new AffineTransform(transform); 1676 } 1677 final double invScale = 1.0 / devScale; 1678 AffineTransform tx = new AffineTransform(invScale, 0, 0, invScale, 1679 -constrainX * invScale, 1680 -constrainY * invScale); 1681 tx.concatenate(transform); 1682 return tx; 1683 } 1684 1685 /** 1686 * Returns the current Transform ignoring the "constrain" 1687 * rectangle. 1688 */ cloneTransform()1689 public AffineTransform cloneTransform() { 1690 return new AffineTransform(transform); 1691 } 1692 1693 /** 1694 * Returns the current Paint in the Graphics2D state. 1695 * @see #setPaint 1696 * @see java.awt.Graphics#setColor 1697 */ getPaint()1698 public Paint getPaint() { 1699 return paint; 1700 } 1701 1702 /** 1703 * Returns the current Composite in the Graphics2D state. 1704 * @see #setComposite 1705 */ getComposite()1706 public Composite getComposite() { 1707 return composite; 1708 } 1709 getColor()1710 public Color getColor() { 1711 return foregroundColor; 1712 } 1713 1714 /* 1715 * Validate the eargb and pixel fields against the current color. 1716 * 1717 * The eargb field must take into account the extraAlpha 1718 * value of an AlphaComposite. It may also take into account 1719 * the Fsrc Porter-Duff blending function if such a function is 1720 * a constant (see handling of Clear mode below). For instance, 1721 * by factoring in the (Fsrc == 0) state of the Clear mode we can 1722 * use a SrcNoEa loop just as easily as a general Alpha loop 1723 * since the math will be the same in both cases. 1724 * 1725 * The pixel field will always be the best pixel data choice for 1726 * the final result of all calculations applied to the eargb field. 1727 * 1728 * Note that this method is only necessary under the following 1729 * conditions: 1730 * (paintState <= PAINT_ALPHA_COLOR && 1731 * compositeState <= COMP_CUSTOM) 1732 * though nothing bad will happen if it is run in other states. 1733 */ validateColor()1734 final void validateColor() { 1735 int eargb; 1736 if (imageComp == CompositeType.Clear) { 1737 eargb = 0; 1738 } else { 1739 eargb = foregroundColor.getRGB(); 1740 if (compositeState <= COMP_ALPHA && 1741 imageComp != CompositeType.SrcNoEa && 1742 imageComp != CompositeType.SrcOverNoEa) 1743 { 1744 AlphaComposite alphacomp = (AlphaComposite) composite; 1745 int a = Math.round(alphacomp.getAlpha() * (eargb >>> 24)); 1746 eargb = (eargb & 0x00ffffff) | (a << 24); 1747 } 1748 } 1749 this.eargb = eargb; 1750 this.pixel = surfaceData.pixelFor(eargb); 1751 } 1752 setColor(Color color)1753 public void setColor(Color color) { 1754 if (color == null || color == paint) { 1755 return; 1756 } 1757 this.paint = foregroundColor = color; 1758 validateColor(); 1759 if ((eargb >> 24) == -1) { 1760 if (paintState == PAINT_OPAQUECOLOR) { 1761 return; 1762 } 1763 paintState = PAINT_OPAQUECOLOR; 1764 if (imageComp == CompositeType.SrcOverNoEa) { 1765 // special case where compState depends on opacity of paint 1766 compositeState = COMP_ISCOPY; 1767 } 1768 } else { 1769 if (paintState == PAINT_ALPHACOLOR) { 1770 return; 1771 } 1772 paintState = PAINT_ALPHACOLOR; 1773 if (imageComp == CompositeType.SrcOverNoEa) { 1774 // special case where compState depends on opacity of paint 1775 compositeState = COMP_ALPHA; 1776 } 1777 } 1778 validFontInfo = false; 1779 invalidatePipe(); 1780 } 1781 1782 /** 1783 * Sets the background color in this context used for clearing a region. 1784 * When Graphics2D is constructed for a component, the backgroung color is 1785 * inherited from the component. Setting the background color in the 1786 * Graphics2D context only affects the subsequent clearRect() calls and 1787 * not the background color of the component. To change the background 1788 * of the component, use appropriate methods of the component. 1789 * @param color The background color that should be used in 1790 * subsequent calls to clearRect(). 1791 * @see getBackground 1792 * @see Graphics.clearRect() 1793 */ setBackground(Color color)1794 public void setBackground(Color color) { 1795 backgroundColor = color; 1796 } 1797 1798 /** 1799 * Returns the background color used for clearing a region. 1800 * @see setBackground 1801 */ getBackground()1802 public Color getBackground() { 1803 return backgroundColor; 1804 } 1805 1806 /** 1807 * Returns the current Stroke in the Graphics2D state. 1808 * @see setStroke 1809 */ getStroke()1810 public Stroke getStroke() { 1811 return stroke; 1812 } 1813 getClipBounds()1814 public Rectangle getClipBounds() { 1815 if (clipState == CLIP_DEVICE) { 1816 return null; 1817 } 1818 return getClipBounds(new Rectangle()); 1819 } 1820 getClipBounds(Rectangle r)1821 public Rectangle getClipBounds(Rectangle r) { 1822 if (clipState != CLIP_DEVICE) { 1823 if (transformState <= TRANSFORM_INT_TRANSLATE) { 1824 if (usrClip instanceof Rectangle) { 1825 r.setBounds((Rectangle) usrClip); 1826 } else { 1827 r.setFrame(usrClip.getBounds2D()); 1828 } 1829 r.translate(-transX, -transY); 1830 } else { 1831 r.setFrame(getClip().getBounds2D()); 1832 } 1833 } else if (r == null) { 1834 throw new NullPointerException("null rectangle parameter"); 1835 } 1836 return r; 1837 } 1838 hitClip(int x, int y, int width, int height)1839 public boolean hitClip(int x, int y, int width, int height) { 1840 if (width <= 0 || height <= 0) { 1841 return false; 1842 } 1843 if (transformState > TRANSFORM_INT_TRANSLATE) { 1844 // Note: Technically the most accurate test would be to 1845 // raster scan the parallelogram of the transformed rectangle 1846 // and do a span for span hit test against the clip, but for 1847 // speed we approximate the test with a bounding box of the 1848 // transformed rectangle. The cost of rasterizing the 1849 // transformed rectangle is probably high enough that it is 1850 // not worth doing so to save the caller from having to call 1851 // a rendering method where we will end up discovering the 1852 // same answer in about the same amount of time anyway. 1853 // This logic breaks down if this hit test is being performed 1854 // on the bounds of a group of shapes in which case it might 1855 // be beneficial to be a little more accurate to avoid lots 1856 // of subsequent rendering calls. In either case, this relaxed 1857 // test should not be significantly less accurate than the 1858 // optimal test for most transforms and so the conservative 1859 // answer should not cause too much extra work. 1860 1861 double d[] = { 1862 x, y, 1863 x+width, y, 1864 x, y+height, 1865 x+width, y+height 1866 }; 1867 transform.transform(d, 0, d, 0, 4); 1868 x = (int) Math.floor(Math.min(Math.min(d[0], d[2]), 1869 Math.min(d[4], d[6]))); 1870 y = (int) Math.floor(Math.min(Math.min(d[1], d[3]), 1871 Math.min(d[5], d[7]))); 1872 width = (int) Math.ceil(Math.max(Math.max(d[0], d[2]), 1873 Math.max(d[4], d[6]))); 1874 height = (int) Math.ceil(Math.max(Math.max(d[1], d[3]), 1875 Math.max(d[5], d[7]))); 1876 } else { 1877 x += transX; 1878 y += transY; 1879 width += x; 1880 height += y; 1881 } 1882 1883 try { 1884 if (!getCompClip().intersectsQuickCheckXYXY(x, y, width, height)) { 1885 return false; 1886 } 1887 } catch (InvalidPipeException e) { 1888 return false; 1889 } 1890 // REMIND: We could go one step further here and examine the 1891 // non-rectangular clip shape more closely if there is one. 1892 // Since the clip has already been rasterized, the performance 1893 // penalty of doing the scan is probably still within the bounds 1894 // of a good tradeoff between speed and quality of the answer. 1895 return true; 1896 } 1897 validateCompClip()1898 protected void validateCompClip() { 1899 int origClipState = clipState; 1900 if (usrClip == null) { 1901 clipState = CLIP_DEVICE; 1902 clipRegion = devClip; 1903 } else if (usrClip instanceof Rectangle2D) { 1904 clipState = CLIP_RECTANGULAR; 1905 if (usrClip instanceof Rectangle) { 1906 clipRegion = devClip.getIntersection((Rectangle)usrClip); 1907 } else { 1908 clipRegion = devClip.getIntersection(usrClip.getBounds()); 1909 } 1910 } else { 1911 PathIterator cpi = usrClip.getPathIterator(null); 1912 int box[] = new int[4]; 1913 ShapeSpanIterator sr = LoopPipe.getFillSSI(this); 1914 try { 1915 sr.setOutputArea(devClip); 1916 sr.appendPath(cpi); 1917 sr.getPathBox(box); 1918 Region r = Region.getInstance(box); 1919 r.appendSpans(sr); 1920 clipRegion = r; 1921 clipState = 1922 r.isRectangular() ? CLIP_RECTANGULAR : CLIP_SHAPE; 1923 } finally { 1924 sr.dispose(); 1925 } 1926 } 1927 if (origClipState != clipState && 1928 (clipState == CLIP_SHAPE || origClipState == CLIP_SHAPE)) 1929 { 1930 validFontInfo = false; 1931 invalidatePipe(); 1932 } 1933 } 1934 1935 static final int NON_RECTILINEAR_TRANSFORM_MASK = 1936 (AffineTransform.TYPE_GENERAL_TRANSFORM | 1937 AffineTransform.TYPE_GENERAL_ROTATION); 1938 transformShape(Shape s)1939 protected Shape transformShape(Shape s) { 1940 if (s == null) { 1941 return null; 1942 } 1943 if (transformState > TRANSFORM_INT_TRANSLATE) { 1944 return transformShape(transform, s); 1945 } else { 1946 return transformShape(transX, transY, s); 1947 } 1948 } 1949 untransformShape(Shape s)1950 public Shape untransformShape(Shape s) { 1951 if (s == null) { 1952 return null; 1953 } 1954 if (transformState > TRANSFORM_INT_TRANSLATE) { 1955 try { 1956 return transformShape(transform.createInverse(), s); 1957 } catch (NoninvertibleTransformException e) { 1958 return null; 1959 } 1960 } else { 1961 return transformShape(-transX, -transY, s); 1962 } 1963 } 1964 transformShape(int tx, int ty, Shape s)1965 protected static Shape transformShape(int tx, int ty, Shape s) { 1966 if (s == null) { 1967 return null; 1968 } 1969 1970 if (s instanceof Rectangle) { 1971 Rectangle r = s.getBounds(); 1972 r.translate(tx, ty); 1973 return r; 1974 } 1975 if (s instanceof Rectangle2D) { 1976 Rectangle2D rect = (Rectangle2D) s; 1977 return new Rectangle2D.Double(rect.getX() + tx, 1978 rect.getY() + ty, 1979 rect.getWidth(), 1980 rect.getHeight()); 1981 } 1982 1983 if (tx == 0 && ty == 0) { 1984 return cloneShape(s); 1985 } 1986 1987 AffineTransform mat = AffineTransform.getTranslateInstance(tx, ty); 1988 return mat.createTransformedShape(s); 1989 } 1990 transformShape(AffineTransform tx, Shape clip)1991 protected static Shape transformShape(AffineTransform tx, Shape clip) { 1992 if (clip == null) { 1993 return null; 1994 } 1995 1996 if (clip instanceof Rectangle2D && 1997 (tx.getType() & NON_RECTILINEAR_TRANSFORM_MASK) == 0) 1998 { 1999 Rectangle2D rect = (Rectangle2D) clip; 2000 double matrix[] = new double[4]; 2001 matrix[0] = rect.getX(); 2002 matrix[1] = rect.getY(); 2003 matrix[2] = matrix[0] + rect.getWidth(); 2004 matrix[3] = matrix[1] + rect.getHeight(); 2005 tx.transform(matrix, 0, matrix, 0, 2); 2006 fixRectangleOrientation(matrix, rect); 2007 return new Rectangle2D.Double(matrix[0], matrix[1], 2008 matrix[2] - matrix[0], 2009 matrix[3] - matrix[1]); 2010 } 2011 2012 if (tx.isIdentity()) { 2013 return cloneShape(clip); 2014 } 2015 2016 return tx.createTransformedShape(clip); 2017 } 2018 2019 /** 2020 * Sets orientation of the rectangle according to the clip. 2021 */ fixRectangleOrientation(double[] m, Rectangle2D clip)2022 private static void fixRectangleOrientation(double[] m, Rectangle2D clip) { 2023 if (clip.getWidth() > 0 != (m[2] - m[0] > 0)) { 2024 double t = m[0]; 2025 m[0] = m[2]; 2026 m[2] = t; 2027 } 2028 if (clip.getHeight() > 0 != (m[3] - m[1] > 0)) { 2029 double t = m[1]; 2030 m[1] = m[3]; 2031 m[3] = t; 2032 } 2033 } 2034 clipRect(int x, int y, int w, int h)2035 public void clipRect(int x, int y, int w, int h) { 2036 clip(new Rectangle(x, y, w, h)); 2037 } 2038 setClip(int x, int y, int w, int h)2039 public void setClip(int x, int y, int w, int h) { 2040 setClip(new Rectangle(x, y, w, h)); 2041 } 2042 getClip()2043 public Shape getClip() { 2044 return untransformShape(usrClip); 2045 } 2046 setClip(Shape sh)2047 public void setClip(Shape sh) { 2048 usrClip = transformShape(sh); 2049 validateCompClip(); 2050 } 2051 2052 /** 2053 * Intersects the current clip with the specified Path and sets the 2054 * current clip to the resulting intersection. The clip is transformed 2055 * with the current transform in the Graphics2D state before being 2056 * intersected with the current clip. This method is used to make the 2057 * current clip smaller. To make the clip larger, use any setClip method. 2058 * @param p The Path to be intersected with the current clip. 2059 */ clip(Shape s)2060 public void clip(Shape s) { 2061 s = transformShape(s); 2062 if (usrClip != null) { 2063 s = intersectShapes(usrClip, s, true, true); 2064 } 2065 usrClip = s; 2066 validateCompClip(); 2067 } 2068 setPaintMode()2069 public void setPaintMode() { 2070 setComposite(AlphaComposite.SrcOver); 2071 } 2072 setXORMode(Color c)2073 public void setXORMode(Color c) { 2074 if (c == null) { 2075 throw new IllegalArgumentException("null XORColor"); 2076 } 2077 setComposite(new XORComposite(c, surfaceData)); 2078 } 2079 2080 Blit lastCAblit; 2081 Composite lastCAcomp; 2082 copyArea(int x, int y, int w, int h, int dx, int dy)2083 public void copyArea(int x, int y, int w, int h, int dx, int dy) { 2084 try { 2085 doCopyArea(x, y, w, h, dx, dy); 2086 } catch (InvalidPipeException e) { 2087 try { 2088 revalidateAll(); 2089 doCopyArea(x, y, w, h, dx, dy); 2090 } catch (InvalidPipeException e2) { 2091 // Still catching the exception; we are not yet ready to 2092 // validate the surfaceData correctly. Fail for now and 2093 // try again next time around. 2094 } 2095 } finally { 2096 surfaceData.markDirty(); 2097 } 2098 } 2099 doCopyArea(int x, int y, int w, int h, int dx, int dy)2100 private void doCopyArea(int x, int y, int w, int h, int dx, int dy) { 2101 if (w <= 0 || h <= 0) { 2102 return; 2103 } 2104 SurfaceData theData = surfaceData; 2105 if (theData.copyArea(this, x, y, w, h, dx, dy)) { 2106 return; 2107 } 2108 if (transformState > TRANSFORM_TRANSLATESCALE) { 2109 throw new InternalError("transformed copyArea not implemented yet"); 2110 } 2111 // REMIND: This method does not deal with missing data from the 2112 // source object (i.e. it does not send exposure events...) 2113 2114 Region clip = getCompClip(); 2115 2116 Composite comp = composite; 2117 if (lastCAcomp != comp) { 2118 SurfaceType dsttype = theData.getSurfaceType(); 2119 CompositeType comptype = imageComp; 2120 if (CompositeType.SrcOverNoEa.equals(comptype) && 2121 theData.getTransparency() == Transparency.OPAQUE) 2122 { 2123 comptype = CompositeType.SrcNoEa; 2124 } 2125 lastCAblit = Blit.locate(dsttype, comptype, dsttype); 2126 lastCAcomp = comp; 2127 } 2128 2129 double[] coords = {x, y, x + w, y + h, x + dx, y + dy}; 2130 transform.transform(coords, 0, coords, 0, 3); 2131 2132 x = (int)Math.ceil(coords[0] - 0.5); 2133 y = (int)Math.ceil(coords[1] - 0.5); 2134 w = ((int)Math.ceil(coords[2] - 0.5)) - x; 2135 h = ((int)Math.ceil(coords[3] - 0.5)) - y; 2136 dx = ((int)Math.ceil(coords[4] - 0.5)) - x; 2137 dy = ((int)Math.ceil(coords[5] - 0.5)) - y; 2138 2139 // In case of negative scale transform, reflect the rect coords. 2140 if (w < 0) { 2141 w *= -1; 2142 x -= w; 2143 } 2144 if (h < 0) { 2145 h *= -1; 2146 y -= h; 2147 } 2148 2149 Blit ob = lastCAblit; 2150 if (dy == 0 && dx > 0 && dx < w) { 2151 while (w > 0) { 2152 int partW = Math.min(w, dx); 2153 w -= partW; 2154 int sx = x + w; 2155 ob.Blit(theData, theData, comp, clip, 2156 sx, y, sx+dx, y+dy, partW, h); 2157 } 2158 return; 2159 } 2160 if (dy > 0 && dy < h && dx > -w && dx < w) { 2161 while (h > 0) { 2162 int partH = Math.min(h, dy); 2163 h -= partH; 2164 int sy = y + h; 2165 ob.Blit(theData, theData, comp, clip, 2166 x, sy, x+dx, sy+dy, w, partH); 2167 } 2168 return; 2169 } 2170 ob.Blit(theData, theData, comp, clip, x, y, x+dx, y+dy, w, h); 2171 } 2172 2173 /* 2174 public void XcopyArea(int x, int y, int w, int h, int dx, int dy) { 2175 Rectangle rect = new Rectangle(x, y, w, h); 2176 rect = transformBounds(rect, transform); 2177 Point2D point = new Point2D.Float(dx, dy); 2178 Point2D root = new Point2D.Float(0, 0); 2179 point = transform.transform(point, point); 2180 root = transform.transform(root, root); 2181 int fdx = (int)(point.getX()-root.getX()); 2182 int fdy = (int)(point.getY()-root.getY()); 2183 2184 Rectangle r = getCompBounds().intersection(rect.getBounds()); 2185 2186 if (r.isEmpty()) { 2187 return; 2188 } 2189 2190 // Begin Rasterizer for Clip Shape 2191 boolean skipClip = true; 2192 byte[] clipAlpha = null; 2193 2194 if (clipState == CLIP_SHAPE) { 2195 2196 int box[] = new int[4]; 2197 2198 clipRegion.getBounds(box); 2199 Rectangle devR = new Rectangle(box[0], box[1], 2200 box[2] - box[0], 2201 box[3] - box[1]); 2202 if (!devR.isEmpty()) { 2203 OutputManager mgr = getOutputManager(); 2204 RegionIterator ri = clipRegion.getIterator(); 2205 while (ri.nextYRange(box)) { 2206 int spany = box[1]; 2207 int spanh = box[3] - spany; 2208 while (ri.nextXBand(box)) { 2209 int spanx = box[0]; 2210 int spanw = box[2] - spanx; 2211 mgr.copyArea(this, null, 2212 spanw, 0, 2213 spanx, spany, 2214 spanw, spanh, 2215 fdx, fdy, 2216 null); 2217 } 2218 } 2219 } 2220 return; 2221 } 2222 // End Rasterizer for Clip Shape 2223 2224 getOutputManager().copyArea(this, null, 2225 r.width, 0, 2226 r.x, r.y, r.width, 2227 r.height, fdx, fdy, 2228 null); 2229 } 2230 */ 2231 drawLine(int x1, int y1, int x2, int y2)2232 public void drawLine(int x1, int y1, int x2, int y2) { 2233 try { 2234 drawpipe.drawLine(this, x1, y1, x2, y2); 2235 } catch (InvalidPipeException e) { 2236 try { 2237 revalidateAll(); 2238 drawpipe.drawLine(this, x1, y1, x2, y2); 2239 } catch (InvalidPipeException e2) { 2240 // Still catching the exception; we are not yet ready to 2241 // validate the surfaceData correctly. Fail for now and 2242 // try again next time around. 2243 } 2244 } finally { 2245 surfaceData.markDirty(); 2246 } 2247 } 2248 drawRoundRect(int x, int y, int w, int h, int arcW, int arcH)2249 public void drawRoundRect(int x, int y, int w, int h, int arcW, int arcH) { 2250 try { 2251 drawpipe.drawRoundRect(this, x, y, w, h, arcW, arcH); 2252 } catch (InvalidPipeException e) { 2253 try { 2254 revalidateAll(); 2255 drawpipe.drawRoundRect(this, x, y, w, h, arcW, arcH); 2256 } catch (InvalidPipeException e2) { 2257 // Still catching the exception; we are not yet ready to 2258 // validate the surfaceData correctly. Fail for now and 2259 // try again next time around. 2260 } 2261 } finally { 2262 surfaceData.markDirty(); 2263 } 2264 } 2265 fillRoundRect(int x, int y, int w, int h, int arcW, int arcH)2266 public void fillRoundRect(int x, int y, int w, int h, int arcW, int arcH) { 2267 try { 2268 fillpipe.fillRoundRect(this, x, y, w, h, arcW, arcH); 2269 } catch (InvalidPipeException e) { 2270 try { 2271 revalidateAll(); 2272 fillpipe.fillRoundRect(this, x, y, w, h, arcW, arcH); 2273 } catch (InvalidPipeException e2) { 2274 // Still catching the exception; we are not yet ready to 2275 // validate the surfaceData correctly. Fail for now and 2276 // try again next time around. 2277 } 2278 } finally { 2279 surfaceData.markDirty(); 2280 } 2281 } 2282 drawOval(int x, int y, int w, int h)2283 public void drawOval(int x, int y, int w, int h) { 2284 try { 2285 drawpipe.drawOval(this, x, y, w, h); 2286 } catch (InvalidPipeException e) { 2287 try { 2288 revalidateAll(); 2289 drawpipe.drawOval(this, x, y, w, h); 2290 } catch (InvalidPipeException e2) { 2291 // Still catching the exception; we are not yet ready to 2292 // validate the surfaceData correctly. Fail for now and 2293 // try again next time around. 2294 } 2295 } finally { 2296 surfaceData.markDirty(); 2297 } 2298 } 2299 fillOval(int x, int y, int w, int h)2300 public void fillOval(int x, int y, int w, int h) { 2301 try { 2302 fillpipe.fillOval(this, x, y, w, h); 2303 } catch (InvalidPipeException e) { 2304 try { 2305 revalidateAll(); 2306 fillpipe.fillOval(this, x, y, w, h); 2307 } catch (InvalidPipeException e2) { 2308 // Still catching the exception; we are not yet ready to 2309 // validate the surfaceData correctly. Fail for now and 2310 // try again next time around. 2311 } 2312 } finally { 2313 surfaceData.markDirty(); 2314 } 2315 } 2316 drawArc(int x, int y, int w, int h, int startAngl, int arcAngl)2317 public void drawArc(int x, int y, int w, int h, 2318 int startAngl, int arcAngl) { 2319 try { 2320 drawpipe.drawArc(this, x, y, w, h, startAngl, arcAngl); 2321 } catch (InvalidPipeException e) { 2322 try { 2323 revalidateAll(); 2324 drawpipe.drawArc(this, x, y, w, h, startAngl, arcAngl); 2325 } catch (InvalidPipeException e2) { 2326 // Still catching the exception; we are not yet ready to 2327 // validate the surfaceData correctly. Fail for now and 2328 // try again next time around. 2329 } 2330 } finally { 2331 surfaceData.markDirty(); 2332 } 2333 } 2334 fillArc(int x, int y, int w, int h, int startAngl, int arcAngl)2335 public void fillArc(int x, int y, int w, int h, 2336 int startAngl, int arcAngl) { 2337 try { 2338 fillpipe.fillArc(this, x, y, w, h, startAngl, arcAngl); 2339 } catch (InvalidPipeException e) { 2340 try { 2341 revalidateAll(); 2342 fillpipe.fillArc(this, x, y, w, h, startAngl, arcAngl); 2343 } catch (InvalidPipeException e2) { 2344 // Still catching the exception; we are not yet ready to 2345 // validate the surfaceData correctly. Fail for now and 2346 // try again next time around. 2347 } 2348 } finally { 2349 surfaceData.markDirty(); 2350 } 2351 } 2352 drawPolyline(int xPoints[], int yPoints[], int nPoints)2353 public void drawPolyline(int xPoints[], int yPoints[], int nPoints) { 2354 try { 2355 drawpipe.drawPolyline(this, xPoints, yPoints, nPoints); 2356 } catch (InvalidPipeException e) { 2357 try { 2358 revalidateAll(); 2359 drawpipe.drawPolyline(this, xPoints, yPoints, nPoints); 2360 } catch (InvalidPipeException e2) { 2361 // Still catching the exception; we are not yet ready to 2362 // validate the surfaceData correctly. Fail for now and 2363 // try again next time around. 2364 } 2365 } finally { 2366 surfaceData.markDirty(); 2367 } 2368 } 2369 drawPolygon(int xPoints[], int yPoints[], int nPoints)2370 public void drawPolygon(int xPoints[], int yPoints[], int nPoints) { 2371 try { 2372 drawpipe.drawPolygon(this, xPoints, yPoints, nPoints); 2373 } catch (InvalidPipeException e) { 2374 try { 2375 revalidateAll(); 2376 drawpipe.drawPolygon(this, xPoints, yPoints, nPoints); 2377 } catch (InvalidPipeException e2) { 2378 // Still catching the exception; we are not yet ready to 2379 // validate the surfaceData correctly. Fail for now and 2380 // try again next time around. 2381 } 2382 } finally { 2383 surfaceData.markDirty(); 2384 } 2385 } 2386 fillPolygon(int xPoints[], int yPoints[], int nPoints)2387 public void fillPolygon(int xPoints[], int yPoints[], int nPoints) { 2388 try { 2389 fillpipe.fillPolygon(this, xPoints, yPoints, nPoints); 2390 } catch (InvalidPipeException e) { 2391 try { 2392 revalidateAll(); 2393 fillpipe.fillPolygon(this, xPoints, yPoints, nPoints); 2394 } catch (InvalidPipeException e2) { 2395 // Still catching the exception; we are not yet ready to 2396 // validate the surfaceData correctly. Fail for now and 2397 // try again next time around. 2398 } 2399 } finally { 2400 surfaceData.markDirty(); 2401 } 2402 } 2403 drawRect(int x, int y, int w, int h)2404 public void drawRect (int x, int y, int w, int h) { 2405 try { 2406 drawpipe.drawRect(this, x, y, w, h); 2407 } catch (InvalidPipeException e) { 2408 try { 2409 revalidateAll(); 2410 drawpipe.drawRect(this, x, y, w, h); 2411 } catch (InvalidPipeException e2) { 2412 // Still catching the exception; we are not yet ready to 2413 // validate the surfaceData correctly. Fail for now and 2414 // try again next time around. 2415 } 2416 } finally { 2417 surfaceData.markDirty(); 2418 } 2419 } 2420 fillRect(int x, int y, int w, int h)2421 public void fillRect (int x, int y, int w, int h) { 2422 try { 2423 fillpipe.fillRect(this, x, y, w, h); 2424 } catch (InvalidPipeException e) { 2425 try { 2426 revalidateAll(); 2427 fillpipe.fillRect(this, x, y, w, h); 2428 } catch (InvalidPipeException e2) { 2429 // Still catching the exception; we are not yet ready to 2430 // validate the surfaceData correctly. Fail for now and 2431 // try again next time around. 2432 } 2433 } finally { 2434 surfaceData.markDirty(); 2435 } 2436 } 2437 revalidateAll()2438 private void revalidateAll() { 2439 try { 2440 // REMIND: This locking needs to be done around the 2441 // caller of this method so that the pipe stays valid 2442 // long enough to call the new primitive. 2443 // REMIND: No locking yet in screen SurfaceData objects! 2444 // surfaceData.lock(); 2445 surfaceData = surfaceData.getReplacement(); 2446 if (surfaceData == null) { 2447 surfaceData = NullSurfaceData.theInstance; 2448 } 2449 2450 invalidatePipe(); 2451 2452 // this will recalculate the composite clip 2453 setDevClip(surfaceData.getBounds()); 2454 2455 if (paintState <= PAINT_ALPHACOLOR) { 2456 validateColor(); 2457 } 2458 if (composite instanceof XORComposite) { 2459 Color c = ((XORComposite) composite).getXorColor(); 2460 setComposite(new XORComposite(c, surfaceData)); 2461 } 2462 validatePipe(); 2463 } finally { 2464 // REMIND: No locking yet in screen SurfaceData objects! 2465 // surfaceData.unlock(); 2466 } 2467 } 2468 clearRect(int x, int y, int w, int h)2469 public void clearRect(int x, int y, int w, int h) { 2470 // REMIND: has some "interesting" consequences if threads are 2471 // not synchronized 2472 Composite c = composite; 2473 Paint p = paint; 2474 setComposite(AlphaComposite.Src); 2475 setColor(getBackground()); 2476 fillRect(x, y, w, h); 2477 setPaint(p); 2478 setComposite(c); 2479 } 2480 2481 /** 2482 * Strokes the outline of a Path using the settings of the current 2483 * graphics state. The rendering attributes applied include the 2484 * clip, transform, paint or color, composite and stroke attributes. 2485 * @param p The path to be drawn. 2486 * @see #setStroke 2487 * @see #setPaint 2488 * @see java.awt.Graphics#setColor 2489 * @see #transform 2490 * @see #setTransform 2491 * @see #clip 2492 * @see #setClip 2493 * @see #setComposite 2494 */ draw(Shape s)2495 public void draw(Shape s) { 2496 try { 2497 shapepipe.draw(this, s); 2498 } catch (InvalidPipeException e) { 2499 try { 2500 revalidateAll(); 2501 shapepipe.draw(this, s); 2502 } catch (InvalidPipeException e2) { 2503 // Still catching the exception; we are not yet ready to 2504 // validate the surfaceData correctly. Fail for now and 2505 // try again next time around. 2506 } 2507 } finally { 2508 surfaceData.markDirty(); 2509 } 2510 } 2511 2512 2513 /** 2514 * Fills the interior of a Path using the settings of the current 2515 * graphics state. The rendering attributes applied include the 2516 * clip, transform, paint or color, and composite. 2517 * @see #setPaint 2518 * @see java.awt.Graphics#setColor 2519 * @see #transform 2520 * @see #setTransform 2521 * @see #setComposite 2522 * @see #clip 2523 * @see #setClip 2524 */ fill(Shape s)2525 public void fill(Shape s) { 2526 try { 2527 shapepipe.fill(this, s); 2528 } catch (InvalidPipeException e) { 2529 try { 2530 revalidateAll(); 2531 shapepipe.fill(this, s); 2532 } catch (InvalidPipeException e2) { 2533 // Still catching the exception; we are not yet ready to 2534 // validate the surfaceData correctly. Fail for now and 2535 // try again next time around. 2536 } 2537 } finally { 2538 surfaceData.markDirty(); 2539 } 2540 } 2541 2542 /** 2543 * Returns true if the given AffineTransform is an integer 2544 * translation. 2545 */ isIntegerTranslation(AffineTransform xform)2546 private static boolean isIntegerTranslation(AffineTransform xform) { 2547 if (xform.isIdentity()) { 2548 return true; 2549 } 2550 if (xform.getType() == AffineTransform.TYPE_TRANSLATION) { 2551 double tx = xform.getTranslateX(); 2552 double ty = xform.getTranslateY(); 2553 return (tx == (int)tx && ty == (int)ty); 2554 } 2555 return false; 2556 } 2557 2558 /** 2559 * Returns the index of the tile corresponding to the supplied position 2560 * given the tile grid offset and size along the same axis. 2561 */ getTileIndex(int p, int tileGridOffset, int tileSize)2562 private static int getTileIndex(int p, int tileGridOffset, int tileSize) { 2563 p -= tileGridOffset; 2564 if (p < 0) { 2565 p += 1 - tileSize; // force round to -infinity (ceiling) 2566 } 2567 return p/tileSize; 2568 } 2569 2570 /** 2571 * Returns a rectangle in image coordinates that may be required 2572 * in order to draw the given image into the given clipping region 2573 * through a pair of AffineTransforms. In addition, horizontal and 2574 * vertical padding factors for antialising and interpolation may 2575 * be used. 2576 */ getImageRegion(RenderedImage img, Region compClip, AffineTransform transform, AffineTransform xform, int padX, int padY)2577 private static Rectangle getImageRegion(RenderedImage img, 2578 Region compClip, 2579 AffineTransform transform, 2580 AffineTransform xform, 2581 int padX, int padY) { 2582 Rectangle imageRect = 2583 new Rectangle(img.getMinX(), img.getMinY(), 2584 img.getWidth(), img.getHeight()); 2585 2586 Rectangle result = null; 2587 try { 2588 double p[] = new double[8]; 2589 p[0] = p[2] = compClip.getLoX(); 2590 p[4] = p[6] = compClip.getHiX(); 2591 p[1] = p[5] = compClip.getLoY(); 2592 p[3] = p[7] = compClip.getHiY(); 2593 2594 // Inverse transform the output bounding rect 2595 transform.inverseTransform(p, 0, p, 0, 4); 2596 xform.inverseTransform(p, 0, p, 0, 4); 2597 2598 // Determine a bounding box for the inverse transformed region 2599 double x0,x1,y0,y1; 2600 x0 = x1 = p[0]; 2601 y0 = y1 = p[1]; 2602 2603 for (int i = 2; i < 8; ) { 2604 double pt = p[i++]; 2605 if (pt < x0) { 2606 x0 = pt; 2607 } else if (pt > x1) { 2608 x1 = pt; 2609 } 2610 pt = p[i++]; 2611 if (pt < y0) { 2612 y0 = pt; 2613 } else if (pt > y1) { 2614 y1 = pt; 2615 } 2616 } 2617 2618 // This is padding for anti-aliasing and such. It may 2619 // be more than is needed. 2620 int x = (int)x0 - padX; 2621 int w = (int)(x1 - x0 + 2*padX); 2622 int y = (int)y0 - padY; 2623 int h = (int)(y1 - y0 + 2*padY); 2624 2625 Rectangle clipRect = new Rectangle(x,y,w,h); 2626 result = clipRect.intersection(imageRect); 2627 } catch (NoninvertibleTransformException nte) { 2628 // Worst case bounds are the bounds of the image. 2629 result = imageRect; 2630 } 2631 2632 return result; 2633 } 2634 2635 /** 2636 * Draws an image, applying a transform from image space into user space 2637 * before drawing. 2638 * The transformation from user space into device space is done with 2639 * the current transform in the Graphics2D. 2640 * The given transformation is applied to the image before the 2641 * transform attribute in the Graphics2D state is applied. 2642 * The rendering attributes applied include the clip, transform, 2643 * and composite attributes. Note that the result is 2644 * undefined, if the given transform is noninvertible. 2645 * @param img The image to be drawn. Does nothing if img is null. 2646 * @param xform The transformation from image space into user space. 2647 * @see #transform 2648 * @see #setTransform 2649 * @see #setComposite 2650 * @see #clip 2651 * @see #setClip 2652 */ drawRenderedImage(RenderedImage img, AffineTransform xform)2653 public void drawRenderedImage(RenderedImage img, 2654 AffineTransform xform) { 2655 2656 if (img == null) { 2657 return; 2658 } 2659 2660 // BufferedImage case: use a simple drawImage call 2661 if (img instanceof BufferedImage) { 2662 BufferedImage bufImg = (BufferedImage)img; 2663 drawImage(bufImg,xform,null); 2664 return; 2665 } 2666 2667 // transformState tracks the state of transform and 2668 // transX, transY contain the integer casts of the 2669 // translation factors 2670 boolean isIntegerTranslate = 2671 (transformState <= TRANSFORM_INT_TRANSLATE) && 2672 isIntegerTranslation(xform); 2673 2674 // Include padding for interpolation/antialiasing if necessary 2675 int pad = isIntegerTranslate ? 0 : 3; 2676 2677 Region clip; 2678 try { 2679 clip = getCompClip(); 2680 } catch (InvalidPipeException e) { 2681 return; 2682 } 2683 2684 // Determine the region of the image that may contribute to 2685 // the clipped drawing area 2686 Rectangle region = getImageRegion(img, 2687 clip, 2688 transform, 2689 xform, 2690 pad, pad); 2691 if (region.width <= 0 || region.height <= 0) { 2692 return; 2693 } 2694 2695 // Attempt to optimize integer translation of tiled images. 2696 // Although theoretically we are O.K. if the concatenation of 2697 // the user transform and the device transform is an integer 2698 // translation, we'll play it safe and only optimize the case 2699 // where both are integer translations. 2700 if (isIntegerTranslate) { 2701 // Use optimized code 2702 // Note that drawTranslatedRenderedImage calls copyImage 2703 // which takes the user space to device space transform into 2704 // account, but we need to provide the image space to user space 2705 // translations. 2706 2707 drawTranslatedRenderedImage(img, region, 2708 (int) xform.getTranslateX(), 2709 (int) xform.getTranslateY()); 2710 return; 2711 } 2712 2713 // General case: cobble the necessary region into a single Raster 2714 Raster raster = img.getData(region); 2715 2716 // Make a new Raster with the same contents as raster 2717 // but starting at (0, 0). This raster is thus in the same 2718 // coordinate system as the SampleModel of the original raster. 2719 WritableRaster wRaster = 2720 Raster.createWritableRaster(raster.getSampleModel(), 2721 raster.getDataBuffer(), 2722 null); 2723 2724 // If the original raster was in a different coordinate 2725 // system than its SampleModel, we need to perform an 2726 // additional translation in order to get the (minX, minY) 2727 // pixel of raster to be pixel (0, 0) of wRaster. We also 2728 // have to have the correct width and height. 2729 int minX = raster.getMinX(); 2730 int minY = raster.getMinY(); 2731 int width = raster.getWidth(); 2732 int height = raster.getHeight(); 2733 int px = minX - raster.getSampleModelTranslateX(); 2734 int py = minY - raster.getSampleModelTranslateY(); 2735 if (px != 0 || py != 0 || width != wRaster.getWidth() || 2736 height != wRaster.getHeight()) { 2737 wRaster = 2738 wRaster.createWritableChild(px, 2739 py, 2740 width, 2741 height, 2742 0, 0, 2743 null); 2744 } 2745 2746 // Now we have a BufferedImage starting at (0, 0) 2747 // with the same contents that started at (minX, minY) 2748 // in raster. So we must draw the BufferedImage with a 2749 // translation of (minX, minY). 2750 AffineTransform transXform = (AffineTransform)xform.clone(); 2751 transXform.translate(minX, minY); 2752 2753 ColorModel cm = img.getColorModel(); 2754 BufferedImage bufImg = new BufferedImage(cm, 2755 wRaster, 2756 cm.isAlphaPremultiplied(), 2757 null); 2758 drawImage(bufImg, transXform, null); 2759 } 2760 2761 /** 2762 * Intersects <code>destRect</code> with <code>clip</code> and 2763 * overwrites <code>destRect</code> with the result. 2764 * Returns false if the intersection was empty, true otherwise. 2765 */ clipTo(Rectangle destRect, Rectangle clip)2766 private boolean clipTo(Rectangle destRect, Rectangle clip) { 2767 int x1 = Math.max(destRect.x, clip.x); 2768 int x2 = Math.min(destRect.x + destRect.width, clip.x + clip.width); 2769 int y1 = Math.max(destRect.y, clip.y); 2770 int y2 = Math.min(destRect.y + destRect.height, clip.y + clip.height); 2771 if (((x2 - x1) < 0) || ((y2 - y1) < 0)) { 2772 destRect.width = -1; // Set both just to be safe 2773 destRect.height = -1; 2774 return false; 2775 } else { 2776 destRect.x = x1; 2777 destRect.y = y1; 2778 destRect.width = x2 - x1; 2779 destRect.height = y2 - y1; 2780 return true; 2781 } 2782 } 2783 2784 /** 2785 * Draw a portion of a RenderedImage tile-by-tile with a given 2786 * integer image to user space translation. The user to 2787 * device transform must also be an integer translation. 2788 */ drawTranslatedRenderedImage(RenderedImage img, Rectangle region, int i2uTransX, int i2uTransY)2789 private void drawTranslatedRenderedImage(RenderedImage img, 2790 Rectangle region, 2791 int i2uTransX, 2792 int i2uTransY) { 2793 // Cache tile grid info 2794 int tileGridXOffset = img.getTileGridXOffset(); 2795 int tileGridYOffset = img.getTileGridYOffset(); 2796 int tileWidth = img.getTileWidth(); 2797 int tileHeight = img.getTileHeight(); 2798 2799 // Determine the tile index extrema in each direction 2800 int minTileX = 2801 getTileIndex(region.x, tileGridXOffset, tileWidth); 2802 int minTileY = 2803 getTileIndex(region.y, tileGridYOffset, tileHeight); 2804 int maxTileX = 2805 getTileIndex(region.x + region.width - 1, 2806 tileGridXOffset, tileWidth); 2807 int maxTileY = 2808 getTileIndex(region.y + region.height - 1, 2809 tileGridYOffset, tileHeight); 2810 2811 // Create a single ColorModel to use for all BufferedImages 2812 ColorModel colorModel = img.getColorModel(); 2813 2814 // Reuse the same Rectangle for each iteration 2815 Rectangle tileRect = new Rectangle(); 2816 2817 for (int ty = minTileY; ty <= maxTileY; ty++) { 2818 for (int tx = minTileX; tx <= maxTileX; tx++) { 2819 // Get the current tile. 2820 Raster raster = img.getTile(tx, ty); 2821 2822 // Fill in tileRect with the tile bounds 2823 tileRect.x = tx*tileWidth + tileGridXOffset; 2824 tileRect.y = ty*tileHeight + tileGridYOffset; 2825 tileRect.width = tileWidth; 2826 tileRect.height = tileHeight; 2827 2828 // Clip the tile against the image bounds and 2829 // backwards mapped clip region 2830 // The result can't be empty 2831 clipTo(tileRect, region); 2832 2833 // Create a WritableRaster containing the tile 2834 WritableRaster wRaster = null; 2835 if (raster instanceof WritableRaster) { 2836 wRaster = (WritableRaster)raster; 2837 } else { 2838 // Create a WritableRaster in the same coordinate system 2839 // as the original raster. 2840 wRaster = 2841 Raster.createWritableRaster(raster.getSampleModel(), 2842 raster.getDataBuffer(), 2843 null); 2844 } 2845 2846 // Translate wRaster to start at (0, 0) and to contain 2847 // only the relevent portion of the tile 2848 wRaster = wRaster.createWritableChild(tileRect.x, tileRect.y, 2849 tileRect.width, 2850 tileRect.height, 2851 0, 0, 2852 null); 2853 2854 // Wrap wRaster in a BufferedImage 2855 BufferedImage bufImg = 2856 new BufferedImage(colorModel, 2857 wRaster, 2858 colorModel.isAlphaPremultiplied(), 2859 null); 2860 // Now we have a BufferedImage starting at (0, 0) that 2861 // represents data from a Raster starting at 2862 // (tileRect.x, tileRect.y). Additionally, it needs 2863 // to be translated by (i2uTransX, i2uTransY). We call 2864 // copyImage to draw just the region of interest 2865 // without needing to create a child image. 2866 copyImage(bufImg, tileRect.x + i2uTransX, 2867 tileRect.y + i2uTransY, 0, 0, tileRect.width, 2868 tileRect.height, null, null); 2869 } 2870 } 2871 } 2872 drawRenderableImage(RenderableImage img, AffineTransform xform)2873 public void drawRenderableImage(RenderableImage img, 2874 AffineTransform xform) { 2875 2876 if (img == null) { 2877 return; 2878 } 2879 2880 AffineTransform pipeTransform = transform; 2881 AffineTransform concatTransform = new AffineTransform(xform); 2882 concatTransform.concatenate(pipeTransform); 2883 AffineTransform reverseTransform; 2884 2885 RenderContext rc = new RenderContext(concatTransform); 2886 2887 try { 2888 reverseTransform = pipeTransform.createInverse(); 2889 } catch (NoninvertibleTransformException nte) { 2890 rc = new RenderContext(pipeTransform); 2891 reverseTransform = new AffineTransform(); 2892 } 2893 2894 RenderedImage rendering = img.createRendering(rc); 2895 drawRenderedImage(rendering,reverseTransform); 2896 } 2897 2898 2899 2900 /* 2901 * Transform the bounding box of the BufferedImage 2902 */ transformBounds(Rectangle rect, AffineTransform tx)2903 protected Rectangle transformBounds(Rectangle rect, 2904 AffineTransform tx) { 2905 if (tx.isIdentity()) { 2906 return rect; 2907 } 2908 2909 Shape s = transformShape(tx, rect); 2910 return s.getBounds(); 2911 } 2912 2913 // text rendering methods drawString(String str, int x, int y)2914 public void drawString(String str, int x, int y) { 2915 if (str == null) { 2916 throw new NullPointerException("String is null"); 2917 } 2918 2919 if (font.hasLayoutAttributes()) { 2920 if (str.length() == 0) { 2921 return; 2922 } 2923 new TextLayout(str, font, getFontRenderContext()).draw(this, x, y); 2924 return; 2925 } 2926 2927 try { 2928 textpipe.drawString(this, str, x, y); 2929 } catch (InvalidPipeException e) { 2930 try { 2931 revalidateAll(); 2932 textpipe.drawString(this, str, x, y); 2933 } catch (InvalidPipeException e2) { 2934 // Still catching the exception; we are not yet ready to 2935 // validate the surfaceData correctly. Fail for now and 2936 // try again next time around. 2937 } 2938 } finally { 2939 surfaceData.markDirty(); 2940 } 2941 } 2942 drawString(String str, float x, float y)2943 public void drawString(String str, float x, float y) { 2944 if (str == null) { 2945 throw new NullPointerException("String is null"); 2946 } 2947 2948 if (font.hasLayoutAttributes()) { 2949 if (str.length() == 0) { 2950 return; 2951 } 2952 new TextLayout(str, font, getFontRenderContext()).draw(this, x, y); 2953 return; 2954 } 2955 2956 try { 2957 textpipe.drawString(this, str, x, y); 2958 } catch (InvalidPipeException e) { 2959 try { 2960 revalidateAll(); 2961 textpipe.drawString(this, str, x, y); 2962 } catch (InvalidPipeException e2) { 2963 // Still catching the exception; we are not yet ready to 2964 // validate the surfaceData correctly. Fail for now and 2965 // try again next time around. 2966 } 2967 } finally { 2968 surfaceData.markDirty(); 2969 } 2970 } 2971 drawString(AttributedCharacterIterator iterator, int x, int y)2972 public void drawString(AttributedCharacterIterator iterator, 2973 int x, int y) { 2974 if (iterator == null) { 2975 throw new NullPointerException("AttributedCharacterIterator is null"); 2976 } 2977 if (iterator.getBeginIndex() == iterator.getEndIndex()) { 2978 return; /* nothing to draw */ 2979 } 2980 TextLayout tl = new TextLayout(iterator, getFontRenderContext()); 2981 tl.draw(this, (float) x, (float) y); 2982 } 2983 drawString(AttributedCharacterIterator iterator, float x, float y)2984 public void drawString(AttributedCharacterIterator iterator, 2985 float x, float y) { 2986 if (iterator == null) { 2987 throw new NullPointerException("AttributedCharacterIterator is null"); 2988 } 2989 if (iterator.getBeginIndex() == iterator.getEndIndex()) { 2990 return; /* nothing to draw */ 2991 } 2992 TextLayout tl = new TextLayout(iterator, getFontRenderContext()); 2993 tl.draw(this, x, y); 2994 } 2995 drawGlyphVector(GlyphVector gv, float x, float y)2996 public void drawGlyphVector(GlyphVector gv, float x, float y) 2997 { 2998 if (gv == null) { 2999 throw new NullPointerException("GlyphVector is null"); 3000 } 3001 3002 try { 3003 textpipe.drawGlyphVector(this, gv, x, y); 3004 } catch (InvalidPipeException e) { 3005 try { 3006 revalidateAll(); 3007 textpipe.drawGlyphVector(this, gv, x, y); 3008 } catch (InvalidPipeException e2) { 3009 // Still catching the exception; we are not yet ready to 3010 // validate the surfaceData correctly. Fail for now and 3011 // try again next time around. 3012 } 3013 } finally { 3014 surfaceData.markDirty(); 3015 } 3016 } 3017 drawChars(char data[], int offset, int length, int x, int y)3018 public void drawChars(char data[], int offset, int length, int x, int y) { 3019 3020 if (data == null) { 3021 throw new NullPointerException("char data is null"); 3022 } 3023 if (offset < 0 || length < 0 || offset + length < length || 3024 offset + length > data.length) { 3025 throw new ArrayIndexOutOfBoundsException("bad offset/length"); 3026 } 3027 if (font.hasLayoutAttributes()) { 3028 if (data.length == 0) { 3029 return; 3030 } 3031 new TextLayout(new String(data, offset, length), 3032 font, getFontRenderContext()).draw(this, x, y); 3033 return; 3034 } 3035 3036 try { 3037 textpipe.drawChars(this, data, offset, length, x, y); 3038 } catch (InvalidPipeException e) { 3039 try { 3040 revalidateAll(); 3041 textpipe.drawChars(this, data, offset, length, x, y); 3042 } catch (InvalidPipeException e2) { 3043 // Still catching the exception; we are not yet ready to 3044 // validate the surfaceData correctly. Fail for now and 3045 // try again next time around. 3046 } 3047 } finally { 3048 surfaceData.markDirty(); 3049 } 3050 } 3051 drawBytes(byte data[], int offset, int length, int x, int y)3052 public void drawBytes(byte data[], int offset, int length, int x, int y) { 3053 if (data == null) { 3054 throw new NullPointerException("byte data is null"); 3055 } 3056 if (offset < 0 || length < 0 || offset + length < length || 3057 offset + length > data.length) { 3058 throw new ArrayIndexOutOfBoundsException("bad offset/length"); 3059 } 3060 /* Byte data is interpreted as 8-bit ASCII. Re-use drawChars loops */ 3061 char chData[] = new char[length]; 3062 for (int i = length; i-- > 0; ) { 3063 chData[i] = (char)(data[i+offset] & 0xff); 3064 } 3065 if (font.hasLayoutAttributes()) { 3066 if (data.length == 0) { 3067 return; 3068 } 3069 new TextLayout(new String(chData), 3070 font, getFontRenderContext()).draw(this, x, y); 3071 return; 3072 } 3073 3074 try { 3075 textpipe.drawChars(this, chData, 0, length, x, y); 3076 } catch (InvalidPipeException e) { 3077 try { 3078 revalidateAll(); 3079 textpipe.drawChars(this, chData, 0, length, x, y); 3080 } catch (InvalidPipeException e2) { 3081 // Still catching the exception; we are not yet ready to 3082 // validate the surfaceData correctly. Fail for now and 3083 // try again next time around. 3084 } 3085 } finally { 3086 surfaceData.markDirty(); 3087 } 3088 } 3089 // end of text rendering methods 3090 isHiDPIImage(final Image img)3091 private boolean isHiDPIImage(final Image img) { 3092 return (SurfaceManager.getImageScale(img) != 1) || 3093 (resolutionVariantHint != SunHints.INTVAL_RESOLUTION_VARIANT_OFF 3094 && img instanceof MultiResolutionImage); 3095 } 3096 drawHiDPIImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver observer)3097 private boolean drawHiDPIImage(Image img, int dx1, int dy1, int dx2, 3098 int dy2, int sx1, int sy1, int sx2, int sy2, 3099 Color bgcolor, ImageObserver observer) { 3100 3101 if (SurfaceManager.getImageScale(img) != 1) { // Volatile Image 3102 final int scale = SurfaceManager.getImageScale(img); 3103 sx1 = Region.clipScale(sx1, scale); 3104 sx2 = Region.clipScale(sx2, scale); 3105 sy1 = Region.clipScale(sy1, scale); 3106 sy2 = Region.clipScale(sy2, scale); 3107 } else if (img instanceof MultiResolutionImage) { 3108 // get scaled destination image size 3109 3110 int width = img.getWidth(observer); 3111 int height = img.getHeight(observer); 3112 3113 Image resolutionVariant = getResolutionVariant( 3114 (MultiResolutionImage) img, width, height, 3115 dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2); 3116 3117 if (resolutionVariant != img && resolutionVariant != null) { 3118 // recalculate source region for the resolution variant 3119 3120 ImageObserver rvObserver = MultiResolutionToolkitImage. 3121 getResolutionVariantObserver(img, observer, 3122 width, height, -1, -1); 3123 3124 int rvWidth = resolutionVariant.getWidth(rvObserver); 3125 int rvHeight = resolutionVariant.getHeight(rvObserver); 3126 3127 if (0 < width && 0 < height && 0 < rvWidth && 0 < rvHeight) { 3128 3129 float widthScale = ((float) rvWidth) / width; 3130 float heightScale = ((float) rvHeight) / height; 3131 3132 sx1 = Region.clipScale(sx1, widthScale); 3133 sy1 = Region.clipScale(sy1, heightScale); 3134 sx2 = Region.clipScale(sx2, widthScale); 3135 sy2 = Region.clipScale(sy2, heightScale); 3136 3137 observer = rvObserver; 3138 img = resolutionVariant; 3139 } 3140 } 3141 } 3142 3143 try { 3144 return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2, sx1, sy1, 3145 sx2, sy2, bgcolor, observer); 3146 } catch (InvalidPipeException e) { 3147 try { 3148 revalidateAll(); 3149 return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2, sx1, 3150 sy1, sx2, sy2, bgcolor, observer); 3151 } catch (InvalidPipeException e2) { 3152 // Still catching the exception; we are not yet ready to 3153 // validate the surfaceData correctly. Fail for now and 3154 // try again next time around. 3155 return false; 3156 } 3157 } finally { 3158 surfaceData.markDirty(); 3159 } 3160 } 3161 getResolutionVariant(MultiResolutionImage img, int srcWidth, int srcHeight, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2)3162 private Image getResolutionVariant(MultiResolutionImage img, 3163 int srcWidth, int srcHeight, int dx1, int dy1, int dx2, int dy2, 3164 int sx1, int sy1, int sx2, int sy2) { 3165 3166 if (srcWidth <= 0 || srcHeight <= 0) { 3167 return null; 3168 } 3169 3170 int sw = sx2 - sx1; 3171 int sh = sy2 - sy1; 3172 3173 if (sw == 0 || sh == 0) { 3174 return null; 3175 } 3176 3177 int type = transform.getType(); 3178 int dw = dx2 - dx1; 3179 int dh = dy2 - dy1; 3180 double destRegionWidth; 3181 double destRegionHeight; 3182 3183 if ((type & ~(TYPE_TRANSLATION | TYPE_FLIP)) == 0) { 3184 destRegionWidth = dw; 3185 destRegionHeight = dh; 3186 } else if ((type & ~(TYPE_TRANSLATION | TYPE_FLIP | TYPE_MASK_SCALE)) == 0) { 3187 destRegionWidth = dw * transform.getScaleX(); 3188 destRegionHeight = dh * transform.getScaleY(); 3189 } else { 3190 destRegionWidth = dw * Math.hypot( 3191 transform.getScaleX(), transform.getShearY()); 3192 destRegionHeight = dh * Math.hypot( 3193 transform.getShearX(), transform.getScaleY()); 3194 } 3195 3196 int destImageWidth = (int) Math.abs(srcWidth * destRegionWidth / sw); 3197 int destImageHeight = (int) Math.abs(srcHeight * destRegionHeight / sh); 3198 3199 Image resolutionVariant 3200 = img.getResolutionVariant(destImageWidth, destImageHeight); 3201 3202 if (resolutionVariant instanceof ToolkitImage 3203 && ((ToolkitImage) resolutionVariant).hasError()) { 3204 return null; 3205 } 3206 3207 return resolutionVariant; 3208 } 3209 3210 /** 3211 * Draws an image scaled to x,y,w,h in nonblocking mode with a 3212 * callback object. 3213 */ drawImage(Image img, int x, int y, int width, int height, ImageObserver observer)3214 public boolean drawImage(Image img, int x, int y, int width, int height, 3215 ImageObserver observer) { 3216 return drawImage(img, x, y, width, height, null, observer); 3217 } 3218 3219 /** 3220 * Not part of the advertised API but a useful utility method 3221 * to call internally. This is for the case where we are 3222 * drawing to/from given coordinates using a given width/height, 3223 * but we guarantee that the surfaceData's width/height of the src and dest 3224 * areas are equal (no scale needed). Note that this method intentionally 3225 * ignore scale factor of the source image, and copy it as is. 3226 */ copyImage(Image img, int dx, int dy, int sx, int sy, int width, int height, Color bgcolor, ImageObserver observer)3227 public boolean copyImage(Image img, int dx, int dy, int sx, int sy, 3228 int width, int height, Color bgcolor, 3229 ImageObserver observer) { 3230 try { 3231 return imagepipe.copyImage(this, img, dx, dy, sx, sy, 3232 width, height, bgcolor, observer); 3233 } catch (InvalidPipeException e) { 3234 try { 3235 revalidateAll(); 3236 return imagepipe.copyImage(this, img, dx, dy, sx, sy, 3237 width, height, bgcolor, observer); 3238 } catch (InvalidPipeException e2) { 3239 // Still catching the exception; we are not yet ready to 3240 // validate the surfaceData correctly. Fail for now and 3241 // try again next time around. 3242 return false; 3243 } 3244 } finally { 3245 surfaceData.markDirty(); 3246 } 3247 } 3248 3249 /** 3250 * Draws an image scaled to x,y,w,h in nonblocking mode with a 3251 * solid background color and a callback object. 3252 */ drawImage(Image img, int x, int y, int width, int height, Color bg, ImageObserver observer)3253 public boolean drawImage(Image img, int x, int y, int width, int height, 3254 Color bg, ImageObserver observer) { 3255 3256 if (img == null) { 3257 return true; 3258 } 3259 3260 if ((width == 0) || (height == 0)) { 3261 return true; 3262 } 3263 3264 final int imgW = img.getWidth(null); 3265 final int imgH = img.getHeight(null); 3266 if (isHiDPIImage(img)) { 3267 return drawHiDPIImage(img, x, y, x + width, y + height, 0, 0, imgW, 3268 imgH, bg, observer); 3269 } 3270 3271 if (width == imgW && height == imgH) { 3272 return copyImage(img, x, y, 0, 0, width, height, bg, observer); 3273 } 3274 3275 try { 3276 return imagepipe.scaleImage(this, img, x, y, width, height, 3277 bg, observer); 3278 } catch (InvalidPipeException e) { 3279 try { 3280 revalidateAll(); 3281 return imagepipe.scaleImage(this, img, x, y, width, height, 3282 bg, observer); 3283 } catch (InvalidPipeException e2) { 3284 // Still catching the exception; we are not yet ready to 3285 // validate the surfaceData correctly. Fail for now and 3286 // try again next time around. 3287 return false; 3288 } 3289 } finally { 3290 surfaceData.markDirty(); 3291 } 3292 } 3293 3294 /** 3295 * Draws an image at x,y in nonblocking mode. 3296 */ drawImage(Image img, int x, int y, ImageObserver observer)3297 public boolean drawImage(Image img, int x, int y, ImageObserver observer) { 3298 return drawImage(img, x, y, null, observer); 3299 } 3300 3301 /** 3302 * Draws an image at x,y in nonblocking mode with a solid background 3303 * color and a callback object. 3304 */ drawImage(Image img, int x, int y, Color bg, ImageObserver observer)3305 public boolean drawImage(Image img, int x, int y, Color bg, 3306 ImageObserver observer) { 3307 3308 if (img == null) { 3309 return true; 3310 } 3311 3312 if (isHiDPIImage(img)) { 3313 final int imgW = img.getWidth(null); 3314 final int imgH = img.getHeight(null); 3315 return drawHiDPIImage(img, x, y, x + imgW, y + imgH, 0, 0, imgW, 3316 imgH, bg, observer); 3317 } 3318 3319 try { 3320 return imagepipe.copyImage(this, img, x, y, bg, observer); 3321 } catch (InvalidPipeException e) { 3322 try { 3323 revalidateAll(); 3324 return imagepipe.copyImage(this, img, x, y, bg, observer); 3325 } catch (InvalidPipeException e2) { 3326 // Still catching the exception; we are not yet ready to 3327 // validate the surfaceData correctly. Fail for now and 3328 // try again next time around. 3329 return false; 3330 } 3331 } finally { 3332 surfaceData.markDirty(); 3333 } 3334 } 3335 3336 /** 3337 * Draws a subrectangle of an image scaled to a destination rectangle 3338 * in nonblocking mode with a callback object. 3339 */ drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer)3340 public boolean drawImage(Image img, 3341 int dx1, int dy1, int dx2, int dy2, 3342 int sx1, int sy1, int sx2, int sy2, 3343 ImageObserver observer) { 3344 return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null, 3345 observer); 3346 } 3347 3348 /** 3349 * Draws a subrectangle of an image scaled to a destination rectangle in 3350 * nonblocking mode with a solid background color and a callback object. 3351 */ drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver observer)3352 public boolean drawImage(Image img, 3353 int dx1, int dy1, int dx2, int dy2, 3354 int sx1, int sy1, int sx2, int sy2, 3355 Color bgcolor, ImageObserver observer) { 3356 3357 if (img == null) { 3358 return true; 3359 } 3360 3361 if (dx1 == dx2 || dy1 == dy2 || 3362 sx1 == sx2 || sy1 == sy2) 3363 { 3364 return true; 3365 } 3366 3367 if (isHiDPIImage(img)) { 3368 return drawHiDPIImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, 3369 bgcolor, observer); 3370 } 3371 3372 if (((sx2 - sx1) == (dx2 - dx1)) && 3373 ((sy2 - sy1) == (dy2 - dy1))) 3374 { 3375 // Not a scale - forward it to a copy routine 3376 int srcX, srcY, dstX, dstY, width, height; 3377 if (sx2 > sx1) { 3378 width = sx2 - sx1; 3379 srcX = sx1; 3380 dstX = dx1; 3381 } else { 3382 width = sx1 - sx2; 3383 srcX = sx2; 3384 dstX = dx2; 3385 } 3386 if (sy2 > sy1) { 3387 height = sy2-sy1; 3388 srcY = sy1; 3389 dstY = dy1; 3390 } else { 3391 height = sy1-sy2; 3392 srcY = sy2; 3393 dstY = dy2; 3394 } 3395 return copyImage(img, dstX, dstY, srcX, srcY, 3396 width, height, bgcolor, observer); 3397 } 3398 3399 try { 3400 return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2, 3401 sx1, sy1, sx2, sy2, bgcolor, 3402 observer); 3403 } catch (InvalidPipeException e) { 3404 try { 3405 revalidateAll(); 3406 return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2, 3407 sx1, sy1, sx2, sy2, bgcolor, 3408 observer); 3409 } catch (InvalidPipeException e2) { 3410 // Still catching the exception; we are not yet ready to 3411 // validate the surfaceData correctly. Fail for now and 3412 // try again next time around. 3413 return false; 3414 } 3415 } finally { 3416 surfaceData.markDirty(); 3417 } 3418 } 3419 3420 /** 3421 * Draw an image, applying a transform from image space into user space 3422 * before drawing. 3423 * The transformation from user space into device space is done with 3424 * the current transform in the Graphics2D. 3425 * The given transformation is applied to the image before the 3426 * transform attribute in the Graphics2D state is applied. 3427 * The rendering attributes applied include the clip, transform, 3428 * paint or color and composite attributes. Note that the result is 3429 * undefined, if the given transform is non-invertible. 3430 * @param img The image to be drawn. 3431 * @param xform The transformation from image space into user space. 3432 * @param observer The image observer to be notified on the image producing 3433 * progress. 3434 * @see #transform 3435 * @see #setComposite 3436 * @see #setClip 3437 */ drawImage(Image img, AffineTransform xform, ImageObserver observer)3438 public boolean drawImage(Image img, 3439 AffineTransform xform, 3440 ImageObserver observer) { 3441 3442 if (img == null) { 3443 return true; 3444 } 3445 3446 if (xform == null || xform.isIdentity()) { 3447 return drawImage(img, 0, 0, null, observer); 3448 } 3449 3450 if (isHiDPIImage(img)) { 3451 final int w = img.getWidth(null); 3452 final int h = img.getHeight(null); 3453 final AffineTransform tx = new AffineTransform(transform); 3454 transform(xform); 3455 boolean result = drawHiDPIImage(img, 0, 0, w, h, 0, 0, w, h, null, 3456 observer); 3457 transform.setTransform(tx); 3458 invalidateTransform(); 3459 return result; 3460 } 3461 3462 try { 3463 return imagepipe.transformImage(this, img, xform, observer); 3464 } catch (InvalidPipeException e) { 3465 try { 3466 revalidateAll(); 3467 return imagepipe.transformImage(this, img, xform, observer); 3468 } catch (InvalidPipeException e2) { 3469 // Still catching the exception; we are not yet ready to 3470 // validate the surfaceData correctly. Fail for now and 3471 // try again next time around. 3472 return false; 3473 } 3474 } finally { 3475 surfaceData.markDirty(); 3476 } 3477 } 3478 drawImage(BufferedImage bImg, BufferedImageOp op, int x, int y)3479 public void drawImage(BufferedImage bImg, 3480 BufferedImageOp op, 3481 int x, 3482 int y) { 3483 3484 if (bImg == null) { 3485 return; 3486 } 3487 3488 try { 3489 imagepipe.transformImage(this, bImg, op, x, y); 3490 } catch (InvalidPipeException e) { 3491 try { 3492 revalidateAll(); 3493 imagepipe.transformImage(this, bImg, op, x, y); 3494 } catch (InvalidPipeException e2) { 3495 // Still catching the exception; we are not yet ready to 3496 // validate the surfaceData correctly. Fail for now and 3497 // try again next time around. 3498 } 3499 } finally { 3500 surfaceData.markDirty(); 3501 } 3502 } 3503 3504 /** 3505 * Get the rendering context of the font 3506 * within this Graphics2D context. 3507 */ getFontRenderContext()3508 public FontRenderContext getFontRenderContext() { 3509 if (cachedFRC == null) { 3510 int aahint = textAntialiasHint; 3511 if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT && 3512 antialiasHint == SunHints.INTVAL_ANTIALIAS_ON) { 3513 aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON; 3514 } 3515 // Translation components should be excluded from the FRC transform 3516 AffineTransform tx = null; 3517 if (transformState >= TRANSFORM_TRANSLATESCALE) { 3518 if (transform.getTranslateX() == 0 && 3519 transform.getTranslateY() == 0) { 3520 tx = transform; 3521 } else { 3522 tx = new AffineTransform(transform.getScaleX(), 3523 transform.getShearY(), 3524 transform.getShearX(), 3525 transform.getScaleY(), 3526 0, 0); 3527 } 3528 } 3529 cachedFRC = new FontRenderContext(tx, 3530 SunHints.Value.get(SunHints.INTKEY_TEXT_ANTIALIASING, aahint), 3531 SunHints.Value.get(SunHints.INTKEY_FRACTIONALMETRICS, 3532 fractionalMetricsHint)); 3533 } 3534 return cachedFRC; 3535 } 3536 private FontRenderContext cachedFRC; 3537 3538 /** 3539 * This object has no resources to dispose of per se, but the 3540 * doc comments for the base method in java.awt.Graphics imply 3541 * that this object will not be useable after it is disposed. 3542 * So, we sabotage the object to prevent further use to prevent 3543 * developers from relying on behavior that may not work on 3544 * other, less forgiving, VMs that really need to dispose of 3545 * resources. 3546 */ dispose()3547 public void dispose() { 3548 surfaceData = NullSurfaceData.theInstance; 3549 invalidatePipe(); 3550 } 3551 3552 /** 3553 * Graphics has a finalize method that automatically calls dispose() 3554 * for subclasses. For SunGraphics2D we do not need to be finalized 3555 * so that method simply causes us to be enqueued on the Finalizer 3556 * queues for no good reason. Unfortunately, that method and 3557 * implementation are now considered part of the public contract 3558 * of that base class so we can not remove or gut the method. 3559 * We override it here with an empty method and the VM is smart 3560 * enough to know that if our override is empty then it should not 3561 * mark us as finalizeable. 3562 */ finalize()3563 public void finalize() { 3564 // DO NOT REMOVE THIS METHOD 3565 } 3566 3567 /** 3568 * Returns destination that this Graphics renders to. This could be 3569 * either an Image or a Component; subclasses of SurfaceData are 3570 * responsible for returning the appropriate object. 3571 */ getDestination()3572 public Object getDestination() { 3573 return surfaceData.getDestination(); 3574 } 3575 3576 /** 3577 * {@inheritDoc} 3578 * 3579 * @see sun.java2d.DestSurfaceProvider#getDestSurface 3580 */ 3581 @Override getDestSurface()3582 public Surface getDestSurface() { 3583 return surfaceData; 3584 } 3585 } 3586