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