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