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