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