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