1 /* 2 * Copyright (c) 1996, 2018, 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 clipRegion = devClip.getIntersection((Rectangle2D) usrClip); 1906 } else { 1907 PathIterator cpi = usrClip.getPathIterator(null); 1908 int[] box = new int[4]; 1909 ShapeSpanIterator sr = LoopPipe.getFillSSI(this); 1910 try { 1911 sr.setOutputArea(devClip); 1912 sr.appendPath(cpi); 1913 sr.getPathBox(box); 1914 Region r = Region.getInstance(box, sr); 1915 clipRegion = r; 1916 clipState = 1917 r.isRectangular() ? CLIP_RECTANGULAR : CLIP_SHAPE; 1918 } finally { 1919 sr.dispose(); 1920 } 1921 } 1922 if (origClipState != clipState && 1923 (clipState == CLIP_SHAPE || origClipState == CLIP_SHAPE)) 1924 { 1925 validFontInfo = false; 1926 invalidatePipe(); 1927 } 1928 } 1929 1930 static final int NON_RECTILINEAR_TRANSFORM_MASK = 1931 (AffineTransform.TYPE_GENERAL_TRANSFORM | 1932 AffineTransform.TYPE_GENERAL_ROTATION); 1933 1934 protected Shape transformShape(Shape s) { 1935 if (s == null) { 1936 return null; 1937 } 1938 if (transformState > TRANSFORM_INT_TRANSLATE) { 1939 return transformShape(transform, s); 1940 } else { 1941 return transformShape(transX, transY, s); 1942 } 1943 } 1944 1945 public Shape untransformShape(Shape s) { 1946 if (s == null) { 1947 return null; 1948 } 1949 if (transformState > TRANSFORM_INT_TRANSLATE) { 1950 try { 1951 return transformShape(transform.createInverse(), s); 1952 } catch (NoninvertibleTransformException e) { 1953 return null; 1954 } 1955 } else { 1956 return transformShape(-transX, -transY, s); 1957 } 1958 } 1959 1960 protected static Shape transformShape(int tx, int ty, Shape s) { 1961 if (s == null) { 1962 return null; 1963 } 1964 1965 if (s instanceof Rectangle) { 1966 Rectangle r = s.getBounds(); 1967 r.translate(tx, ty); 1968 return r; 1969 } 1970 if (s instanceof Rectangle2D) { 1971 Rectangle2D rect = (Rectangle2D) s; 1972 return new Rectangle2D.Double(rect.getX() + tx, 1973 rect.getY() + ty, 1974 rect.getWidth(), 1975 rect.getHeight()); 1976 } 1977 1978 if (tx == 0 && ty == 0) { 1979 return cloneShape(s); 1980 } 1981 1982 AffineTransform mat = AffineTransform.getTranslateInstance(tx, ty); 1983 return mat.createTransformedShape(s); 1984 } 1985 1986 protected static Shape transformShape(AffineTransform tx, Shape clip) { 1987 if (clip == null) { 1988 return null; 1989 } 1990 1991 if (clip instanceof Rectangle2D && 1992 (tx.getType() & NON_RECTILINEAR_TRANSFORM_MASK) == 0) 1993 { 1994 Rectangle2D rect = (Rectangle2D) clip; 1995 double[] matrix = new double[4]; 1996 matrix[0] = rect.getX(); 1997 matrix[1] = rect.getY(); 1998 matrix[2] = matrix[0] + rect.getWidth(); 1999 matrix[3] = matrix[1] + rect.getHeight(); 2000 tx.transform(matrix, 0, matrix, 0, 2); 2001 fixRectangleOrientation(matrix, rect); 2002 return new Rectangle2D.Double(matrix[0], matrix[1], 2003 matrix[2] - matrix[0], 2004 matrix[3] - matrix[1]); 2005 } 2006 2007 if (tx.isIdentity()) { 2008 return cloneShape(clip); 2009 } 2010 2011 return tx.createTransformedShape(clip); 2012 } 2013 2014 /** 2015 * Sets orientation of the rectangle according to the clip. 2016 */ 2017 private static void fixRectangleOrientation(double[] m, Rectangle2D clip) { 2018 if (clip.getWidth() > 0 != (m[2] - m[0] > 0)) { 2019 double t = m[0]; 2020 m[0] = m[2]; 2021 m[2] = t; 2022 } 2023 if (clip.getHeight() > 0 != (m[3] - m[1] > 0)) { 2024 double t = m[1]; 2025 m[1] = m[3]; 2026 m[3] = t; 2027 } 2028 } 2029 2030 public void clipRect(int x, int y, int w, int h) { 2031 clip(new Rectangle(x, y, w, h)); 2032 } 2033 2034 public void setClip(int x, int y, int w, int h) { 2035 setClip(new Rectangle(x, y, w, h)); 2036 } 2037 2038 public Shape getClip() { 2039 return untransformShape(usrClip); 2040 } 2041 2042 public void setClip(Shape sh) { 2043 usrClip = transformShape(sh); 2044 validateCompClip(); 2045 } 2046 2047 /** 2048 * Intersects the current clip with the specified Path and sets the 2049 * current clip to the resulting intersection. The clip is transformed 2050 * with the current transform in the Graphics2D state before being 2051 * intersected with the current clip. This method is used to make the 2052 * current clip smaller. To make the clip larger, use any setClip method. 2053 * @param s The Path to be intersected with the current clip. 2054 */ 2055 public void clip(Shape s) { 2056 s = transformShape(s); 2057 if (usrClip != null) { 2058 s = intersectShapes(usrClip, s, true, true); 2059 } 2060 usrClip = s; 2061 validateCompClip(); 2062 } 2063 2064 public void setPaintMode() { 2065 setComposite(AlphaComposite.SrcOver); 2066 } 2067 2068 public void setXORMode(Color c) { 2069 if (c == null) { 2070 throw new IllegalArgumentException("null XORColor"); 2071 } 2072 setComposite(new XORComposite(c, surfaceData)); 2073 } 2074 2075 Blit lastCAblit; 2076 Composite lastCAcomp; 2077 2078 public void copyArea(int x, int y, int w, int h, int dx, int dy) { 2079 try { 2080 doCopyArea(x, y, w, h, dx, dy); 2081 } catch (InvalidPipeException e) { 2082 try { 2083 revalidateAll(); 2084 doCopyArea(x, y, w, h, dx, dy); 2085 } catch (InvalidPipeException e2) { 2086 // Still catching the exception; we are not yet ready to 2087 // validate the surfaceData correctly. Fail for now and 2088 // try again next time around. 2089 } 2090 } finally { 2091 surfaceData.markDirty(); 2092 } 2093 } 2094 2095 private void doCopyArea(int x, int y, int w, int h, int dx, int dy) { 2096 if (w <= 0 || h <= 0) { 2097 return; 2098 } 2099 2100 if (transformState == SunGraphics2D.TRANSFORM_ISIDENT) { 2101 // do nothing 2102 } else if (transformState <= SunGraphics2D.TRANSFORM_ANY_TRANSLATE) { 2103 x += transX; 2104 y += transY; 2105 } else if (transformState == SunGraphics2D.TRANSFORM_TRANSLATESCALE) { 2106 final double[] coords = {x, y, x + w, y + h, x + dx, y + dy}; 2107 transform.transform(coords, 0, coords, 0, 3); 2108 x = (int) Math.ceil(coords[0] - 0.5); 2109 y = (int) Math.ceil(coords[1] - 0.5); 2110 w = ((int) Math.ceil(coords[2] - 0.5)) - x; 2111 h = ((int) Math.ceil(coords[3] - 0.5)) - y; 2112 dx = ((int) Math.ceil(coords[4] - 0.5)) - x; 2113 dy = ((int) Math.ceil(coords[5] - 0.5)) - y; 2114 // In case of negative scale transform, reflect the rect coords. 2115 if (w < 0) { 2116 w = -w; 2117 x -= w; 2118 } 2119 if (h < 0) { 2120 h = -h; 2121 y -= h; 2122 } 2123 } else { 2124 throw new InternalError("transformed copyArea not implemented yet"); 2125 } 2126 2127 SurfaceData theData = surfaceData; 2128 if (theData.copyArea(this, x, y, w, h, dx, dy)) { 2129 return; 2130 } 2131 2132 // REMIND: This method does not deal with missing data from the 2133 // source object (i.e. it does not send exposure events...) 2134 2135 Region clip = getCompClip(); 2136 2137 Composite comp = composite; 2138 if (lastCAcomp != comp) { 2139 SurfaceType dsttype = theData.getSurfaceType(); 2140 CompositeType comptype = imageComp; 2141 if (CompositeType.SrcOverNoEa.equals(comptype) && 2142 theData.getTransparency() == Transparency.OPAQUE) 2143 { 2144 comptype = CompositeType.SrcNoEa; 2145 } 2146 lastCAblit = Blit.locate(dsttype, comptype, dsttype); 2147 lastCAcomp = comp; 2148 } 2149 2150 Blit ob = lastCAblit; 2151 if (dy == 0 && dx > 0 && dx < w) { 2152 while (w > 0) { 2153 int partW = Math.min(w, dx); 2154 w -= partW; 2155 int sx = x + w; 2156 ob.Blit(theData, theData, comp, clip, 2157 sx, y, sx+dx, y+dy, partW, h); 2158 } 2159 return; 2160 } 2161 if (dy > 0 && dy < h && dx > -w && dx < w) { 2162 while (h > 0) { 2163 int partH = Math.min(h, dy); 2164 h -= partH; 2165 int sy = y + h; 2166 ob.Blit(theData, theData, comp, clip, 2167 x, sy, x+dx, sy+dy, w, partH); 2168 } 2169 return; 2170 } 2171 ob.Blit(theData, theData, comp, clip, x, y, x+dx, y+dy, w, h); 2172 } 2173 2174 /* 2175 public void XcopyArea(int x, int y, int w, int h, int dx, int dy) { 2176 Rectangle rect = new Rectangle(x, y, w, h); 2177 rect = transformBounds(rect, transform); 2178 Point2D point = new Point2D.Float(dx, dy); 2179 Point2D root = new Point2D.Float(0, 0); 2180 point = transform.transform(point, point); 2181 root = transform.transform(root, root); 2182 int fdx = (int)(point.getX()-root.getX()); 2183 int fdy = (int)(point.getY()-root.getY()); 2184 2185 Rectangle r = getCompBounds().intersection(rect.getBounds()); 2186 2187 if (r.isEmpty()) { 2188 return; 2189 } 2190 2191 // Begin Rasterizer for Clip Shape 2192 boolean skipClip = true; 2193 byte[] clipAlpha = null; 2194 2195 if (clipState == CLIP_SHAPE) { 2196 2197 int box[] = new int[4]; 2198 2199 clipRegion.getBounds(box); 2200 Rectangle devR = new Rectangle(box[0], box[1], 2201 box[2] - box[0], 2202 box[3] - box[1]); 2203 if (!devR.isEmpty()) { 2204 OutputManager mgr = getOutputManager(); 2205 RegionIterator ri = clipRegion.getIterator(); 2206 while (ri.nextYRange(box)) { 2207 int spany = box[1]; 2208 int spanh = box[3] - spany; 2209 while (ri.nextXBand(box)) { 2210 int spanx = box[0]; 2211 int spanw = box[2] - spanx; 2212 mgr.copyArea(this, null, 2213 spanw, 0, 2214 spanx, spany, 2215 spanw, spanh, 2216 fdx, fdy, 2217 null); 2218 } 2219 } 2220 } 2221 return; 2222 } 2223 // End Rasterizer for Clip Shape 2224 2225 getOutputManager().copyArea(this, null, 2226 r.width, 0, 2227 r.x, r.y, r.width, 2228 r.height, fdx, fdy, 2229 null); 2230 } 2231 */ 2232 2233 public void drawLine(int x1, int y1, int x2, int y2) { 2234 try { 2235 drawpipe.drawLine(this, x1, y1, x2, y2); 2236 } catch (InvalidPipeException e) { 2237 try { 2238 revalidateAll(); 2239 drawpipe.drawLine(this, x1, y1, x2, y2); 2240 } catch (InvalidPipeException e2) { 2241 // Still catching the exception; we are not yet ready to 2242 // validate the surfaceData correctly. Fail for now and 2243 // try again next time around. 2244 } 2245 } finally { 2246 surfaceData.markDirty(); 2247 } 2248 } 2249 2250 public void drawRoundRect(int x, int y, int w, int h, int arcW, int arcH) { 2251 try { 2252 drawpipe.drawRoundRect(this, x, y, w, h, arcW, arcH); 2253 } catch (InvalidPipeException e) { 2254 try { 2255 revalidateAll(); 2256 drawpipe.drawRoundRect(this, x, y, w, h, arcW, arcH); 2257 } catch (InvalidPipeException e2) { 2258 // Still catching the exception; we are not yet ready to 2259 // validate the surfaceData correctly. Fail for now and 2260 // try again next time around. 2261 } 2262 } finally { 2263 surfaceData.markDirty(); 2264 } 2265 } 2266 2267 public void fillRoundRect(int x, int y, int w, int h, int arcW, int arcH) { 2268 try { 2269 fillpipe.fillRoundRect(this, x, y, w, h, arcW, arcH); 2270 } catch (InvalidPipeException e) { 2271 try { 2272 revalidateAll(); 2273 fillpipe.fillRoundRect(this, x, y, w, h, arcW, arcH); 2274 } catch (InvalidPipeException e2) { 2275 // Still catching the exception; we are not yet ready to 2276 // validate the surfaceData correctly. Fail for now and 2277 // try again next time around. 2278 } 2279 } finally { 2280 surfaceData.markDirty(); 2281 } 2282 } 2283 2284 public void drawOval(int x, int y, int w, int h) { 2285 try { 2286 drawpipe.drawOval(this, x, y, w, h); 2287 } catch (InvalidPipeException e) { 2288 try { 2289 revalidateAll(); 2290 drawpipe.drawOval(this, x, y, w, h); 2291 } catch (InvalidPipeException e2) { 2292 // Still catching the exception; we are not yet ready to 2293 // validate the surfaceData correctly. Fail for now and 2294 // try again next time around. 2295 } 2296 } finally { 2297 surfaceData.markDirty(); 2298 } 2299 } 2300 2301 public void fillOval(int x, int y, int w, int h) { 2302 try { 2303 fillpipe.fillOval(this, x, y, w, h); 2304 } catch (InvalidPipeException e) { 2305 try { 2306 revalidateAll(); 2307 fillpipe.fillOval(this, x, y, w, h); 2308 } catch (InvalidPipeException e2) { 2309 // Still catching the exception; we are not yet ready to 2310 // validate the surfaceData correctly. Fail for now and 2311 // try again next time around. 2312 } 2313 } finally { 2314 surfaceData.markDirty(); 2315 } 2316 } 2317 2318 public void drawArc(int x, int y, int w, int h, 2319 int startAngl, int arcAngl) { 2320 try { 2321 drawpipe.drawArc(this, x, y, w, h, startAngl, arcAngl); 2322 } catch (InvalidPipeException e) { 2323 try { 2324 revalidateAll(); 2325 drawpipe.drawArc(this, x, y, w, h, startAngl, arcAngl); 2326 } catch (InvalidPipeException e2) { 2327 // Still catching the exception; we are not yet ready to 2328 // validate the surfaceData correctly. Fail for now and 2329 // try again next time around. 2330 } 2331 } finally { 2332 surfaceData.markDirty(); 2333 } 2334 } 2335 2336 public void fillArc(int x, int y, int w, int h, 2337 int startAngl, int arcAngl) { 2338 try { 2339 fillpipe.fillArc(this, x, y, w, h, startAngl, arcAngl); 2340 } catch (InvalidPipeException e) { 2341 try { 2342 revalidateAll(); 2343 fillpipe.fillArc(this, x, y, w, h, startAngl, arcAngl); 2344 } catch (InvalidPipeException e2) { 2345 // Still catching the exception; we are not yet ready to 2346 // validate the surfaceData correctly. Fail for now and 2347 // try again next time around. 2348 } 2349 } finally { 2350 surfaceData.markDirty(); 2351 } 2352 } 2353 2354 public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) { 2355 try { 2356 drawpipe.drawPolyline(this, xPoints, yPoints, nPoints); 2357 } catch (InvalidPipeException e) { 2358 try { 2359 revalidateAll(); 2360 drawpipe.drawPolyline(this, xPoints, yPoints, nPoints); 2361 } catch (InvalidPipeException e2) { 2362 // Still catching the exception; we are not yet ready to 2363 // validate the surfaceData correctly. Fail for now and 2364 // try again next time around. 2365 } 2366 } finally { 2367 surfaceData.markDirty(); 2368 } 2369 } 2370 2371 public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) { 2372 try { 2373 drawpipe.drawPolygon(this, xPoints, yPoints, nPoints); 2374 } catch (InvalidPipeException e) { 2375 try { 2376 revalidateAll(); 2377 drawpipe.drawPolygon(this, xPoints, yPoints, nPoints); 2378 } catch (InvalidPipeException e2) { 2379 // Still catching the exception; we are not yet ready to 2380 // validate the surfaceData correctly. Fail for now and 2381 // try again next time around. 2382 } 2383 } finally { 2384 surfaceData.markDirty(); 2385 } 2386 } 2387 2388 public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) { 2389 try { 2390 fillpipe.fillPolygon(this, xPoints, yPoints, nPoints); 2391 } catch (InvalidPipeException e) { 2392 try { 2393 revalidateAll(); 2394 fillpipe.fillPolygon(this, xPoints, yPoints, nPoints); 2395 } catch (InvalidPipeException e2) { 2396 // Still catching the exception; we are not yet ready to 2397 // validate the surfaceData correctly. Fail for now and 2398 // try again next time around. 2399 } 2400 } finally { 2401 surfaceData.markDirty(); 2402 } 2403 } 2404 2405 public void drawRect (int x, int y, int w, int h) { 2406 try { 2407 drawpipe.drawRect(this, x, y, w, h); 2408 } catch (InvalidPipeException e) { 2409 try { 2410 revalidateAll(); 2411 drawpipe.drawRect(this, x, y, w, h); 2412 } catch (InvalidPipeException e2) { 2413 // Still catching the exception; we are not yet ready to 2414 // validate the surfaceData correctly. Fail for now and 2415 // try again next time around. 2416 } 2417 } finally { 2418 surfaceData.markDirty(); 2419 } 2420 } 2421 2422 public void fillRect (int x, int y, int w, int h) { 2423 try { 2424 fillpipe.fillRect(this, x, y, w, h); 2425 } catch (InvalidPipeException e) { 2426 try { 2427 revalidateAll(); 2428 fillpipe.fillRect(this, x, y, w, h); 2429 } catch (InvalidPipeException e2) { 2430 // Still catching the exception; we are not yet ready to 2431 // validate the surfaceData correctly. Fail for now and 2432 // try again next time around. 2433 } 2434 } finally { 2435 surfaceData.markDirty(); 2436 } 2437 } 2438 2439 private void revalidateAll() { 2440 try { 2441 // REMIND: This locking needs to be done around the 2442 // caller of this method so that the pipe stays valid 2443 // long enough to call the new primitive. 2444 // REMIND: No locking yet in screen SurfaceData objects! 2445 // surfaceData.lock(); 2446 surfaceData = surfaceData.getReplacement(); 2447 if (surfaceData == null) { 2448 surfaceData = NullSurfaceData.theInstance; 2449 } 2450 2451 invalidatePipe(); 2452 2453 // this will recalculate the composite clip 2454 setDevClip(surfaceData.getBounds()); 2455 2456 if (paintState <= PAINT_ALPHACOLOR) { 2457 validateColor(); 2458 } 2459 if (composite instanceof XORComposite) { 2460 Color c = ((XORComposite) composite).getXorColor(); 2461 setComposite(new XORComposite(c, surfaceData)); 2462 } 2463 validatePipe(); 2464 } finally { 2465 // REMIND: No locking yet in screen SurfaceData objects! 2466 // surfaceData.unlock(); 2467 } 2468 } 2469 2470 public void clearRect(int x, int y, int w, int h) { 2471 // REMIND: has some "interesting" consequences if threads are 2472 // not synchronized 2473 Composite c = composite; 2474 Paint p = paint; 2475 setComposite(AlphaComposite.Src); 2476 setColor(getBackground()); 2477 fillRect(x, y, w, h); 2478 setPaint(p); 2479 setComposite(c); 2480 } 2481 2482 /** 2483 * Strokes the outline of a Path using the settings of the current 2484 * graphics state. The rendering attributes applied include the 2485 * clip, transform, paint or color, composite and stroke attributes. 2486 * @param s The path to be drawn. 2487 * @see #setStroke 2488 * @see #setPaint 2489 * @see java.awt.Graphics#setColor 2490 * @see #transform 2491 * @see #setTransform 2492 * @see #clip 2493 * @see #setClip 2494 * @see #setComposite 2495 */ 2496 public void draw(Shape s) { 2497 try { 2498 shapepipe.draw(this, s); 2499 } catch (InvalidPipeException e) { 2500 try { 2501 revalidateAll(); 2502 shapepipe.draw(this, s); 2503 } catch (InvalidPipeException e2) { 2504 // Still catching the exception; we are not yet ready to 2505 // validate the surfaceData correctly. Fail for now and 2506 // try again next time around. 2507 } 2508 } finally { 2509 surfaceData.markDirty(); 2510 } 2511 } 2512 2513 2514 /** 2515 * Fills the interior of a Path using the settings of the current 2516 * graphics state. The rendering attributes applied include the 2517 * clip, transform, paint or color, and composite. 2518 * @see #setPaint 2519 * @see java.awt.Graphics#setColor 2520 * @see #transform 2521 * @see #setTransform 2522 * @see #setComposite 2523 * @see #clip 2524 * @see #setClip 2525 */ 2526 public void fill(Shape s) { 2527 try { 2528 shapepipe.fill(this, s); 2529 } catch (InvalidPipeException e) { 2530 try { 2531 revalidateAll(); 2532 shapepipe.fill(this, s); 2533 } catch (InvalidPipeException e2) { 2534 // Still catching the exception; we are not yet ready to 2535 // validate the surfaceData correctly. Fail for now and 2536 // try again next time around. 2537 } 2538 } finally { 2539 surfaceData.markDirty(); 2540 } 2541 } 2542 2543 /** 2544 * Returns true if the given AffineTransform is an integer 2545 * translation. 2546 */ 2547 private static boolean isIntegerTranslation(AffineTransform xform) { 2548 if (xform.isIdentity()) { 2549 return true; 2550 } 2551 if (xform.getType() == AffineTransform.TYPE_TRANSLATION) { 2552 double tx = xform.getTranslateX(); 2553 double ty = xform.getTranslateY(); 2554 return (tx == (int)tx && ty == (int)ty); 2555 } 2556 return false; 2557 } 2558 2559 /** 2560 * Returns the index of the tile corresponding to the supplied position 2561 * given the tile grid offset and size along the same axis. 2562 */ 2563 private static int getTileIndex(int p, int tileGridOffset, int tileSize) { 2564 p -= tileGridOffset; 2565 if (p < 0) { 2566 p += 1 - tileSize; // force round to -infinity (ceiling) 2567 } 2568 return p/tileSize; 2569 } 2570 2571 /** 2572 * Returns a rectangle in image coordinates that may be required 2573 * in order to draw the given image into the given clipping region 2574 * through a pair of AffineTransforms. In addition, horizontal and 2575 * vertical padding factors for antialising and interpolation may 2576 * be used. 2577 */ 2578 private static Rectangle getImageRegion(RenderedImage img, 2579 Region compClip, 2580 AffineTransform transform, 2581 AffineTransform xform, 2582 int padX, int padY) { 2583 Rectangle imageRect = 2584 new Rectangle(img.getMinX(), img.getMinY(), 2585 img.getWidth(), img.getHeight()); 2586 2587 Rectangle result = null; 2588 try { 2589 double[] p = new double[8]; 2590 p[0] = p[2] = compClip.getLoX(); 2591 p[4] = p[6] = compClip.getHiX(); 2592 p[1] = p[5] = compClip.getLoY(); 2593 p[3] = p[7] = compClip.getHiY(); 2594 2595 // Inverse transform the output bounding rect 2596 transform.inverseTransform(p, 0, p, 0, 4); 2597 xform.inverseTransform(p, 0, p, 0, 4); 2598 2599 // Determine a bounding box for the inverse transformed region 2600 double x0,x1,y0,y1; 2601 x0 = x1 = p[0]; 2602 y0 = y1 = p[1]; 2603 2604 for (int i = 2; i < 8; ) { 2605 double pt = p[i++]; 2606 if (pt < x0) { 2607 x0 = pt; 2608 } else if (pt > x1) { 2609 x1 = pt; 2610 } 2611 pt = p[i++]; 2612 if (pt < y0) { 2613 y0 = pt; 2614 } else if (pt > y1) { 2615 y1 = pt; 2616 } 2617 } 2618 2619 // This is padding for anti-aliasing and such. It may 2620 // be more than is needed. 2621 int x = (int)x0 - padX; 2622 int w = (int)(x1 - x0 + 2*padX); 2623 int y = (int)y0 - padY; 2624 int h = (int)(y1 - y0 + 2*padY); 2625 2626 Rectangle clipRect = new Rectangle(x,y,w,h); 2627 result = clipRect.intersection(imageRect); 2628 } catch (NoninvertibleTransformException nte) { 2629 // Worst case bounds are the bounds of the image. 2630 result = imageRect; 2631 } 2632 2633 return result; 2634 } 2635 2636 /** 2637 * Draws an image, applying a transform from image space into user space 2638 * before drawing. 2639 * The transformation from user space into device space is done with 2640 * the current transform in the Graphics2D. 2641 * The given transformation is applied to the image before the 2642 * transform attribute in the Graphics2D state is applied. 2643 * The rendering attributes applied include the clip, transform, 2644 * and composite attributes. Note that the result is 2645 * undefined, if the given transform is noninvertible. 2646 * @param img The image to be drawn. Does nothing if img is null. 2647 * @param xform The transformation from image space into user space. 2648 * @see #transform 2649 * @see #setTransform 2650 * @see #setComposite 2651 * @see #clip 2652 * @see #setClip 2653 */ 2654 public void drawRenderedImage(RenderedImage img, 2655 AffineTransform xform) { 2656 2657 if (img == null) { 2658 return; 2659 } 2660 2661 // BufferedImage case: use a simple drawImage call 2662 if (img instanceof BufferedImage) { 2663 BufferedImage bufImg = (BufferedImage)img; 2664 drawImage(bufImg,xform,null); 2665 return; 2666 } 2667 2668 // transformState tracks the state of transform and 2669 // transX, transY contain the integer casts of the 2670 // translation factors 2671 boolean isIntegerTranslate = 2672 (transformState <= TRANSFORM_INT_TRANSLATE) && 2673 isIntegerTranslation(xform); 2674 2675 // Include padding for interpolation/antialiasing if necessary 2676 int pad = isIntegerTranslate ? 0 : 3; 2677 2678 Region clip; 2679 try { 2680 clip = getCompClip(); 2681 } catch (InvalidPipeException e) { 2682 return; 2683 } 2684 2685 // Determine the region of the image that may contribute to 2686 // the clipped drawing area 2687 Rectangle region = getImageRegion(img, 2688 clip, 2689 transform, 2690 xform, 2691 pad, pad); 2692 if (region.width <= 0 || region.height <= 0) { 2693 return; 2694 } 2695 2696 // Attempt to optimize integer translation of tiled images. 2697 // Although theoretically we are O.K. if the concatenation of 2698 // the user transform and the device transform is an integer 2699 // translation, we'll play it safe and only optimize the case 2700 // where both are integer translations. 2701 if (isIntegerTranslate) { 2702 // Use optimized code 2703 // Note that drawTranslatedRenderedImage calls copyImage 2704 // which takes the user space to device space transform into 2705 // account, but we need to provide the image space to user space 2706 // translations. 2707 2708 drawTranslatedRenderedImage(img, region, 2709 (int) xform.getTranslateX(), 2710 (int) xform.getTranslateY()); 2711 return; 2712 } 2713 2714 // General case: cobble the necessary region into a single Raster 2715 Raster raster = img.getData(region); 2716 2717 // Make a new Raster with the same contents as raster 2718 // but starting at (0, 0). This raster is thus in the same 2719 // coordinate system as the SampleModel of the original raster. 2720 WritableRaster wRaster = 2721 Raster.createWritableRaster(raster.getSampleModel(), 2722 raster.getDataBuffer(), 2723 null); 2724 2725 // If the original raster was in a different coordinate 2726 // system than its SampleModel, we need to perform an 2727 // additional translation in order to get the (minX, minY) 2728 // pixel of raster to be pixel (0, 0) of wRaster. We also 2729 // have to have the correct width and height. 2730 int minX = raster.getMinX(); 2731 int minY = raster.getMinY(); 2732 int width = raster.getWidth(); 2733 int height = raster.getHeight(); 2734 int px = minX - raster.getSampleModelTranslateX(); 2735 int py = minY - raster.getSampleModelTranslateY(); 2736 if (px != 0 || py != 0 || width != wRaster.getWidth() || 2737 height != wRaster.getHeight()) { 2738 wRaster = 2739 wRaster.createWritableChild(px, 2740 py, 2741 width, 2742 height, 2743 0, 0, 2744 null); 2745 } 2746 2747 // Now we have a BufferedImage starting at (0, 0) 2748 // with the same contents that started at (minX, minY) 2749 // in raster. So we must draw the BufferedImage with a 2750 // translation of (minX, minY). 2751 AffineTransform transXform = (AffineTransform)xform.clone(); 2752 transXform.translate(minX, minY); 2753 2754 ColorModel cm = img.getColorModel(); 2755 BufferedImage bufImg = new BufferedImage(cm, 2756 wRaster, 2757 cm.isAlphaPremultiplied(), 2758 null); 2759 drawImage(bufImg, transXform, null); 2760 } 2761 2762 /** 2763 * Intersects {@code destRect} with {@code clip} and 2764 * overwrites {@code destRect} with the result. 2765 * Returns false if the intersection was empty, true otherwise. 2766 */ 2767 private boolean clipTo(Rectangle destRect, Rectangle clip) { 2768 int x1 = Math.max(destRect.x, clip.x); 2769 int x2 = Math.min(destRect.x + destRect.width, clip.x + clip.width); 2770 int y1 = Math.max(destRect.y, clip.y); 2771 int y2 = Math.min(destRect.y + destRect.height, clip.y + clip.height); 2772 if (((x2 - x1) < 0) || ((y2 - y1) < 0)) { 2773 destRect.width = -1; // Set both just to be safe 2774 destRect.height = -1; 2775 return false; 2776 } else { 2777 destRect.x = x1; 2778 destRect.y = y1; 2779 destRect.width = x2 - x1; 2780 destRect.height = y2 - y1; 2781 return true; 2782 } 2783 } 2784 2785 /** 2786 * Draw a portion of a RenderedImage tile-by-tile with a given 2787 * integer image to user space translation. The user to 2788 * device transform must also be an integer translation. 2789 */ 2790 private void drawTranslatedRenderedImage(RenderedImage img, 2791 Rectangle region, 2792 int i2uTransX, 2793 int i2uTransY) { 2794 // Cache tile grid info 2795 int tileGridXOffset = img.getTileGridXOffset(); 2796 int tileGridYOffset = img.getTileGridYOffset(); 2797 int tileWidth = img.getTileWidth(); 2798 int tileHeight = img.getTileHeight(); 2799 2800 // Determine the tile index extrema in each direction 2801 int minTileX = 2802 getTileIndex(region.x, tileGridXOffset, tileWidth); 2803 int minTileY = 2804 getTileIndex(region.y, tileGridYOffset, tileHeight); 2805 int maxTileX = 2806 getTileIndex(region.x + region.width - 1, 2807 tileGridXOffset, tileWidth); 2808 int maxTileY = 2809 getTileIndex(region.y + region.height - 1, 2810 tileGridYOffset, tileHeight); 2811 2812 // Create a single ColorModel to use for all BufferedImages 2813 ColorModel colorModel = img.getColorModel(); 2814 2815 // Reuse the same Rectangle for each iteration 2816 Rectangle tileRect = new Rectangle(); 2817 2818 for (int ty = minTileY; ty <= maxTileY; ty++) { 2819 for (int tx = minTileX; tx <= maxTileX; tx++) { 2820 // Get the current tile. 2821 Raster raster = img.getTile(tx, ty); 2822 2823 // Fill in tileRect with the tile bounds 2824 tileRect.x = tx*tileWidth + tileGridXOffset; 2825 tileRect.y = ty*tileHeight + tileGridYOffset; 2826 tileRect.width = tileWidth; 2827 tileRect.height = tileHeight; 2828 2829 // Clip the tile against the image bounds and 2830 // backwards mapped clip region 2831 // The result can't be empty 2832 clipTo(tileRect, region); 2833 2834 // Create a WritableRaster containing the tile 2835 WritableRaster wRaster = null; 2836 if (raster instanceof WritableRaster) { 2837 wRaster = (WritableRaster)raster; 2838 } else { 2839 // Create a WritableRaster in the same coordinate system 2840 // as the original raster. 2841 wRaster = 2842 Raster.createWritableRaster(raster.getSampleModel(), 2843 raster.getDataBuffer(), 2844 null); 2845 } 2846 2847 // Translate wRaster to start at (0, 0) and to contain 2848 // only the relevent portion of the tile 2849 wRaster = wRaster.createWritableChild(tileRect.x, tileRect.y, 2850 tileRect.width, 2851 tileRect.height, 2852 0, 0, 2853 null); 2854 2855 // Wrap wRaster in a BufferedImage 2856 BufferedImage bufImg = 2857 new BufferedImage(colorModel, 2858 wRaster, 2859 colorModel.isAlphaPremultiplied(), 2860 null); 2861 // Now we have a BufferedImage starting at (0, 0) that 2862 // represents data from a Raster starting at 2863 // (tileRect.x, tileRect.y). Additionally, it needs 2864 // to be translated by (i2uTransX, i2uTransY). We call 2865 // copyImage to draw just the region of interest 2866 // without needing to create a child image. 2867 copyImage(bufImg, tileRect.x + i2uTransX, 2868 tileRect.y + i2uTransY, 0, 0, tileRect.width, 2869 tileRect.height, null, null); 2870 } 2871 } 2872 } 2873 2874 public void drawRenderableImage(RenderableImage img, 2875 AffineTransform xform) { 2876 2877 if (img == null) { 2878 return; 2879 } 2880 2881 AffineTransform pipeTransform = transform; 2882 AffineTransform concatTransform = new AffineTransform(xform); 2883 concatTransform.concatenate(pipeTransform); 2884 AffineTransform reverseTransform; 2885 2886 RenderContext rc = new RenderContext(concatTransform); 2887 2888 try { 2889 reverseTransform = pipeTransform.createInverse(); 2890 } catch (NoninvertibleTransformException nte) { 2891 rc = new RenderContext(pipeTransform); 2892 reverseTransform = new AffineTransform(); 2893 } 2894 2895 RenderedImage rendering = img.createRendering(rc); 2896 drawRenderedImage(rendering,reverseTransform); 2897 } 2898 2899 2900 2901 /* 2902 * Transform the bounding box of the BufferedImage 2903 */ 2904 protected Rectangle transformBounds(Rectangle rect, 2905 AffineTransform tx) { 2906 if (tx.isIdentity()) { 2907 return rect; 2908 } 2909 2910 Shape s = transformShape(tx, rect); 2911 return s.getBounds(); 2912 } 2913 2914 // text rendering methods 2915 public void drawString(String str, int x, int y) { 2916 if (str == null) { 2917 throw new NullPointerException("String is null"); 2918 } 2919 2920 if (font.hasLayoutAttributes()) { 2921 if (str.length() == 0) { 2922 return; 2923 } 2924 new TextLayout(str, font, getFontRenderContext()).draw(this, x, y); 2925 return; 2926 } 2927 2928 try { 2929 textpipe.drawString(this, str, x, y); 2930 } catch (InvalidPipeException e) { 2931 try { 2932 revalidateAll(); 2933 textpipe.drawString(this, str, x, y); 2934 } catch (InvalidPipeException e2) { 2935 // Still catching the exception; we are not yet ready to 2936 // validate the surfaceData correctly. Fail for now and 2937 // try again next time around. 2938 } 2939 } finally { 2940 surfaceData.markDirty(); 2941 } 2942 } 2943 2944 public void drawString(String str, float x, float y) { 2945 if (str == null) { 2946 throw new NullPointerException("String is null"); 2947 } 2948 2949 if (font.hasLayoutAttributes()) { 2950 if (str.length() == 0) { 2951 return; 2952 } 2953 new TextLayout(str, font, getFontRenderContext()).draw(this, x, y); 2954 return; 2955 } 2956 2957 try { 2958 textpipe.drawString(this, str, x, y); 2959 } catch (InvalidPipeException e) { 2960 try { 2961 revalidateAll(); 2962 textpipe.drawString(this, str, x, y); 2963 } catch (InvalidPipeException e2) { 2964 // Still catching the exception; we are not yet ready to 2965 // validate the surfaceData correctly. Fail for now and 2966 // try again next time around. 2967 } 2968 } finally { 2969 surfaceData.markDirty(); 2970 } 2971 } 2972 2973 public void drawString(AttributedCharacterIterator iterator, 2974 int x, int y) { 2975 if (iterator == null) { 2976 throw new NullPointerException("AttributedCharacterIterator is null"); 2977 } 2978 if (iterator.getBeginIndex() == iterator.getEndIndex()) { 2979 return; /* nothing to draw */ 2980 } 2981 TextLayout tl = new TextLayout(iterator, getFontRenderContext()); 2982 tl.draw(this, (float) x, (float) y); 2983 } 2984 2985 public void drawString(AttributedCharacterIterator iterator, 2986 float x, float y) { 2987 if (iterator == null) { 2988 throw new NullPointerException("AttributedCharacterIterator is null"); 2989 } 2990 if (iterator.getBeginIndex() == iterator.getEndIndex()) { 2991 return; /* nothing to draw */ 2992 } 2993 TextLayout tl = new TextLayout(iterator, getFontRenderContext()); 2994 tl.draw(this, x, y); 2995 } 2996 2997 public void drawGlyphVector(GlyphVector gv, float x, float y) 2998 { 2999 if (gv == null) { 3000 throw new NullPointerException("GlyphVector is null"); 3001 } 3002 3003 try { 3004 textpipe.drawGlyphVector(this, gv, x, y); 3005 } catch (InvalidPipeException e) { 3006 try { 3007 revalidateAll(); 3008 textpipe.drawGlyphVector(this, gv, x, y); 3009 } catch (InvalidPipeException e2) { 3010 // Still catching the exception; we are not yet ready to 3011 // validate the surfaceData correctly. Fail for now and 3012 // try again next time around. 3013 } 3014 } finally { 3015 surfaceData.markDirty(); 3016 } 3017 } 3018 3019 public void drawChars(char[] data, int offset, int length, int x, int y) { 3020 3021 if (data == null) { 3022 throw new NullPointerException("char data is null"); 3023 } 3024 if (offset < 0 || length < 0 || offset + length > data.length) { 3025 throw new ArrayIndexOutOfBoundsException("bad offset/length"); 3026 } 3027 if (font.hasLayoutAttributes()) { 3028 if (data.length == 0) { 3029 return; 3030 } 3031 new TextLayout(new String(data, offset, length), 3032 font, getFontRenderContext()).draw(this, x, y); 3033 return; 3034 } 3035 3036 try { 3037 textpipe.drawChars(this, data, offset, length, x, y); 3038 } catch (InvalidPipeException e) { 3039 try { 3040 revalidateAll(); 3041 textpipe.drawChars(this, data, offset, length, x, y); 3042 } catch (InvalidPipeException e2) { 3043 // Still catching the exception; we are not yet ready to 3044 // validate the surfaceData correctly. Fail for now and 3045 // try again next time around. 3046 } 3047 } finally { 3048 surfaceData.markDirty(); 3049 } 3050 } 3051 3052 public void drawBytes(byte[] data, int offset, int length, int x, int y) { 3053 if (data == null) { 3054 throw new NullPointerException("byte data is null"); 3055 } 3056 if (offset < 0 || length < 0 || offset + length > data.length) { 3057 throw new ArrayIndexOutOfBoundsException("bad offset/length"); 3058 } 3059 /* Byte data is interpreted as 8-bit ASCII. Re-use drawChars loops */ 3060 char[] chData = new char[length]; 3061 for (int i = length; i-- > 0; ) { 3062 chData[i] = (char)(data[i+offset] & 0xff); 3063 } 3064 if (font.hasLayoutAttributes()) { 3065 if (data.length == 0) { 3066 return; 3067 } 3068 new TextLayout(new String(chData), 3069 font, getFontRenderContext()).draw(this, x, y); 3070 return; 3071 } 3072 3073 try { 3074 textpipe.drawChars(this, chData, 0, length, x, y); 3075 } catch (InvalidPipeException e) { 3076 try { 3077 revalidateAll(); 3078 textpipe.drawChars(this, chData, 0, length, x, y); 3079 } catch (InvalidPipeException e2) { 3080 // Still catching the exception; we are not yet ready to 3081 // validate the surfaceData correctly. Fail for now and 3082 // try again next time around. 3083 } 3084 } finally { 3085 surfaceData.markDirty(); 3086 } 3087 } 3088 // end of text rendering methods 3089 3090 private Boolean drawHiDPIImage(Image img, 3091 int dx1, int dy1, int dx2, int dy2, 3092 int sx1, int sy1, int sx2, int sy2, 3093 Color bgcolor, ImageObserver observer, 3094 AffineTransform xform) { 3095 3096 if (img instanceof VolatileImage) { 3097 final SurfaceData sd = SurfaceManager.getManager(img) 3098 .getPrimarySurfaceData(); 3099 final double scaleX = sd.getDefaultScaleX(); 3100 final double scaleY = sd.getDefaultScaleY(); 3101 if (scaleX == 1 && scaleY == 1) { 3102 return null; 3103 } 3104 sx1 = Region.clipRound(sx1 * scaleX); 3105 sx2 = Region.clipRound(sx2 * scaleX); 3106 sy1 = Region.clipRound(sy1 * scaleY); 3107 sy2 = Region.clipRound(sy2 * scaleY); 3108 3109 AffineTransform tx = null; 3110 if (xform != null) { 3111 tx = new AffineTransform(transform); 3112 transform(xform); 3113 } 3114 boolean result = scaleImage(img, dx1, dy1, dx2, dy2, 3115 sx1, sy1, sx2, sy2, 3116 bgcolor, observer); 3117 if (tx != null) { 3118 transform.setTransform(tx); 3119 invalidateTransform(); 3120 } 3121 return result; 3122 } else if (img instanceof MultiResolutionImage) { 3123 // get scaled destination image size 3124 3125 int width = img.getWidth(observer); 3126 int height = img.getHeight(observer); 3127 3128 MultiResolutionImage mrImage = (MultiResolutionImage) img; 3129 Image resolutionVariant = getResolutionVariant(mrImage, width, height, 3130 dx1, dy1, dx2, dy2, 3131 sx1, sy1, sx2, sy2, 3132 xform); 3133 3134 if (resolutionVariant != img && resolutionVariant != null) { 3135 // recalculate source region for the resolution variant 3136 3137 ImageObserver rvObserver = MultiResolutionToolkitImage. 3138 getResolutionVariantObserver(img, observer, 3139 width, height, -1, -1); 3140 3141 int rvWidth = resolutionVariant.getWidth(rvObserver); 3142 int rvHeight = resolutionVariant.getHeight(rvObserver); 3143 3144 if (0 < width && 0 < height && 0 < rvWidth && 0 < rvHeight) { 3145 3146 double widthScale = ((double) rvWidth) / width; 3147 double heightScale = ((double) rvHeight) / height; 3148 3149 if (resolutionVariant instanceof VolatileImage) { 3150 SurfaceData sd = SurfaceManager 3151 .getManager(resolutionVariant) 3152 .getPrimarySurfaceData(); 3153 widthScale *= sd.getDefaultScaleX(); 3154 heightScale *= sd.getDefaultScaleY(); 3155 } 3156 3157 sx1 = Region.clipScale(sx1, widthScale); 3158 sy1 = Region.clipScale(sy1, heightScale); 3159 sx2 = Region.clipScale(sx2, widthScale); 3160 sy2 = Region.clipScale(sy2, heightScale); 3161 3162 observer = rvObserver; 3163 img = resolutionVariant; 3164 3165 if (xform != null) { 3166 assert dx1 == 0 && dy1 == 0; 3167 assert dx2 == img.getWidth(observer); 3168 assert dy2 == img.getHeight(observer); 3169 AffineTransform renderTX = new AffineTransform(xform); 3170 renderTX.scale(1 / widthScale, 1 / heightScale); 3171 return transformImage(img, renderTX, observer); 3172 } 3173 3174 return scaleImage(img, dx1, dy1, dx2, dy2, 3175 sx1, sy1, sx2, sy2, 3176 bgcolor, observer); 3177 } 3178 } 3179 } 3180 return null; 3181 } 3182 3183 private boolean scaleImage(Image img, int dx1, int dy1, int dx2, int dy2, 3184 int sx1, int sy1, int sx2, int sy2, 3185 Color bgcolor, ImageObserver observer) 3186 { 3187 try { 3188 return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2, sx1, sy1, 3189 sx2, sy2, bgcolor, observer); 3190 } catch (InvalidPipeException e) { 3191 try { 3192 revalidateAll(); 3193 return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2, sx1, 3194 sy1, sx2, sy2, bgcolor, observer); 3195 } catch (InvalidPipeException e2) { 3196 // Still catching the exception; we are not yet ready to 3197 // validate the surfaceData correctly. Fail for now and 3198 // try again next time around. 3199 return false; 3200 } 3201 } finally { 3202 surfaceData.markDirty(); 3203 } 3204 } 3205 3206 private boolean transformImage(Image img, 3207 AffineTransform xform, 3208 ImageObserver observer) 3209 { 3210 try { 3211 return imagepipe.transformImage(this, img, xform, observer); 3212 } catch (InvalidPipeException e) { 3213 try { 3214 revalidateAll(); 3215 return imagepipe.transformImage(this, img, xform, observer); 3216 } catch (InvalidPipeException e2) { 3217 // Still catching the exception; we are not yet ready to 3218 // validate the surfaceData correctly. Fail for now and 3219 // try again next time around. 3220 return false; 3221 } 3222 } finally { 3223 surfaceData.markDirty(); 3224 } 3225 } 3226 3227 private Image getResolutionVariant(MultiResolutionImage img, 3228 int srcWidth, int srcHeight, int dx1, int dy1, int dx2, int dy2, 3229 int sx1, int sy1, int sx2, int sy2, AffineTransform xform) { 3230 3231 if (srcWidth <= 0 || srcHeight <= 0) { 3232 return null; 3233 } 3234 3235 int sw = sx2 - sx1; 3236 int sh = sy2 - sy1; 3237 3238 if (sw == 0 || sh == 0) { 3239 return null; 3240 } 3241 3242 AffineTransform tx; 3243 3244 if (xform == null) { 3245 tx = transform; 3246 } else { 3247 tx = new AffineTransform(transform); 3248 tx.concatenate(xform); 3249 } 3250 3251 int type = tx.getType(); 3252 int dw = dx2 - dx1; 3253 int dh = dy2 - dy1; 3254 3255 double destImageWidth; 3256 double destImageHeight; 3257 3258 if (resolutionVariantHint == SunHints.INTVAL_RESOLUTION_VARIANT_BASE) { 3259 destImageWidth = srcWidth; 3260 destImageHeight = srcHeight; 3261 } else if (resolutionVariantHint == SunHints.INTVAL_RESOLUTION_VARIANT_DPI_FIT) { 3262 AffineTransform configTransform = getDefaultTransform(); 3263 if (configTransform.isIdentity()) { 3264 destImageWidth = srcWidth; 3265 destImageHeight = srcHeight; 3266 } else { 3267 destImageWidth = srcWidth * configTransform.getScaleX(); 3268 destImageHeight = srcHeight * configTransform.getScaleY(); 3269 } 3270 } else { 3271 double destRegionWidth; 3272 double destRegionHeight; 3273 3274 if ((type & ~(TYPE_TRANSLATION | TYPE_FLIP)) == 0) { 3275 destRegionWidth = dw; 3276 destRegionHeight = dh; 3277 } else if ((type & ~(TYPE_TRANSLATION | TYPE_FLIP | TYPE_MASK_SCALE)) == 0) { 3278 destRegionWidth = dw * tx.getScaleX(); 3279 destRegionHeight = dh * tx.getScaleY(); 3280 } else { 3281 destRegionWidth = dw * Math.hypot( 3282 tx.getScaleX(), tx.getShearY()); 3283 destRegionHeight = dh * Math.hypot( 3284 tx.getShearX(), tx.getScaleY()); 3285 } 3286 destImageWidth = Math.abs(srcWidth * destRegionWidth / sw); 3287 destImageHeight = Math.abs(srcHeight * destRegionHeight / sh); 3288 } 3289 3290 Image resolutionVariant 3291 = img.getResolutionVariant(destImageWidth, destImageHeight); 3292 3293 if (resolutionVariant instanceof ToolkitImage 3294 && ((ToolkitImage) resolutionVariant).hasError()) { 3295 return null; 3296 } 3297 3298 return resolutionVariant; 3299 } 3300 3301 /** 3302 * Draws an image scaled to x,y,w,h in nonblocking mode with a 3303 * callback object. 3304 */ 3305 public boolean drawImage(Image img, int x, int y, int width, int height, 3306 ImageObserver observer) { 3307 return drawImage(img, x, y, width, height, null, observer); 3308 } 3309 3310 /** 3311 * Not part of the advertised API but a useful utility method 3312 * to call internally. This is for the case where we are 3313 * drawing to/from given coordinates using a given width/height, 3314 * but we guarantee that the surfaceData's width/height of the src and dest 3315 * areas are equal (no scale needed). Note that this method intentionally 3316 * ignore scale factor of the source image, and copy it as is. 3317 */ 3318 public boolean copyImage(Image img, int dx, int dy, int sx, int sy, 3319 int width, int height, Color bgcolor, 3320 ImageObserver observer) { 3321 try { 3322 return imagepipe.copyImage(this, img, dx, dy, sx, sy, 3323 width, height, bgcolor, observer); 3324 } catch (InvalidPipeException e) { 3325 try { 3326 revalidateAll(); 3327 return imagepipe.copyImage(this, img, dx, dy, sx, sy, 3328 width, height, bgcolor, observer); 3329 } catch (InvalidPipeException e2) { 3330 // Still catching the exception; we are not yet ready to 3331 // validate the surfaceData correctly. Fail for now and 3332 // try again next time around. 3333 return false; 3334 } 3335 } finally { 3336 surfaceData.markDirty(); 3337 } 3338 } 3339 3340 /** 3341 * Draws an image scaled to x,y,w,h in nonblocking mode with a 3342 * solid background color and a callback object. 3343 */ 3344 public boolean drawImage(Image img, int x, int y, int width, int height, 3345 Color bg, ImageObserver observer) { 3346 3347 if (img == null) { 3348 return true; 3349 } 3350 3351 if ((width == 0) || (height == 0)) { 3352 return true; 3353 } 3354 3355 final int imgW = img.getWidth(null); 3356 final int imgH = img.getHeight(null); 3357 Boolean hidpiImageDrawn = drawHiDPIImage(img, x, y, x + width, y + height, 3358 0, 0, imgW, imgH, bg, observer, 3359 null); 3360 if (hidpiImageDrawn != null) { 3361 return hidpiImageDrawn; 3362 } 3363 3364 if (width == imgW && height == imgH) { 3365 return copyImage(img, x, y, 0, 0, width, height, bg, observer); 3366 } 3367 3368 try { 3369 return imagepipe.scaleImage(this, img, x, y, width, height, 3370 bg, observer); 3371 } catch (InvalidPipeException e) { 3372 try { 3373 revalidateAll(); 3374 return imagepipe.scaleImage(this, img, x, y, width, height, 3375 bg, observer); 3376 } catch (InvalidPipeException e2) { 3377 // Still catching the exception; we are not yet ready to 3378 // validate the surfaceData correctly. Fail for now and 3379 // try again next time around. 3380 return false; 3381 } 3382 } finally { 3383 surfaceData.markDirty(); 3384 } 3385 } 3386 3387 /** 3388 * Draws an image at x,y in nonblocking mode. 3389 */ 3390 public boolean drawImage(Image img, int x, int y, ImageObserver observer) { 3391 return drawImage(img, x, y, null, observer); 3392 } 3393 3394 /** 3395 * Draws an image at x,y in nonblocking mode with a solid background 3396 * color and a callback object. 3397 */ 3398 public boolean drawImage(Image img, int x, int y, Color bg, 3399 ImageObserver observer) { 3400 3401 if (img == null) { 3402 return true; 3403 } 3404 3405 final int imgW = img.getWidth(null); 3406 final int imgH = img.getHeight(null); 3407 Boolean hidpiImageDrawn = drawHiDPIImage(img, x, y, x + imgW, y + imgH, 3408 0, 0, imgW, imgH, bg, observer, 3409 null); 3410 if (hidpiImageDrawn != null) { 3411 return hidpiImageDrawn; 3412 } 3413 3414 try { 3415 return imagepipe.copyImage(this, img, x, y, bg, observer); 3416 } catch (InvalidPipeException e) { 3417 try { 3418 revalidateAll(); 3419 return imagepipe.copyImage(this, img, x, y, bg, observer); 3420 } catch (InvalidPipeException e2) { 3421 // Still catching the exception; we are not yet ready to 3422 // validate the surfaceData correctly. Fail for now and 3423 // try again next time around. 3424 return false; 3425 } 3426 } finally { 3427 surfaceData.markDirty(); 3428 } 3429 } 3430 3431 /** 3432 * Draws a subrectangle of an image scaled to a destination rectangle 3433 * in nonblocking mode with a callback object. 3434 */ 3435 public boolean drawImage(Image img, 3436 int dx1, int dy1, int dx2, int dy2, 3437 int sx1, int sy1, int sx2, int sy2, 3438 ImageObserver observer) { 3439 return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null, 3440 observer); 3441 } 3442 3443 /** 3444 * Draws a subrectangle of an image scaled to a destination rectangle in 3445 * nonblocking mode with a solid background color and a callback object. 3446 */ 3447 public boolean drawImage(Image img, 3448 int dx1, int dy1, int dx2, int dy2, 3449 int sx1, int sy1, int sx2, int sy2, 3450 Color bgcolor, ImageObserver observer) { 3451 3452 if (img == null) { 3453 return true; 3454 } 3455 3456 if (dx1 == dx2 || dy1 == dy2 || 3457 sx1 == sx2 || sy1 == sy2) 3458 { 3459 return true; 3460 } 3461 3462 Boolean hidpiImageDrawn = drawHiDPIImage(img, dx1, dy1, dx2, dy2, 3463 sx1, sy1, sx2, sy2, 3464 bgcolor, observer, null); 3465 3466 if (hidpiImageDrawn != null) { 3467 return hidpiImageDrawn; 3468 } 3469 3470 if (((sx2 - sx1) == (dx2 - dx1)) && 3471 ((sy2 - sy1) == (dy2 - dy1))) 3472 { 3473 // Not a scale - forward it to a copy routine 3474 int srcX, srcY, dstX, dstY, width, height; 3475 if (sx2 > sx1) { 3476 width = sx2 - sx1; 3477 srcX = sx1; 3478 dstX = dx1; 3479 } else { 3480 width = sx1 - sx2; 3481 srcX = sx2; 3482 dstX = dx2; 3483 } 3484 if (sy2 > sy1) { 3485 height = sy2-sy1; 3486 srcY = sy1; 3487 dstY = dy1; 3488 } else { 3489 height = sy1-sy2; 3490 srcY = sy2; 3491 dstY = dy2; 3492 } 3493 return copyImage(img, dstX, dstY, srcX, srcY, 3494 width, height, bgcolor, observer); 3495 } 3496 3497 try { 3498 return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2, 3499 sx1, sy1, sx2, sy2, bgcolor, 3500 observer); 3501 } catch (InvalidPipeException e) { 3502 try { 3503 revalidateAll(); 3504 return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2, 3505 sx1, sy1, sx2, sy2, bgcolor, 3506 observer); 3507 } catch (InvalidPipeException e2) { 3508 // Still catching the exception; we are not yet ready to 3509 // validate the surfaceData correctly. Fail for now and 3510 // try again next time around. 3511 return false; 3512 } 3513 } finally { 3514 surfaceData.markDirty(); 3515 } 3516 } 3517 3518 /** 3519 * Draw an image, applying a transform from image space into user space 3520 * before drawing. 3521 * The transformation from user space into device space is done with 3522 * the current transform in the Graphics2D. 3523 * The given transformation is applied to the image before the 3524 * transform attribute in the Graphics2D state is applied. 3525 * The rendering attributes applied include the clip, transform, 3526 * paint or color and composite attributes. Note that the result is 3527 * undefined, if the given transform is non-invertible. 3528 * @param img The image to be drawn. 3529 * @param xform The transformation from image space into user space. 3530 * @param observer The image observer to be notified on the image producing 3531 * progress. 3532 * @see #transform 3533 * @see #setComposite 3534 * @see #setClip 3535 */ 3536 public boolean drawImage(Image img, 3537 AffineTransform xform, 3538 ImageObserver observer) { 3539 3540 if (img == null) { 3541 return true; 3542 } 3543 3544 if (xform == null || xform.isIdentity()) { 3545 return drawImage(img, 0, 0, null, observer); 3546 } 3547 3548 final int w = img.getWidth(null); 3549 final int h = img.getHeight(null); 3550 Boolean hidpiImageDrawn = drawHiDPIImage(img, 0, 0, w, h, 0, 0, w, h, 3551 null, observer, xform); 3552 3553 if (hidpiImageDrawn != null) { 3554 return hidpiImageDrawn; 3555 } 3556 3557 return transformImage(img, xform, observer); 3558 } 3559 3560 public void drawImage(BufferedImage bImg, 3561 BufferedImageOp op, 3562 int x, 3563 int y) { 3564 3565 if (bImg == null) { 3566 return; 3567 } 3568 3569 try { 3570 imagepipe.transformImage(this, bImg, op, x, y); 3571 } catch (InvalidPipeException e) { 3572 try { 3573 revalidateAll(); 3574 imagepipe.transformImage(this, bImg, op, x, y); 3575 } catch (InvalidPipeException e2) { 3576 // Still catching the exception; we are not yet ready to 3577 // validate the surfaceData correctly. Fail for now and 3578 // try again next time around. 3579 } 3580 } finally { 3581 surfaceData.markDirty(); 3582 } 3583 } 3584 3585 /** 3586 * Get the rendering context of the font 3587 * within this Graphics2D context. 3588 */ 3589 public FontRenderContext getFontRenderContext() { 3590 if (cachedFRC == null) { 3591 int aahint = textAntialiasHint; 3592 if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT && 3593 antialiasHint == SunHints.INTVAL_ANTIALIAS_ON) { 3594 aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON; 3595 } 3596 // Translation components should be excluded from the FRC transform 3597 AffineTransform tx = null; 3598 if (transformState >= TRANSFORM_TRANSLATESCALE) { 3599 if (transform.getTranslateX() == 0 && 3600 transform.getTranslateY() == 0) { 3601 tx = transform; 3602 } else { 3603 tx = new AffineTransform(transform.getScaleX(), 3604 transform.getShearY(), 3605 transform.getShearX(), 3606 transform.getScaleY(), 3607 0, 0); 3608 } 3609 } 3610 cachedFRC = new FontRenderContext(tx, 3611 SunHints.Value.get(SunHints.INTKEY_TEXT_ANTIALIASING, aahint), 3612 SunHints.Value.get(SunHints.INTKEY_FRACTIONALMETRICS, 3613 fractionalMetricsHint)); 3614 } 3615 return cachedFRC; 3616 } 3617 private FontRenderContext cachedFRC; 3618 3619 /** 3620 * This object has no resources to dispose of per se, but the 3621 * doc comments for the base method in java.awt.Graphics imply 3622 * that this object will not be useable after it is disposed. 3623 * So, we sabotage the object to prevent further use to prevent 3624 * developers from relying on behavior that may not work on 3625 * other, less forgiving, VMs that really need to dispose of 3626 * resources. 3627 */ 3628 public void dispose() { 3629 surfaceData = NullSurfaceData.theInstance; 3630 invalidatePipe(); 3631 } 3632 3633 /** 3634 * Graphics has a finalize method that automatically calls dispose() 3635 * for subclasses. For SunGraphics2D we do not need to be finalized 3636 * so that method simply causes us to be enqueued on the Finalizer 3637 * queues for no good reason. Unfortunately, that method and 3638 * implementation are now considered part of the public contract 3639 * of that base class so we can not remove or gut the method. 3640 * We override it here with an empty method and the VM is smart 3641 * enough to know that if our override is empty then it should not 3642 * mark us as finalizeable. 3643 */ 3644 @SuppressWarnings("deprecation") 3645 public void finalize() { 3646 // DO NOT REMOVE THIS METHOD 3647 } 3648 3649 /** 3650 * Returns destination that this Graphics renders to. This could be 3651 * either an Image or a Component; subclasses of SurfaceData are 3652 * responsible for returning the appropriate object. 3653 */ 3654 public Object getDestination() { 3655 return surfaceData.getDestination(); 3656 } 3657 3658 /** 3659 * {@inheritDoc} 3660 * 3661 * @see sun.java2d.DestSurfaceProvider#getDestSurface 3662 */ 3663 @Override 3664 public Surface getDestSurface() { 3665 return surfaceData; 3666 } 3667 }