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