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