|
1 /* |
|
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 |
|
26 package sun.java2d; |
|
27 |
|
28 import java.awt.Graphics; |
|
29 import java.awt.Graphics2D; |
|
30 import java.awt.RenderingHints; |
|
31 import java.awt.RenderingHints.Key; |
|
32 import java.awt.geom.Area; |
|
33 import java.awt.geom.AffineTransform; |
|
34 import java.awt.geom.NoninvertibleTransformException; |
|
35 import java.awt.AlphaComposite; |
|
36 import java.awt.BasicStroke; |
|
37 import java.awt.image.BufferedImage; |
|
38 import java.awt.image.BufferedImageOp; |
|
39 import java.awt.image.RenderedImage; |
|
40 import java.awt.image.renderable.RenderableImage; |
|
41 import java.awt.image.renderable.RenderContext; |
|
42 import java.awt.image.AffineTransformOp; |
|
43 import java.awt.image.Raster; |
|
44 import java.awt.image.SampleModel; |
|
45 import java.awt.image.VolatileImage; |
|
46 import java.awt.image.WritableRaster; |
|
47 import java.awt.Image; |
|
48 import java.awt.Composite; |
|
49 import java.awt.Color; |
|
50 import java.awt.color.ColorSpace; |
|
51 import java.awt.image.DataBuffer; |
|
52 import java.awt.image.ColorModel; |
|
53 import java.awt.image.IndexColorModel; |
|
54 import java.awt.image.DirectColorModel; |
|
55 import java.awt.GraphicsConfiguration; |
|
56 import java.awt.Paint; |
|
57 import java.awt.GradientPaint; |
|
58 import java.awt.LinearGradientPaint; |
|
59 import java.awt.RadialGradientPaint; |
|
60 import java.awt.TexturePaint; |
|
61 import java.awt.geom.Point2D; |
|
62 import java.awt.geom.Rectangle2D; |
|
63 import java.awt.geom.PathIterator; |
|
64 import java.awt.geom.GeneralPath; |
|
65 import java.awt.Shape; |
|
66 import java.awt.Stroke; |
|
67 import java.awt.FontMetrics; |
|
68 import java.awt.Rectangle; |
|
69 import java.text.AttributedCharacterIterator; |
|
70 import java.awt.Font; |
|
71 import java.awt.image.ImageObserver; |
|
72 import java.awt.image.ColorConvertOp; |
|
73 import java.awt.Transparency; |
|
74 import java.awt.font.GlyphVector; |
|
75 import java.awt.font.TextLayout; |
|
76 import sun.font.FontDesignMetrics; |
|
77 import sun.font.StandardGlyphVector; |
|
78 import sun.java2d.pipe.PixelDrawPipe; |
|
79 import sun.java2d.pipe.PixelFillPipe; |
|
80 import sun.java2d.pipe.ShapeDrawPipe; |
|
81 import sun.java2d.pipe.ValidatePipe; |
|
82 import sun.java2d.pipe.ShapeSpanIterator; |
|
83 import sun.java2d.pipe.Region; |
|
84 import sun.java2d.pipe.RegionIterator; |
|
85 import sun.java2d.pipe.TextPipe; |
|
86 import sun.java2d.pipe.DrawImagePipe; |
|
87 import sun.java2d.pipe.LoopPipe; |
|
88 import sun.java2d.loops.FontInfo; |
|
89 import sun.java2d.loops.RenderLoops; |
|
90 import sun.java2d.loops.CompositeType; |
|
91 import sun.java2d.loops.SurfaceType; |
|
92 import sun.java2d.loops.Blit; |
|
93 import sun.java2d.loops.BlitBg; |
|
94 import sun.java2d.loops.MaskFill; |
|
95 import sun.font.FontManager; |
|
96 import java.awt.font.FontRenderContext; |
|
97 import sun.java2d.loops.XORComposite; |
|
98 import sun.awt.ConstrainableGraphics; |
|
99 import sun.awt.SunHints; |
|
100 import java.util.Map; |
|
101 import java.util.Iterator; |
|
102 import sun.awt.image.OffScreenImage; |
|
103 import 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 */ |
|
114 public 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 } |