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