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