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