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