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