1 /* 2 * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.java2d; 27 28 import java.awt.Graphics; 29 import java.awt.Graphics2D; 30 import java.awt.RenderingHints; 31 import java.awt.RenderingHints.Key; 32 import java.awt.geom.Area; 33 import java.awt.geom.AffineTransform; 34 import java.awt.geom.NoninvertibleTransformException; 35 import java.awt.AlphaComposite; 36 import java.awt.BasicStroke; 37 import java.awt.image.BufferedImage; 38 import java.awt.image.BufferedImageOp; 39 import java.awt.image.RenderedImage; 40 import java.awt.image.renderable.RenderableImage; 41 import java.awt.image.renderable.RenderContext; 42 import java.awt.image.AffineTransformOp; 43 import java.awt.image.Raster; 44 import java.awt.image.WritableRaster; 45 import java.awt.Image; 46 import java.awt.Composite; 47 import java.awt.Color; 48 import java.awt.image.ColorModel; 49 import java.awt.GraphicsConfiguration; 50 import java.awt.Paint; 51 import java.awt.GradientPaint; 52 import java.awt.LinearGradientPaint; 53 import java.awt.RadialGradientPaint; 54 import java.awt.TexturePaint; 55 import java.awt.geom.Rectangle2D; 56 import java.awt.geom.PathIterator; 57 import java.awt.geom.GeneralPath; 58 import java.awt.Shape; 59 import java.awt.Stroke; 60 import java.awt.FontMetrics; 61 import java.awt.Rectangle; 62 import java.text.AttributedCharacterIterator; 63 import java.awt.Font; 64 import java.awt.Point; 65 import java.awt.image.ImageObserver; 66 import java.awt.Transparency; 67 import java.awt.font.GlyphVector; 68 import java.awt.font.TextLayout; 69 70 import sun.awt.image.SurfaceManager; 71 import sun.font.FontDesignMetrics; 72 import sun.font.FontUtilities; 73 import sun.java2d.pipe.PixelDrawPipe; 74 import sun.java2d.pipe.PixelFillPipe; 75 import sun.java2d.pipe.ShapeDrawPipe; 76 import sun.java2d.pipe.ValidatePipe; 77 import sun.java2d.pipe.ShapeSpanIterator; 78 import sun.java2d.pipe.Region; 79 import sun.java2d.pipe.TextPipe; 80 import sun.java2d.pipe.DrawImagePipe; 81 import sun.java2d.pipe.LoopPipe; 82 import sun.java2d.loops.FontInfo; 83 import sun.java2d.loops.RenderLoops; 84 import sun.java2d.loops.CompositeType; 85 import sun.java2d.loops.SurfaceType; 86 import sun.java2d.loops.Blit; 87 import sun.java2d.loops.MaskFill; 88 import java.awt.font.FontRenderContext; 89 import sun.java2d.loops.XORComposite; 90 import sun.awt.ConstrainableGraphics; 91 import sun.awt.SunHints; 92 import java.util.Map; 93 import java.util.Iterator; 94 import sun.misc.PerformanceLogger; 95 96 import java.lang.annotation.Native; 97 import java.awt.image.MultiResolutionImage; 98 99 import static java.awt.geom.AffineTransform.TYPE_FLIP; 100 import static java.awt.geom.AffineTransform.TYPE_MASK_SCALE; 101 import static java.awt.geom.AffineTransform.TYPE_TRANSLATION; 102 import sun.awt.image.MultiResolutionToolkitImage; 103 import sun.awt.image.ToolkitImage; 104 105 /** 106 * This is a the master Graphics2D superclass for all of the Sun 107 * Graphics implementations. This class relies on subclasses to 108 * manage the various device information, but provides an overall 109 * general framework for performing all of the requests in the 110 * Graphics and Graphics2D APIs. 111 * 112 * @author Jim Graham 113 */ 114 public final class SunGraphics2D 115 extends Graphics2D 116 implements ConstrainableGraphics, Cloneable, DestSurfaceProvider 117 { 118 /* 119 * Attribute States 120 */ 121 /* Paint */ 122 @Native 123 public static final int PAINT_CUSTOM = 6; /* Any other Paint object */ 124 @Native 125 public static final int PAINT_TEXTURE = 5; /* Tiled Image */ 126 @Native 127 public static final int PAINT_RAD_GRADIENT = 4; /* Color RadialGradient */ 128 @Native 129 public static final int PAINT_LIN_GRADIENT = 3; /* Color LinearGradient */ 130 @Native 131 public static final int PAINT_GRADIENT = 2; /* Color Gradient */ 132 @Native 133 public static final int PAINT_ALPHACOLOR = 1; /* Non-opaque Color */ 134 @Native 135 public static final int PAINT_OPAQUECOLOR = 0; /* Opaque Color */ 136 137 /* Composite*/ 138 @Native 139 public static final int COMP_CUSTOM = 3;/* Custom Composite */ 140 @Native 141 public static final int COMP_XOR = 2;/* XOR Mode Composite */ 142 @Native 143 public static final int COMP_ALPHA = 1;/* AlphaComposite */ 144 @Native 145 public static final int COMP_ISCOPY = 0;/* simple stores into destination, 146 * i.e. Src, SrcOverNoEa, and other 147 * alpha modes which replace 148 * the destination. 149 */ 150 151 /* Stroke */ 152 @Native 153 public static final int STROKE_CUSTOM = 3; /* custom Stroke */ 154 @Native 155 public static final int STROKE_WIDE = 2; /* BasicStroke */ 156 @Native 157 public static final int STROKE_THINDASHED = 1; /* BasicStroke */ 158 @Native 159 public static final int STROKE_THIN = 0; /* BasicStroke */ 160 161 /* Transform */ 162 @Native 163 public static final int TRANSFORM_GENERIC = 4; /* any 3x2 */ 164 @Native 165 public static final int TRANSFORM_TRANSLATESCALE = 3; /* scale XY */ 166 @Native 167 public static final int TRANSFORM_ANY_TRANSLATE = 2; /* non-int translate */ 168 @Native 169 public static final int TRANSFORM_INT_TRANSLATE = 1; /* int translate */ 170 @Native 171 public static final int TRANSFORM_ISIDENT = 0; /* Identity */ 172 173 /* Clipping */ 174 @Native 175 public static final int CLIP_SHAPE = 2; /* arbitrary clip */ 176 @Native 177 public static final int CLIP_RECTANGULAR = 1; /* rectangular clip */ 178 @Native 179 public static final int CLIP_DEVICE = 0; /* no clipping set */ 180 181 /* The following fields are used when the current Paint is a Color. */ 182 public int eargb; // ARGB value with ExtraAlpha baked in 183 public int pixel; // pixel value for eargb 184 185 public SurfaceData surfaceData; 186 187 public PixelDrawPipe drawpipe; 188 public PixelFillPipe fillpipe; 189 public DrawImagePipe imagepipe; 190 public ShapeDrawPipe shapepipe; 191 public TextPipe textpipe; 192 public MaskFill alphafill; 193 194 public RenderLoops loops; 195 196 public CompositeType imageComp; /* Image Transparency checked on fly */ 197 198 public int paintState; 199 public int compositeState; 200 public int strokeState; 201 public int transformState; 202 public int clipState; 203 204 public Color foregroundColor; 205 public Color backgroundColor; 206 207 public AffineTransform transform; 208 public int transX; 209 public int transY; 210 211 protected static final Stroke defaultStroke = new BasicStroke(); 212 protected static final Composite defaultComposite = AlphaComposite.SrcOver; 213 private static final Font defaultFont = 214 new Font(Font.DIALOG, Font.PLAIN, 12); 215 216 public Paint paint; 217 public Stroke stroke; 218 public Composite composite; 219 protected Font font; 220 protected FontMetrics fontMetrics; 221 222 public int renderHint; 223 public int antialiasHint; 224 public int textAntialiasHint; 225 protected int fractionalMetricsHint; 226 227 /* A gamma adjustment to the colour used in lcd text blitting */ 228 public int lcdTextContrast; 229 private static int lcdTextContrastDefaultValue = 140; 230 231 private int interpolationHint; // raw value of rendering Hint 232 public int strokeHint; 233 234 public int interpolationType; // algorithm choice based on 235 // interpolation and render Hints 236 237 public RenderingHints hints; 238 239 public Region constrainClip; // lightweight bounds in pixels 240 public int constrainX; 241 public int constrainY; 242 243 public Region clipRegion; 244 public Shape usrClip; 245 protected Region devClip; // Actual physical drawable in pixels 246 247 private 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 isHiDPIImage(final Image img) { 3090 return (SurfaceManager.getImageScale(img) != 1) 3091 || img instanceof MultiResolutionImage; 3092 } 3093 3094 private boolean drawHiDPIImage(Image img, int dx1, int dy1, int dx2, 3095 int dy2, int sx1, int sy1, int sx2, int sy2, 3096 Color bgcolor, ImageObserver observer) { 3097 3098 if (SurfaceManager.getImageScale(img) != 1) { // Volatile Image 3099 final int scale = SurfaceManager.getImageScale(img); 3100 sx1 = Region.clipScale(sx1, scale); 3101 sx2 = Region.clipScale(sx2, scale); 3102 sy1 = Region.clipScale(sy1, scale); 3103 sy2 = Region.clipScale(sy2, scale); 3104 } else if (img instanceof MultiResolutionImage) { 3105 // get scaled destination image size 3106 3107 int width = img.getWidth(observer); 3108 int height = img.getHeight(observer); 3109 3110 Image resolutionVariant = getResolutionVariant( 3111 (MultiResolutionImage) img, width, height, 3112 dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2); 3113 3114 if (resolutionVariant != img && resolutionVariant != null) { 3115 // recalculate source region for the resolution variant 3116 3117 ImageObserver rvObserver = MultiResolutionToolkitImage. 3118 getResolutionVariantObserver(img, observer, 3119 width, height, -1, -1); 3120 3121 int rvWidth = resolutionVariant.getWidth(rvObserver); 3122 int rvHeight = resolutionVariant.getHeight(rvObserver); 3123 3124 if (0 < width && 0 < height && 0 < rvWidth && 0 < rvHeight) { 3125 3126 float widthScale = ((float) rvWidth) / width; 3127 float heightScale = ((float) rvHeight) / height; 3128 3129 sx1 = Region.clipScale(sx1, widthScale); 3130 sy1 = Region.clipScale(sy1, heightScale); 3131 sx2 = Region.clipScale(sx2, widthScale); 3132 sy2 = Region.clipScale(sy2, heightScale); 3133 3134 observer = rvObserver; 3135 img = resolutionVariant; 3136 } 3137 } 3138 } 3139 3140 try { 3141 return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2, sx1, sy1, 3142 sx2, sy2, bgcolor, observer); 3143 } catch (InvalidPipeException e) { 3144 try { 3145 revalidateAll(); 3146 return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2, sx1, 3147 sy1, sx2, sy2, bgcolor, observer); 3148 } catch (InvalidPipeException e2) { 3149 // Still catching the exception; we are not yet ready to 3150 // validate the surfaceData correctly. Fail for now and 3151 // try again next time around. 3152 return false; 3153 } 3154 } finally { 3155 surfaceData.markDirty(); 3156 } 3157 } 3158 3159 private Image getResolutionVariant(MultiResolutionImage img, 3160 int srcWidth, int srcHeight, int dx1, int dy1, int dx2, int dy2, 3161 int sx1, int sy1, int sx2, int sy2) { 3162 3163 if (srcWidth <= 0 || srcHeight <= 0) { 3164 return null; 3165 } 3166 3167 int sw = sx2 - sx1; 3168 int sh = sy2 - sy1; 3169 3170 if (sw == 0 || sh == 0) { 3171 return null; 3172 } 3173 3174 int type = transform.getType(); 3175 int dw = dx2 - dx1; 3176 int dh = dy2 - dy1; 3177 3178 double destImageWidth; 3179 double destImageHeight; 3180 3181 if (resolutionVariantHint == SunHints.INTVAL_RESOLUTION_VARIANT_BASE) { 3182 destImageWidth = srcWidth; 3183 destImageHeight = srcHeight; 3184 } else if (resolutionVariantHint == SunHints.INTVAL_RESOLUTION_VARIANT_DPI_FIT) { 3185 AffineTransform configTransform = getDefaultTransform(); 3186 if (configTransform.isIdentity()) { 3187 destImageWidth = srcWidth; 3188 destImageHeight = srcHeight; 3189 } else { 3190 destImageWidth = srcWidth * configTransform.getScaleX(); 3191 destImageHeight = srcHeight * configTransform.getScaleY(); 3192 } 3193 } else { 3194 double destRegionWidth; 3195 double destRegionHeight; 3196 3197 if ((type & ~(TYPE_TRANSLATION | TYPE_FLIP)) == 0) { 3198 destRegionWidth = dw; 3199 destRegionHeight = dh; 3200 } else if ((type & ~(TYPE_TRANSLATION | TYPE_FLIP | TYPE_MASK_SCALE)) == 0) { 3201 destRegionWidth = dw * transform.getScaleX(); 3202 destRegionHeight = dh * transform.getScaleY(); 3203 } else { 3204 destRegionWidth = dw * Math.hypot( 3205 transform.getScaleX(), transform.getShearY()); 3206 destRegionHeight = dh * Math.hypot( 3207 transform.getShearX(), transform.getScaleY()); 3208 } 3209 destImageWidth = Math.abs(srcWidth * destRegionWidth / sw); 3210 destImageHeight = Math.abs(srcHeight * destRegionHeight / sh); 3211 } 3212 3213 Image resolutionVariant 3214 = img.getResolutionVariant(destImageWidth, destImageHeight); 3215 3216 if (resolutionVariant instanceof ToolkitImage 3217 && ((ToolkitImage) resolutionVariant).hasError()) { 3218 return null; 3219 } 3220 3221 return resolutionVariant; 3222 } 3223 3224 /** 3225 * Draws an image scaled to x,y,w,h in nonblocking mode with a 3226 * callback object. 3227 */ 3228 public boolean drawImage(Image img, int x, int y, int width, int height, 3229 ImageObserver observer) { 3230 return drawImage(img, x, y, width, height, null, observer); 3231 } 3232 3233 /** 3234 * Not part of the advertised API but a useful utility method 3235 * to call internally. This is for the case where we are 3236 * drawing to/from given coordinates using a given width/height, 3237 * but we guarantee that the surfaceData's width/height of the src and dest 3238 * areas are equal (no scale needed). Note that this method intentionally 3239 * ignore scale factor of the source image, and copy it as is. 3240 */ 3241 public boolean copyImage(Image img, int dx, int dy, int sx, int sy, 3242 int width, int height, Color bgcolor, 3243 ImageObserver observer) { 3244 try { 3245 return imagepipe.copyImage(this, img, dx, dy, sx, sy, 3246 width, height, bgcolor, observer); 3247 } catch (InvalidPipeException e) { 3248 try { 3249 revalidateAll(); 3250 return imagepipe.copyImage(this, img, dx, dy, sx, sy, 3251 width, height, bgcolor, observer); 3252 } catch (InvalidPipeException e2) { 3253 // Still catching the exception; we are not yet ready to 3254 // validate the surfaceData correctly. Fail for now and 3255 // try again next time around. 3256 return false; 3257 } 3258 } finally { 3259 surfaceData.markDirty(); 3260 } 3261 } 3262 3263 /** 3264 * Draws an image scaled to x,y,w,h in nonblocking mode with a 3265 * solid background color and a callback object. 3266 */ 3267 public boolean drawImage(Image img, int x, int y, int width, int height, 3268 Color bg, ImageObserver observer) { 3269 3270 if (img == null) { 3271 return true; 3272 } 3273 3274 if ((width == 0) || (height == 0)) { 3275 return true; 3276 } 3277 3278 final int imgW = img.getWidth(null); 3279 final int imgH = img.getHeight(null); 3280 if (isHiDPIImage(img)) { 3281 return drawHiDPIImage(img, x, y, x + width, y + height, 0, 0, imgW, 3282 imgH, bg, observer); 3283 } 3284 3285 if (width == imgW && height == imgH) { 3286 return copyImage(img, x, y, 0, 0, width, height, bg, observer); 3287 } 3288 3289 try { 3290 return imagepipe.scaleImage(this, img, x, y, width, height, 3291 bg, observer); 3292 } catch (InvalidPipeException e) { 3293 try { 3294 revalidateAll(); 3295 return imagepipe.scaleImage(this, img, x, y, width, height, 3296 bg, observer); 3297 } catch (InvalidPipeException e2) { 3298 // Still catching the exception; we are not yet ready to 3299 // validate the surfaceData correctly. Fail for now and 3300 // try again next time around. 3301 return false; 3302 } 3303 } finally { 3304 surfaceData.markDirty(); 3305 } 3306 } 3307 3308 /** 3309 * Draws an image at x,y in nonblocking mode. 3310 */ 3311 public boolean drawImage(Image img, int x, int y, ImageObserver observer) { 3312 return drawImage(img, x, y, null, observer); 3313 } 3314 3315 /** 3316 * Draws an image at x,y in nonblocking mode with a solid background 3317 * color and a callback object. 3318 */ 3319 public boolean drawImage(Image img, int x, int y, Color bg, 3320 ImageObserver observer) { 3321 3322 if (img == null) { 3323 return true; 3324 } 3325 3326 if (isHiDPIImage(img)) { 3327 final int imgW = img.getWidth(null); 3328 final int imgH = img.getHeight(null); 3329 return drawHiDPIImage(img, x, y, x + imgW, y + imgH, 0, 0, imgW, 3330 imgH, bg, observer); 3331 } 3332 3333 try { 3334 return imagepipe.copyImage(this, img, x, y, bg, observer); 3335 } catch (InvalidPipeException e) { 3336 try { 3337 revalidateAll(); 3338 return imagepipe.copyImage(this, img, x, y, bg, observer); 3339 } catch (InvalidPipeException e2) { 3340 // Still catching the exception; we are not yet ready to 3341 // validate the surfaceData correctly. Fail for now and 3342 // try again next time around. 3343 return false; 3344 } 3345 } finally { 3346 surfaceData.markDirty(); 3347 } 3348 } 3349 3350 /** 3351 * Draws a subrectangle of an image scaled to a destination rectangle 3352 * in nonblocking mode with a callback object. 3353 */ 3354 public boolean drawImage(Image img, 3355 int dx1, int dy1, int dx2, int dy2, 3356 int sx1, int sy1, int sx2, int sy2, 3357 ImageObserver observer) { 3358 return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null, 3359 observer); 3360 } 3361 3362 /** 3363 * Draws a subrectangle of an image scaled to a destination rectangle in 3364 * nonblocking mode with a solid background color and a callback object. 3365 */ 3366 public boolean drawImage(Image img, 3367 int dx1, int dy1, int dx2, int dy2, 3368 int sx1, int sy1, int sx2, int sy2, 3369 Color bgcolor, ImageObserver observer) { 3370 3371 if (img == null) { 3372 return true; 3373 } 3374 3375 if (dx1 == dx2 || dy1 == dy2 || 3376 sx1 == sx2 || sy1 == sy2) 3377 { 3378 return true; 3379 } 3380 3381 if (isHiDPIImage(img)) { 3382 return drawHiDPIImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, 3383 bgcolor, observer); 3384 } 3385 3386 if (((sx2 - sx1) == (dx2 - dx1)) && 3387 ((sy2 - sy1) == (dy2 - dy1))) 3388 { 3389 // Not a scale - forward it to a copy routine 3390 int srcX, srcY, dstX, dstY, width, height; 3391 if (sx2 > sx1) { 3392 width = sx2 - sx1; 3393 srcX = sx1; 3394 dstX = dx1; 3395 } else { 3396 width = sx1 - sx2; 3397 srcX = sx2; 3398 dstX = dx2; 3399 } 3400 if (sy2 > sy1) { 3401 height = sy2-sy1; 3402 srcY = sy1; 3403 dstY = dy1; 3404 } else { 3405 height = sy1-sy2; 3406 srcY = sy2; 3407 dstY = dy2; 3408 } 3409 return copyImage(img, dstX, dstY, srcX, srcY, 3410 width, height, bgcolor, observer); 3411 } 3412 3413 try { 3414 return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2, 3415 sx1, sy1, sx2, sy2, bgcolor, 3416 observer); 3417 } catch (InvalidPipeException e) { 3418 try { 3419 revalidateAll(); 3420 return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2, 3421 sx1, sy1, sx2, sy2, bgcolor, 3422 observer); 3423 } catch (InvalidPipeException e2) { 3424 // Still catching the exception; we are not yet ready to 3425 // validate the surfaceData correctly. Fail for now and 3426 // try again next time around. 3427 return false; 3428 } 3429 } finally { 3430 surfaceData.markDirty(); 3431 } 3432 } 3433 3434 /** 3435 * Draw an image, applying a transform from image space into user space 3436 * before drawing. 3437 * The transformation from user space into device space is done with 3438 * the current transform in the Graphics2D. 3439 * The given transformation is applied to the image before the 3440 * transform attribute in the Graphics2D state is applied. 3441 * The rendering attributes applied include the clip, transform, 3442 * paint or color and composite attributes. Note that the result is 3443 * undefined, if the given transform is non-invertible. 3444 * @param img The image to be drawn. 3445 * @param xform The transformation from image space into user space. 3446 * @param observer The image observer to be notified on the image producing 3447 * progress. 3448 * @see #transform 3449 * @see #setComposite 3450 * @see #setClip 3451 */ 3452 public boolean drawImage(Image img, 3453 AffineTransform xform, 3454 ImageObserver observer) { 3455 3456 if (img == null) { 3457 return true; 3458 } 3459 3460 if (xform == null || xform.isIdentity()) { 3461 return drawImage(img, 0, 0, null, observer); 3462 } 3463 3464 if (isHiDPIImage(img)) { 3465 final int w = img.getWidth(null); 3466 final int h = img.getHeight(null); 3467 final AffineTransform tx = new AffineTransform(transform); 3468 transform(xform); 3469 boolean result = drawHiDPIImage(img, 0, 0, w, h, 0, 0, w, h, null, 3470 observer); 3471 transform.setTransform(tx); 3472 invalidateTransform(); 3473 return result; 3474 } 3475 3476 try { 3477 return imagepipe.transformImage(this, img, xform, observer); 3478 } catch (InvalidPipeException e) { 3479 try { 3480 revalidateAll(); 3481 return imagepipe.transformImage(this, img, xform, observer); 3482 } catch (InvalidPipeException e2) { 3483 // Still catching the exception; we are not yet ready to 3484 // validate the surfaceData correctly. Fail for now and 3485 // try again next time around. 3486 return false; 3487 } 3488 } finally { 3489 surfaceData.markDirty(); 3490 } 3491 } 3492 3493 public void drawImage(BufferedImage bImg, 3494 BufferedImageOp op, 3495 int x, 3496 int y) { 3497 3498 if (bImg == null) { 3499 return; 3500 } 3501 3502 try { 3503 imagepipe.transformImage(this, bImg, op, x, y); 3504 } catch (InvalidPipeException e) { 3505 try { 3506 revalidateAll(); 3507 imagepipe.transformImage(this, bImg, op, x, y); 3508 } catch (InvalidPipeException e2) { 3509 // Still catching the exception; we are not yet ready to 3510 // validate the surfaceData correctly. Fail for now and 3511 // try again next time around. 3512 } 3513 } finally { 3514 surfaceData.markDirty(); 3515 } 3516 } 3517 3518 /** 3519 * Get the rendering context of the font 3520 * within this Graphics2D context. 3521 */ 3522 public FontRenderContext getFontRenderContext() { 3523 if (cachedFRC == null) { 3524 int aahint = textAntialiasHint; 3525 if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT && 3526 antialiasHint == SunHints.INTVAL_ANTIALIAS_ON) { 3527 aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON; 3528 } 3529 // Translation components should be excluded from the FRC transform 3530 AffineTransform tx = null; 3531 if (transformState >= TRANSFORM_TRANSLATESCALE) { 3532 if (transform.getTranslateX() == 0 && 3533 transform.getTranslateY() == 0) { 3534 tx = transform; 3535 } else { 3536 tx = new AffineTransform(transform.getScaleX(), 3537 transform.getShearY(), 3538 transform.getShearX(), 3539 transform.getScaleY(), 3540 0, 0); 3541 } 3542 } 3543 cachedFRC = new FontRenderContext(tx, 3544 SunHints.Value.get(SunHints.INTKEY_TEXT_ANTIALIASING, aahint), 3545 SunHints.Value.get(SunHints.INTKEY_FRACTIONALMETRICS, 3546 fractionalMetricsHint)); 3547 } 3548 return cachedFRC; 3549 } 3550 private FontRenderContext cachedFRC; 3551 3552 /** 3553 * This object has no resources to dispose of per se, but the 3554 * doc comments for the base method in java.awt.Graphics imply 3555 * that this object will not be useable after it is disposed. 3556 * So, we sabotage the object to prevent further use to prevent 3557 * developers from relying on behavior that may not work on 3558 * other, less forgiving, VMs that really need to dispose of 3559 * resources. 3560 */ 3561 public void dispose() { 3562 surfaceData = NullSurfaceData.theInstance; 3563 invalidatePipe(); 3564 } 3565 3566 /** 3567 * Graphics has a finalize method that automatically calls dispose() 3568 * for subclasses. For SunGraphics2D we do not need to be finalized 3569 * so that method simply causes us to be enqueued on the Finalizer 3570 * queues for no good reason. Unfortunately, that method and 3571 * implementation are now considered part of the public contract 3572 * of that base class so we can not remove or gut the method. 3573 * We override it here with an empty method and the VM is smart 3574 * enough to know that if our override is empty then it should not 3575 * mark us as finalizeable. 3576 */ 3577 public void finalize() { 3578 // DO NOT REMOVE THIS METHOD 3579 } 3580 3581 /** 3582 * Returns destination that this Graphics renders to. This could be 3583 * either an Image or a Component; subclasses of SurfaceData are 3584 * responsible for returning the appropriate object. 3585 */ 3586 public Object getDestination() { 3587 return surfaceData.getDestination(); 3588 } 3589 3590 /** 3591 * {@inheritDoc} 3592 * 3593 * @see sun.java2d.DestSurfaceProvider#getDestSurface 3594 */ 3595 @Override 3596 public Surface getDestSurface() { 3597 return surfaceData; 3598 } 3599 }