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