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