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