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