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