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