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