Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.desktop/share/classes/sun/java2d/marlin/DMarlinRenderingEngine.java
41159 views
1
/*
2
* Copyright (c) 2007, 2021, 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.marlin;
27
28
import java.awt.BasicStroke;
29
import java.awt.Shape;
30
import java.awt.geom.AffineTransform;
31
import java.awt.geom.Path2D;
32
import java.awt.geom.PathIterator;
33
import java.security.AccessController;
34
import java.util.Arrays;
35
import sun.awt.geom.PathConsumer2D;
36
import static sun.java2d.marlin.MarlinUtils.logInfo;
37
import sun.java2d.ReentrantContextProvider;
38
import sun.java2d.ReentrantContextProviderCLQ;
39
import sun.java2d.ReentrantContextProviderTL;
40
import sun.java2d.pipe.AATileGenerator;
41
import sun.java2d.pipe.Region;
42
import sun.java2d.pipe.RenderingEngine;
43
import sun.security.action.GetPropertyAction;
44
45
/**
46
* Marlin RendererEngine implementation (derived from Pisces)
47
*/
48
public final class DMarlinRenderingEngine extends RenderingEngine
49
implements MarlinConst
50
{
51
// slightly slower ~2% if enabled stroker clipping (lines) but skipping cap / join handling is few percents faster in specific cases
52
static final boolean DISABLE_2ND_STROKER_CLIPPING = true;
53
54
static final boolean DO_TRACE_PATH = false;
55
56
static final boolean DO_CLIP = MarlinProperties.isDoClip();
57
static final boolean DO_CLIP_FILL = true;
58
static final boolean DO_CLIP_RUNTIME_ENABLE = MarlinProperties.isDoClipRuntimeFlag();
59
60
private static final float MIN_PEN_SIZE = 1.0f / MIN_SUBPIXELS;
61
62
static final double UPPER_BND = Float.MAX_VALUE / 2.0d;
63
static final double LOWER_BND = -UPPER_BND;
64
65
private enum NormMode {
66
ON_WITH_AA {
67
@Override
68
PathIterator getNormalizingPathIterator(final RendererContext rdrCtx,
69
final PathIterator src)
70
{
71
// NormalizingPathIterator NearestPixelCenter:
72
return rdrCtx.nPCPathIterator.init(src);
73
}
74
},
75
ON_NO_AA{
76
@Override
77
PathIterator getNormalizingPathIterator(final RendererContext rdrCtx,
78
final PathIterator src)
79
{
80
// NearestPixel NormalizingPathIterator:
81
return rdrCtx.nPQPathIterator.init(src);
82
}
83
},
84
OFF{
85
@Override
86
PathIterator getNormalizingPathIterator(final RendererContext rdrCtx,
87
final PathIterator src)
88
{
89
// return original path iterator if normalization is disabled:
90
return src;
91
}
92
};
93
94
abstract PathIterator getNormalizingPathIterator(RendererContext rdrCtx,
95
PathIterator src);
96
}
97
98
/**
99
* Public constructor
100
*/
101
public DMarlinRenderingEngine() {
102
super();
103
logSettings(DMarlinRenderingEngine.class.getName());
104
}
105
106
/**
107
* Create a widened path as specified by the parameters.
108
* <p>
109
* The specified {@code src} {@link Shape} is widened according
110
* to the specified attribute parameters as per the
111
* {@link BasicStroke} specification.
112
*
113
* @param src the source path to be widened
114
* @param width the width of the widened path as per {@code BasicStroke}
115
* @param caps the end cap decorations as per {@code BasicStroke}
116
* @param join the segment join decorations as per {@code BasicStroke}
117
* @param miterlimit the miter limit as per {@code BasicStroke}
118
* @param dashes the dash length array as per {@code BasicStroke}
119
* @param dashphase the initial dash phase as per {@code BasicStroke}
120
* @return the widened path stored in a new {@code Shape} object
121
* @since 1.7
122
*/
123
@Override
124
public Shape createStrokedShape(Shape src,
125
float width,
126
int caps,
127
int join,
128
float miterlimit,
129
float[] dashes,
130
float dashphase)
131
{
132
final RendererContext rdrCtx = getRendererContext();
133
try {
134
// initialize a large copyable Path2D to avoid a lot of array growing:
135
final Path2D.Double p2d = rdrCtx.getPath2D();
136
137
strokeTo(rdrCtx,
138
src,
139
null,
140
width,
141
NormMode.OFF,
142
caps,
143
join,
144
miterlimit,
145
dashes,
146
dashphase,
147
rdrCtx.transformerPC2D.wrapPath2D(p2d)
148
);
149
150
// Use Path2D copy constructor (trim)
151
return new Path2D.Double(p2d);
152
153
} finally {
154
// recycle the RendererContext instance
155
returnRendererContext(rdrCtx);
156
}
157
}
158
159
/**
160
* Sends the geometry for a widened path as specified by the parameters
161
* to the specified consumer.
162
* <p>
163
* The specified {@code src} {@link Shape} is widened according
164
* to the parameters specified by the {@link BasicStroke} object.
165
* Adjustments are made to the path as appropriate for the
166
* {@link java.awt.RenderingHints#VALUE_STROKE_NORMALIZE} hint if the
167
* {@code normalize} boolean parameter is true.
168
* Adjustments are made to the path as appropriate for the
169
* {@link java.awt.RenderingHints#VALUE_ANTIALIAS_ON} hint if the
170
* {@code antialias} boolean parameter is true.
171
* <p>
172
* The geometry of the widened path is forwarded to the indicated
173
* {@link PathConsumer2D} object as it is calculated.
174
*
175
* @param src the source path to be widened
176
* @param at the transform to be applied to the shape and the
177
* stroke attributes
178
* @param bs the {@code BasicStroke} object specifying the
179
* decorations to be applied to the widened path
180
* @param thin true if the transformed stroke attributes are smaller
181
* than the minimum dropout pen width
182
* @param normalize indicates whether stroke normalization should
183
* be applied
184
* @param antialias indicates whether or not adjustments appropriate
185
* to antialiased rendering should be applied
186
* @param consumer the {@code PathConsumer2D} instance to forward
187
* the widened geometry to
188
* @since 1.7
189
*/
190
@Override
191
public void strokeTo(Shape src,
192
AffineTransform at,
193
BasicStroke bs,
194
boolean thin,
195
boolean normalize,
196
boolean antialias,
197
final PathConsumer2D consumer)
198
{
199
strokeTo(src, at, null, bs, thin, normalize, antialias, consumer);
200
}
201
202
/**
203
* Sends the geometry for a widened path as specified by the parameters
204
* to the specified consumer.
205
* <p>
206
* The specified {@code src} {@link Shape} is widened according
207
* to the parameters specified by the {@link BasicStroke} object.
208
* Adjustments are made to the path as appropriate for the
209
* {@link java.awt.RenderingHints#VALUE_STROKE_NORMALIZE} hint if the
210
* {@code normalize} boolean parameter is true.
211
* Adjustments are made to the path as appropriate for the
212
* {@link java.awt.RenderingHints#VALUE_ANTIALIAS_ON} hint if the
213
* {@code antialias} boolean parameter is true.
214
* <p>
215
* The geometry of the widened path is forwarded to the indicated
216
* {@link PathConsumer2D} object as it is calculated.
217
*
218
* @param src the source path to be widened
219
* @param at the transform to be applied to the shape and the
220
* stroke attributes
221
* @param clip the current clip in effect in device coordinates
222
* @param bs the {@code BasicStroke} object specifying the
223
* decorations to be applied to the widened path
224
* @param thin true if the transformed stroke attributes are smaller
225
* than the minimum dropout pen width
226
* @param normalize indicates whether stroke normalization should
227
* be applied
228
* @param antialias indicates whether or not adjustments appropriate
229
* to antialiased rendering should be applied
230
* @param consumer the {@code PathConsumer2D} instance to forward
231
* the widened geometry to
232
* @since 17
233
*/
234
/* @Override (only for 17+) */
235
public void strokeTo(Shape src,
236
AffineTransform at,
237
Region clip,
238
BasicStroke bs,
239
boolean thin,
240
boolean normalize,
241
boolean antialias,
242
final PathConsumer2D consumer)
243
{
244
// Test if at is identity:
245
final AffineTransform _at = (at != null && !at.isIdentity()) ? at
246
: null;
247
248
final NormMode norm = (normalize) ?
249
((antialias) ? NormMode.ON_WITH_AA : NormMode.ON_NO_AA)
250
: NormMode.OFF;
251
252
final RendererContext rdrCtx = getRendererContext();
253
try {
254
if ((clip != null) &&
255
(DO_CLIP || (DO_CLIP_RUNTIME_ENABLE && MarlinProperties.isDoClipAtRuntime()))) {
256
// Define the initial clip bounds:
257
final double[] clipRect = rdrCtx.clipRect;
258
259
// Adjust the clipping rectangle with the renderer offsets
260
final double rdrOffX = 0.25d; // LBO: is it correct for AA or non AA cases ?
261
final double rdrOffY = 0.25d; // see NearestPixelQuarter (depends on normalization ?)
262
263
// add a small rounding error:
264
final double margin = 1e-3d;
265
266
clipRect[0] = clip.getLoY()
267
- margin + rdrOffY;
268
clipRect[1] = clip.getLoY() + clip.getHeight()
269
+ margin + rdrOffY;
270
clipRect[2] = clip.getLoX()
271
- margin + rdrOffX;
272
clipRect[3] = clip.getLoX() + clip.getWidth()
273
+ margin + rdrOffX;
274
275
if (MarlinConst.DO_LOG_CLIP) {
276
MarlinUtils.logInfo("clipRect (clip): "
277
+ Arrays.toString(rdrCtx.clipRect));
278
}
279
280
// Enable clipping:
281
rdrCtx.doClip = true;
282
}
283
284
strokeTo(rdrCtx, src, _at, bs, thin, norm, antialias,
285
rdrCtx.p2dAdapter.init(consumer));
286
} finally {
287
// recycle the RendererContext instance
288
returnRendererContext(rdrCtx);
289
}
290
}
291
292
void strokeTo(final RendererContext rdrCtx,
293
Shape src,
294
AffineTransform at,
295
BasicStroke bs,
296
boolean thin,
297
NormMode normalize,
298
boolean antialias,
299
DPathConsumer2D pc2d)
300
{
301
double lw;
302
if (thin) {
303
if (antialias) {
304
lw = userSpaceLineWidth(at, MIN_PEN_SIZE);
305
} else {
306
lw = userSpaceLineWidth(at, 1.0d);
307
}
308
} else {
309
lw = bs.getLineWidth();
310
}
311
strokeTo(rdrCtx,
312
src,
313
at,
314
lw,
315
normalize,
316
bs.getEndCap(),
317
bs.getLineJoin(),
318
bs.getMiterLimit(),
319
bs.getDashArray(),
320
bs.getDashPhase(),
321
pc2d);
322
}
323
324
private double userSpaceLineWidth(AffineTransform at, double lw) {
325
326
double widthScale;
327
328
if (at == null) {
329
widthScale = 1.0d;
330
} else if ((at.getType() & (AffineTransform.TYPE_GENERAL_TRANSFORM |
331
AffineTransform.TYPE_GENERAL_SCALE)) != 0) {
332
// Determinant may be negative (flip), use its absolute value:
333
widthScale = Math.sqrt(Math.abs(at.getDeterminant()));
334
} else {
335
// First calculate the "maximum scale" of this transform.
336
double A = at.getScaleX(); // m00
337
double C = at.getShearX(); // m01
338
double B = at.getShearY(); // m10
339
double D = at.getScaleY(); // m11
340
341
/*
342
* Given a 2 x 2 affine matrix [ A B ] such that
343
* [ C D ]
344
* v' = [x' y'] = [Ax + Cy, Bx + Dy], we want to
345
* find the maximum magnitude (norm) of the vector v'
346
* with the constraint (x^2 + y^2 = 1).
347
* The equation to maximize is
348
* |v'| = sqrt((Ax+Cy)^2+(Bx+Dy)^2)
349
* or |v'| = sqrt((AA+BB)x^2 + 2(AC+BD)xy + (CC+DD)y^2).
350
* Since sqrt is monotonic we can maximize |v'|^2
351
* instead and plug in the substitution y = sqrt(1 - x^2).
352
* Trigonometric equalities can then be used to get
353
* rid of most of the sqrt terms.
354
*/
355
356
double EA = A*A + B*B; // x^2 coefficient
357
double EB = 2.0d * (A*C + B*D); // xy coefficient
358
double EC = C*C + D*D; // y^2 coefficient
359
360
/*
361
* There is a lot of calculus omitted here.
362
*
363
* Conceptually, in the interests of understanding the
364
* terms that the calculus produced we can consider
365
* that EA and EC end up providing the lengths along
366
* the major axes and the hypot term ends up being an
367
* adjustment for the additional length along the off-axis
368
* angle of rotated or sheared ellipses as well as an
369
* adjustment for the fact that the equation below
370
* averages the two major axis lengths. (Notice that
371
* the hypot term contains a part which resolves to the
372
* difference of these two axis lengths in the absence
373
* of rotation.)
374
*
375
* In the calculus, the ratio of the EB and (EA-EC) terms
376
* ends up being the tangent of 2*theta where theta is
377
* the angle that the long axis of the ellipse makes
378
* with the horizontal axis. Thus, this equation is
379
* calculating the length of the hypotenuse of a triangle
380
* along that axis.
381
*/
382
383
double hypot = Math.sqrt(EB*EB + (EA-EC)*(EA-EC));
384
// sqrt omitted, compare to squared limits below.
385
double widthsquared = ((EA + EC + hypot) / 2.0d);
386
387
widthScale = Math.sqrt(widthsquared);
388
}
389
390
return (lw / widthScale);
391
}
392
393
void strokeTo(final RendererContext rdrCtx,
394
Shape src,
395
AffineTransform at,
396
double width,
397
NormMode norm,
398
int caps,
399
int join,
400
float miterlimit,
401
float[] dashes,
402
float dashphase,
403
DPathConsumer2D pc2d)
404
{
405
// We use strokerat so that in Stroker and Dasher we can work only
406
// with the pre-transformation coordinates. This will repeat a lot of
407
// computations done in the path iterator, but the alternative is to
408
// work with transformed paths and compute untransformed coordinates
409
// as needed. This would be faster but I do not think the complexity
410
// of working with both untransformed and transformed coordinates in
411
// the same code is worth it.
412
// However, if a path's width is constant after a transformation,
413
// we can skip all this untransforming.
414
415
// As pathTo() will check transformed coordinates for invalid values
416
// (NaN / Infinity) to ignore such points, it is necessary to apply the
417
// transformation before the path processing.
418
AffineTransform strokerat = null;
419
420
int dashLen = -1;
421
boolean recycleDashes = false;
422
double[] dashesD = null;
423
424
// Ensure converting dashes to double precision:
425
if (dashes != null) {
426
recycleDashes = true;
427
dashLen = dashes.length;
428
dashesD = rdrCtx.dasher.copyDashArray(dashes);
429
}
430
431
if (at != null && !at.isIdentity()) {
432
final double a = at.getScaleX();
433
final double b = at.getShearX();
434
final double c = at.getShearY();
435
final double d = at.getScaleY();
436
final double det = a * d - c * b;
437
438
if (Math.abs(det) <= (2.0d * Double.MIN_VALUE)) {
439
// this rendering engine takes one dimensional curves and turns
440
// them into 2D shapes by giving them width.
441
// However, if everything is to be passed through a singular
442
// transformation, these 2D shapes will be squashed down to 1D
443
// again so, nothing can be drawn.
444
445
// Every path needs an initial moveTo and a pathDone. If these
446
// are not there this causes a SIGSEGV in libawt.so (at the time
447
// of writing of this comment (September 16, 2010)). Actually,
448
// I am not sure if the moveTo is necessary to avoid the SIGSEGV
449
// but the pathDone is definitely needed.
450
pc2d.moveTo(0.0d, 0.0d);
451
pc2d.pathDone();
452
return;
453
}
454
455
// If the transform is a constant multiple of an orthogonal transformation
456
// then every length is just multiplied by a constant, so we just
457
// need to transform input paths to stroker and tell stroker
458
// the scaled width. This condition is satisfied if
459
// a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we
460
// leave a bit of room for error.
461
if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) {
462
final double scale = Math.sqrt(a*a + c*c);
463
464
if (dashesD != null) {
465
for (int i = 0; i < dashLen; i++) {
466
dashesD[i] *= scale;
467
}
468
dashphase *= scale;
469
}
470
width *= scale;
471
472
// by now strokerat == null. Input paths to
473
// stroker (and maybe dasher) will have the full transform at
474
// applied to them and nothing will happen to the output paths.
475
} else {
476
strokerat = at;
477
478
// by now strokerat == at. Input paths to
479
// stroker (and maybe dasher) will have the full transform at
480
// applied to them, then they will be normalized, and then
481
// the inverse of *only the non translation part of at* will
482
// be applied to the normalized paths. This won't cause problems
483
// in stroker, because, suppose at = T*A, where T is just the
484
// translation part of at, and A is the rest. T*A has already
485
// been applied to Stroker/Dasher's input. Then Ainv will be
486
// applied. Ainv*T*A is not equal to T, but it is a translation,
487
// which means that none of stroker's assumptions about its
488
// input will be violated. After all this, A will be applied
489
// to stroker's output.
490
}
491
} else {
492
// either at is null or it's the identity. In either case
493
// we don't transform the path.
494
at = null;
495
}
496
497
final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;
498
499
if (DO_TRACE_PATH) {
500
// trace Stroker:
501
pc2d = transformerPC2D.traceStroker(pc2d);
502
}
503
504
if (USE_SIMPLIFIER) {
505
// Use simplifier after stroker before Renderer
506
// to remove collinear segments (notably due to cap square)
507
pc2d = rdrCtx.simplifier.init(pc2d);
508
}
509
510
// deltaTransformConsumer may adjust the clip rectangle:
511
pc2d = transformerPC2D.deltaTransformConsumer(pc2d, strokerat);
512
513
// stroker will adjust the clip rectangle (width / miter limit):
514
pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit,
515
(dashesD == null));
516
517
// Curve Monotizer:
518
rdrCtx.monotonizer.init(width);
519
520
if (dashesD != null) {
521
if (DO_TRACE_PATH) {
522
pc2d = transformerPC2D.traceDasher(pc2d);
523
}
524
pc2d = rdrCtx.dasher.init(pc2d, dashesD, dashLen, dashphase,
525
recycleDashes);
526
527
if (DISABLE_2ND_STROKER_CLIPPING) {
528
// disable stoker clipping:
529
rdrCtx.stroker.disableClipping();
530
}
531
532
} else if (rdrCtx.doClip && (caps != CAP_BUTT)) {
533
if (DO_TRACE_PATH) {
534
pc2d = transformerPC2D.traceClosedPathDetector(pc2d);
535
}
536
537
// If no dash and clip is enabled:
538
// detect closedPaths (polygons) for caps
539
pc2d = transformerPC2D.detectClosedPath(pc2d);
540
}
541
pc2d = transformerPC2D.inverseDeltaTransformConsumer(pc2d, strokerat);
542
543
if (DO_TRACE_PATH) {
544
// trace Input:
545
pc2d = transformerPC2D.traceInput(pc2d);
546
}
547
548
final PathIterator pi = norm.getNormalizingPathIterator(rdrCtx,
549
src.getPathIterator(at));
550
551
pathTo(rdrCtx, pi, pc2d);
552
553
/*
554
* Pipeline seems to be:
555
* shape.getPathIterator(at)
556
* -> (NormalizingPathIterator)
557
* -> (inverseDeltaTransformConsumer)
558
* -> (Dasher)
559
* -> Stroker
560
* -> (deltaTransformConsumer)
561
*
562
* -> (CollinearSimplifier) to remove redundant segments
563
*
564
* -> pc2d = Renderer (bounding box)
565
*/
566
}
567
568
private static boolean nearZero(final double num) {
569
return Math.abs(num) < 2.0d * Math.ulp(num);
570
}
571
572
abstract static class NormalizingPathIterator implements PathIterator {
573
574
private PathIterator src;
575
576
// the adjustment applied to the current position.
577
private double curx_adjust, cury_adjust;
578
// the adjustment applied to the last moveTo position.
579
private double movx_adjust, movy_adjust;
580
581
private final double[] tmp;
582
583
NormalizingPathIterator(final double[] tmp) {
584
this.tmp = tmp;
585
}
586
587
final NormalizingPathIterator init(final PathIterator src) {
588
this.src = src;
589
return this; // fluent API
590
}
591
592
/**
593
* Disposes this path iterator:
594
* clean up before reusing this instance
595
*/
596
final void dispose() {
597
// free source PathIterator:
598
this.src = null;
599
}
600
601
@Override
602
public final int currentSegment(final double[] coords) {
603
int lastCoord;
604
final int type = src.currentSegment(coords);
605
606
switch(type) {
607
case PathIterator.SEG_MOVETO:
608
case PathIterator.SEG_LINETO:
609
lastCoord = 0;
610
break;
611
case PathIterator.SEG_QUADTO:
612
lastCoord = 2;
613
break;
614
case PathIterator.SEG_CUBICTO:
615
lastCoord = 4;
616
break;
617
case PathIterator.SEG_CLOSE:
618
// we don't want to deal with this case later. We just exit now
619
curx_adjust = movx_adjust;
620
cury_adjust = movy_adjust;
621
return type;
622
default:
623
throw new InternalError("Unrecognized curve type");
624
}
625
626
// normalize endpoint
627
double coord, x_adjust, y_adjust;
628
629
coord = coords[lastCoord];
630
x_adjust = normCoord(coord); // new coord
631
coords[lastCoord] = x_adjust;
632
x_adjust -= coord;
633
634
coord = coords[lastCoord + 1];
635
y_adjust = normCoord(coord); // new coord
636
coords[lastCoord + 1] = y_adjust;
637
y_adjust -= coord;
638
639
// now that the end points are done, normalize the control points
640
switch(type) {
641
case PathIterator.SEG_MOVETO:
642
movx_adjust = x_adjust;
643
movy_adjust = y_adjust;
644
break;
645
case PathIterator.SEG_LINETO:
646
break;
647
case PathIterator.SEG_QUADTO:
648
coords[0] += (curx_adjust + x_adjust) / 2.0d;
649
coords[1] += (cury_adjust + y_adjust) / 2.0d;
650
break;
651
case PathIterator.SEG_CUBICTO:
652
coords[0] += curx_adjust;
653
coords[1] += cury_adjust;
654
coords[2] += x_adjust;
655
coords[3] += y_adjust;
656
break;
657
case PathIterator.SEG_CLOSE:
658
// handled earlier
659
default:
660
}
661
curx_adjust = x_adjust;
662
cury_adjust = y_adjust;
663
return type;
664
}
665
666
abstract double normCoord(final double coord);
667
668
@Override
669
public final int currentSegment(final float[] coords) {
670
final double[] _tmp = tmp; // dirty
671
int type = this.currentSegment(_tmp);
672
for (int i = 0; i < 6; i++) {
673
coords[i] = (float)_tmp[i];
674
}
675
return type;
676
}
677
678
@Override
679
public final int getWindingRule() {
680
return src.getWindingRule();
681
}
682
683
@Override
684
public final boolean isDone() {
685
if (src.isDone()) {
686
// Dispose this instance:
687
dispose();
688
return true;
689
}
690
return false;
691
}
692
693
@Override
694
public final void next() {
695
src.next();
696
}
697
698
static final class NearestPixelCenter
699
extends NormalizingPathIterator
700
{
701
NearestPixelCenter(final double[] tmp) {
702
super(tmp);
703
}
704
705
@Override
706
double normCoord(final double coord) {
707
// round to nearest pixel center
708
return Math.floor(coord) + 0.5d;
709
}
710
}
711
712
static final class NearestPixelQuarter
713
extends NormalizingPathIterator
714
{
715
NearestPixelQuarter(final double[] tmp) {
716
super(tmp);
717
}
718
719
@Override
720
double normCoord(final double coord) {
721
// round to nearest (0.25, 0.25) pixel quarter
722
return Math.floor(coord + 0.25d) + 0.25d;
723
}
724
}
725
}
726
727
private static void pathTo(final RendererContext rdrCtx, final PathIterator pi,
728
DPathConsumer2D pc2d)
729
{
730
if (USE_PATH_SIMPLIFIER) {
731
// Use path simplifier at the first step
732
// to remove useless points
733
pc2d = rdrCtx.pathSimplifier.init(pc2d);
734
}
735
736
// mark context as DIRTY:
737
rdrCtx.dirty = true;
738
739
pathToLoop(rdrCtx.double6, pi, pc2d);
740
741
// mark context as CLEAN:
742
rdrCtx.dirty = false;
743
}
744
745
private static void pathToLoop(final double[] coords, final PathIterator pi,
746
final DPathConsumer2D pc2d)
747
{
748
// ported from DuctusRenderingEngine.feedConsumer() but simplified:
749
// - removed skip flag = !subpathStarted
750
// - removed pathClosed (ie subpathStarted not set to false)
751
boolean subpathStarted = false;
752
753
for (; !pi.isDone(); pi.next()) {
754
switch (pi.currentSegment(coords)) {
755
case PathIterator.SEG_MOVETO:
756
/* Checking SEG_MOVETO coordinates if they are out of the
757
* [LOWER_BND, UPPER_BND] range. This check also handles NaN
758
* and Infinity values. Skipping next path segment in case of
759
* invalid data.
760
*/
761
if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
762
coords[1] < UPPER_BND && coords[1] > LOWER_BND)
763
{
764
pc2d.moveTo(coords[0], coords[1]);
765
subpathStarted = true;
766
}
767
break;
768
case PathIterator.SEG_LINETO:
769
/* Checking SEG_LINETO coordinates if they are out of the
770
* [LOWER_BND, UPPER_BND] range. This check also handles NaN
771
* and Infinity values. Ignoring current path segment in case
772
* of invalid data. If segment is skipped its endpoint
773
* (if valid) is used to begin new subpath.
774
*/
775
if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
776
coords[1] < UPPER_BND && coords[1] > LOWER_BND)
777
{
778
if (subpathStarted) {
779
pc2d.lineTo(coords[0], coords[1]);
780
} else {
781
pc2d.moveTo(coords[0], coords[1]);
782
subpathStarted = true;
783
}
784
}
785
break;
786
case PathIterator.SEG_QUADTO:
787
// Quadratic curves take two points
788
/* Checking SEG_QUADTO coordinates if they are out of the
789
* [LOWER_BND, UPPER_BND] range. This check also handles NaN
790
* and Infinity values. Ignoring current path segment in case
791
* of invalid endpoints's data. Equivalent to the SEG_LINETO
792
* if endpoint coordinates are valid but there are invalid data
793
* among other coordinates
794
*/
795
if (coords[2] < UPPER_BND && coords[2] > LOWER_BND &&
796
coords[3] < UPPER_BND && coords[3] > LOWER_BND)
797
{
798
if (subpathStarted) {
799
if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
800
coords[1] < UPPER_BND && coords[1] > LOWER_BND)
801
{
802
pc2d.quadTo(coords[0], coords[1],
803
coords[2], coords[3]);
804
} else {
805
pc2d.lineTo(coords[2], coords[3]);
806
}
807
} else {
808
pc2d.moveTo(coords[2], coords[3]);
809
subpathStarted = true;
810
}
811
}
812
break;
813
case PathIterator.SEG_CUBICTO:
814
// Cubic curves take three points
815
/* Checking SEG_CUBICTO coordinates if they are out of the
816
* [LOWER_BND, UPPER_BND] range. This check also handles NaN
817
* and Infinity values. Ignoring current path segment in case
818
* of invalid endpoints's data. Equivalent to the SEG_LINETO
819
* if endpoint coordinates are valid but there are invalid data
820
* among other coordinates
821
*/
822
if (coords[4] < UPPER_BND && coords[4] > LOWER_BND &&
823
coords[5] < UPPER_BND && coords[5] > LOWER_BND)
824
{
825
if (subpathStarted) {
826
if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
827
coords[1] < UPPER_BND && coords[1] > LOWER_BND &&
828
coords[2] < UPPER_BND && coords[2] > LOWER_BND &&
829
coords[3] < UPPER_BND && coords[3] > LOWER_BND)
830
{
831
pc2d.curveTo(coords[0], coords[1],
832
coords[2], coords[3],
833
coords[4], coords[5]);
834
} else {
835
pc2d.lineTo(coords[4], coords[5]);
836
}
837
} else {
838
pc2d.moveTo(coords[4], coords[5]);
839
subpathStarted = true;
840
}
841
}
842
break;
843
case PathIterator.SEG_CLOSE:
844
if (subpathStarted) {
845
pc2d.closePath();
846
// do not set subpathStarted to false
847
// in case of missing moveTo() after close()
848
}
849
break;
850
default:
851
}
852
}
853
pc2d.pathDone();
854
}
855
856
/**
857
* Construct an antialiased tile generator for the given shape with
858
* the given rendering attributes and store the bounds of the tile
859
* iteration in the bbox parameter.
860
* The {@code at} parameter specifies a transform that should affect
861
* both the shape and the {@code BasicStroke} attributes.
862
* The {@code clip} parameter specifies the current clip in effect
863
* in device coordinates and can be used to prune the data for the
864
* operation, but the renderer is not required to perform any
865
* clipping.
866
* If the {@code BasicStroke} parameter is null then the shape
867
* should be filled as is, otherwise the attributes of the
868
* {@code BasicStroke} should be used to specify a draw operation.
869
* The {@code thin} parameter indicates whether or not the
870
* transformed {@code BasicStroke} represents coordinates smaller
871
* than the minimum resolution of the antialiasing rasterizer as
872
* specified by the {@code getMinimumAAPenWidth()} method.
873
* <p>
874
* Upon returning, this method will fill the {@code bbox} parameter
875
* with 4 values indicating the bounds of the iteration of the
876
* tile generator.
877
* The iteration order of the tiles will be as specified by the
878
* pseudo-code:
879
* <pre>
880
* for (y = bbox[1]; y < bbox[3]; y += tileheight) {
881
* for (x = bbox[0]; x < bbox[2]; x += tilewidth) {
882
* }
883
* }
884
* </pre>
885
* If there is no output to be rendered, this method may return
886
* null.
887
*
888
* @param s the shape to be rendered (fill or draw)
889
* @param at the transform to be applied to the shape and the
890
* stroke attributes
891
* @param clip the current clip in effect in device coordinates
892
* @param bs if non-null, a {@code BasicStroke} whose attributes
893
* should be applied to this operation
894
* @param thin true if the transformed stroke attributes are smaller
895
* than the minimum dropout pen width
896
* @param normalize true if the {@code VALUE_STROKE_NORMALIZE}
897
* {@code RenderingHint} is in effect
898
* @param bbox returns the bounds of the iteration
899
* @return the {@code AATileGenerator} instance to be consulted
900
* for tile coverages, or null if there is no output to render
901
* @since 1.7
902
*/
903
@Override
904
public AATileGenerator getAATileGenerator(Shape s,
905
AffineTransform at,
906
Region clip,
907
BasicStroke bs,
908
boolean thin,
909
boolean normalize,
910
int[] bbox)
911
{
912
MarlinTileGenerator ptg = null;
913
Renderer r = null;
914
915
final RendererContext rdrCtx = getRendererContext();
916
try {
917
if (DO_CLIP || (DO_CLIP_RUNTIME_ENABLE && MarlinProperties.isDoClipAtRuntime())) {
918
// Define the initial clip bounds:
919
final double[] clipRect = rdrCtx.clipRect;
920
921
// Adjust the clipping rectangle with the renderer offsets
922
final double rdrOffX = Renderer.RDR_OFFSET_X;
923
final double rdrOffY = Renderer.RDR_OFFSET_Y;
924
925
// add a small rounding error:
926
final double margin = 1e-3d;
927
928
clipRect[0] = clip.getLoY()
929
- margin + rdrOffY;
930
clipRect[1] = clip.getLoY() + clip.getHeight()
931
+ margin + rdrOffY;
932
clipRect[2] = clip.getLoX()
933
- margin + rdrOffX;
934
clipRect[3] = clip.getLoX() + clip.getWidth()
935
+ margin + rdrOffX;
936
937
if (MarlinConst.DO_LOG_CLIP) {
938
MarlinUtils.logInfo("clipRect (clip): "
939
+ Arrays.toString(rdrCtx.clipRect));
940
}
941
942
// Enable clipping:
943
rdrCtx.doClip = true;
944
}
945
946
// Test if at is identity:
947
final AffineTransform _at = (at != null && !at.isIdentity()) ? at
948
: null;
949
950
final NormMode norm = (normalize) ? NormMode.ON_WITH_AA : NormMode.OFF;
951
952
if (bs == null) {
953
// fill shape:
954
final PathIterator pi = norm.getNormalizingPathIterator(rdrCtx,
955
s.getPathIterator(_at));
956
957
// note: Winding rule may be EvenOdd ONLY for fill operations !
958
r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),
959
clip.getWidth(), clip.getHeight(),
960
pi.getWindingRule());
961
962
DPathConsumer2D pc2d = r;
963
964
if (DO_CLIP_FILL && rdrCtx.doClip) {
965
if (DO_TRACE_PATH) {
966
// trace Filler:
967
pc2d = rdrCtx.transformerPC2D.traceFiller(pc2d);
968
}
969
pc2d = rdrCtx.transformerPC2D.pathClipper(pc2d);
970
}
971
972
if (DO_TRACE_PATH) {
973
// trace Input:
974
pc2d = rdrCtx.transformerPC2D.traceInput(pc2d);
975
}
976
pathTo(rdrCtx, pi, pc2d);
977
978
} else {
979
// draw shape with given stroke:
980
r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),
981
clip.getWidth(), clip.getHeight(),
982
WIND_NON_ZERO);
983
984
strokeTo(rdrCtx, s, _at, bs, thin, norm, true, r);
985
}
986
if (r.endRendering()) {
987
ptg = rdrCtx.ptg.init();
988
ptg.getBbox(bbox);
989
// note: do not returnRendererContext(rdrCtx)
990
// as it will be called later by MarlinTileGenerator.dispose()
991
r = null;
992
}
993
} finally {
994
if (r != null) {
995
// dispose renderer and recycle the RendererContext instance:
996
r.dispose();
997
}
998
}
999
1000
// Return null to cancel AA tile generation (nothing to render)
1001
return ptg;
1002
}
1003
1004
@Override
1005
public AATileGenerator getAATileGenerator(double x, double y,
1006
double dx1, double dy1,
1007
double dx2, double dy2,
1008
double lw1, double lw2,
1009
Region clip,
1010
int[] bbox)
1011
{
1012
// REMIND: Deal with large coordinates!
1013
double ldx1, ldy1, ldx2, ldy2;
1014
boolean innerpgram = (lw1 > 0.0d && lw2 > 0.0d);
1015
1016
if (innerpgram) {
1017
ldx1 = dx1 * lw1;
1018
ldy1 = dy1 * lw1;
1019
ldx2 = dx2 * lw2;
1020
ldy2 = dy2 * lw2;
1021
x -= (ldx1 + ldx2) / 2.0d;
1022
y -= (ldy1 + ldy2) / 2.0d;
1023
dx1 += ldx1;
1024
dy1 += ldy1;
1025
dx2 += ldx2;
1026
dy2 += ldy2;
1027
if (lw1 > 1.0d && lw2 > 1.0d) {
1028
// Inner parallelogram was entirely consumed by stroke...
1029
innerpgram = false;
1030
}
1031
} else {
1032
ldx1 = ldy1 = ldx2 = ldy2 = 0.0d;
1033
}
1034
1035
MarlinTileGenerator ptg = null;
1036
Renderer r = null;
1037
1038
final RendererContext rdrCtx = getRendererContext();
1039
try {
1040
r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),
1041
clip.getWidth(), clip.getHeight(),
1042
WIND_EVEN_ODD);
1043
1044
r.moveTo( x, y);
1045
r.lineTo( (x+dx1), (y+dy1));
1046
r.lineTo( (x+dx1+dx2), (y+dy1+dy2));
1047
r.lineTo( (x+dx2), (y+dy2));
1048
r.closePath();
1049
1050
if (innerpgram) {
1051
x += ldx1 + ldx2;
1052
y += ldy1 + ldy2;
1053
dx1 -= 2.0d * ldx1;
1054
dy1 -= 2.0d * ldy1;
1055
dx2 -= 2.0d * ldx2;
1056
dy2 -= 2.0d * ldy2;
1057
r.moveTo( x, y);
1058
r.lineTo( (x+dx1), (y+dy1));
1059
r.lineTo( (x+dx1+dx2), (y+dy1+dy2));
1060
r.lineTo( (x+dx2), (y+dy2));
1061
r.closePath();
1062
}
1063
r.pathDone();
1064
1065
if (r.endRendering()) {
1066
ptg = rdrCtx.ptg.init();
1067
ptg.getBbox(bbox);
1068
// note: do not returnRendererContext(rdrCtx)
1069
// as it will be called later by MarlinTileGenerator.dispose()
1070
r = null;
1071
}
1072
} finally {
1073
if (r != null) {
1074
// dispose renderer and recycle the RendererContext instance:
1075
r.dispose();
1076
}
1077
}
1078
1079
// Return null to cancel AA tile generation (nothing to render)
1080
return ptg;
1081
}
1082
1083
/**
1084
* Returns the minimum pen width that the antialiasing rasterizer
1085
* can represent without dropouts occuring.
1086
* @since 1.7
1087
*/
1088
@Override
1089
public float getMinimumAAPenSize() {
1090
return MIN_PEN_SIZE;
1091
}
1092
1093
static {
1094
if (PathIterator.WIND_NON_ZERO != WIND_NON_ZERO ||
1095
PathIterator.WIND_EVEN_ODD != WIND_EVEN_ODD ||
1096
BasicStroke.JOIN_MITER != JOIN_MITER ||
1097
BasicStroke.JOIN_ROUND != JOIN_ROUND ||
1098
BasicStroke.JOIN_BEVEL != JOIN_BEVEL ||
1099
BasicStroke.CAP_BUTT != CAP_BUTT ||
1100
BasicStroke.CAP_ROUND != CAP_ROUND ||
1101
BasicStroke.CAP_SQUARE != CAP_SQUARE)
1102
{
1103
throw new InternalError("mismatched renderer constants");
1104
}
1105
}
1106
1107
// --- RendererContext handling ---
1108
// use ThreadLocal or ConcurrentLinkedQueue to get one RendererContext
1109
private static final boolean USE_THREAD_LOCAL;
1110
1111
// reference type stored in either TL or CLQ
1112
static final int REF_TYPE;
1113
1114
// Per-thread RendererContext
1115
private static final ReentrantContextProvider<RendererContext> RDR_CTX_PROVIDER;
1116
1117
// Static initializer to use TL or CLQ mode
1118
static {
1119
USE_THREAD_LOCAL = MarlinProperties.isUseThreadLocal();
1120
1121
// Soft reference by default:
1122
@SuppressWarnings("removal")
1123
final String refType = AccessController.doPrivileged(
1124
new GetPropertyAction("sun.java2d.renderer.useRef",
1125
"soft"));
1126
switch (refType) {
1127
default:
1128
case "soft":
1129
REF_TYPE = ReentrantContextProvider.REF_SOFT;
1130
break;
1131
case "weak":
1132
REF_TYPE = ReentrantContextProvider.REF_WEAK;
1133
break;
1134
case "hard":
1135
REF_TYPE = ReentrantContextProvider.REF_HARD;
1136
break;
1137
}
1138
1139
if (USE_THREAD_LOCAL) {
1140
RDR_CTX_PROVIDER = new ReentrantContextProviderTL<RendererContext>(REF_TYPE)
1141
{
1142
@Override
1143
protected RendererContext newContext() {
1144
return RendererContext.createContext();
1145
}
1146
};
1147
} else {
1148
RDR_CTX_PROVIDER = new ReentrantContextProviderCLQ<RendererContext>(REF_TYPE)
1149
{
1150
@Override
1151
protected RendererContext newContext() {
1152
return RendererContext.createContext();
1153
}
1154
};
1155
}
1156
}
1157
1158
private static boolean SETTINGS_LOGGED = !ENABLE_LOGS;
1159
1160
private static void logSettings(final String reClass) {
1161
// log information at startup
1162
if (SETTINGS_LOGGED) {
1163
return;
1164
}
1165
SETTINGS_LOGGED = true;
1166
1167
String refType;
1168
switch (REF_TYPE) {
1169
default:
1170
case ReentrantContextProvider.REF_HARD:
1171
refType = "hard";
1172
break;
1173
case ReentrantContextProvider.REF_SOFT:
1174
refType = "soft";
1175
break;
1176
case ReentrantContextProvider.REF_WEAK:
1177
refType = "weak";
1178
break;
1179
}
1180
1181
logInfo("=========================================================="
1182
+ "=====================");
1183
1184
logInfo("Marlin software rasterizer = ENABLED");
1185
logInfo("Version = ["
1186
+ Version.getVersion() + "]");
1187
logInfo("sun.java2d.renderer = "
1188
+ reClass);
1189
logInfo("sun.java2d.renderer.useThreadLocal = "
1190
+ USE_THREAD_LOCAL);
1191
logInfo("sun.java2d.renderer.useRef = "
1192
+ refType);
1193
1194
logInfo("sun.java2d.renderer.edges = "
1195
+ MarlinConst.INITIAL_EDGES_COUNT);
1196
logInfo("sun.java2d.renderer.pixelWidth = "
1197
+ MarlinConst.INITIAL_PIXEL_WIDTH);
1198
logInfo("sun.java2d.renderer.pixelHeight = "
1199
+ MarlinConst.INITIAL_PIXEL_HEIGHT);
1200
1201
logInfo("sun.java2d.renderer.subPixel_log2_X = "
1202
+ MarlinConst.SUBPIXEL_LG_POSITIONS_X);
1203
logInfo("sun.java2d.renderer.subPixel_log2_Y = "
1204
+ MarlinConst.SUBPIXEL_LG_POSITIONS_Y);
1205
1206
logInfo("sun.java2d.renderer.tileSize_log2 = "
1207
+ MarlinConst.TILE_H_LG);
1208
logInfo("sun.java2d.renderer.tileWidth_log2 = "
1209
+ MarlinConst.TILE_W_LG);
1210
logInfo("sun.java2d.renderer.blockSize_log2 = "
1211
+ MarlinConst.BLOCK_SIZE_LG);
1212
1213
// RLE / blockFlags settings
1214
1215
logInfo("sun.java2d.renderer.forceRLE = "
1216
+ MarlinProperties.isForceRLE());
1217
logInfo("sun.java2d.renderer.forceNoRLE = "
1218
+ MarlinProperties.isForceNoRLE());
1219
logInfo("sun.java2d.renderer.useTileFlags = "
1220
+ MarlinProperties.isUseTileFlags());
1221
logInfo("sun.java2d.renderer.useTileFlags.useHeuristics = "
1222
+ MarlinProperties.isUseTileFlagsWithHeuristics());
1223
logInfo("sun.java2d.renderer.rleMinWidth = "
1224
+ MarlinCache.RLE_MIN_WIDTH);
1225
1226
// optimisation parameters
1227
logInfo("sun.java2d.renderer.useSimplifier = "
1228
+ MarlinConst.USE_SIMPLIFIER);
1229
logInfo("sun.java2d.renderer.usePathSimplifier= "
1230
+ MarlinConst.USE_PATH_SIMPLIFIER);
1231
logInfo("sun.java2d.renderer.pathSimplifier.pixTol = "
1232
+ MarlinProperties.getPathSimplifierPixelTolerance());
1233
1234
logInfo("sun.java2d.renderer.clip = "
1235
+ MarlinProperties.isDoClip());
1236
logInfo("sun.java2d.renderer.clip.runtime.enable = "
1237
+ MarlinProperties.isDoClipRuntimeFlag());
1238
1239
logInfo("sun.java2d.renderer.clip.subdivider = "
1240
+ MarlinProperties.isDoClipSubdivider());
1241
logInfo("sun.java2d.renderer.clip.subdivider.minLength = "
1242
+ MarlinProperties.getSubdividerMinLength());
1243
1244
// debugging parameters
1245
logInfo("sun.java2d.renderer.doStats = "
1246
+ MarlinConst.DO_STATS);
1247
logInfo("sun.java2d.renderer.doMonitors = "
1248
+ MarlinConst.DO_MONITORS);
1249
logInfo("sun.java2d.renderer.doChecks = "
1250
+ MarlinConst.DO_CHECKS);
1251
1252
// logging parameters
1253
logInfo("sun.java2d.renderer.useLogger = "
1254
+ MarlinConst.USE_LOGGER);
1255
logInfo("sun.java2d.renderer.logCreateContext = "
1256
+ MarlinConst.LOG_CREATE_CONTEXT);
1257
logInfo("sun.java2d.renderer.logUnsafeMalloc = "
1258
+ MarlinConst.LOG_UNSAFE_MALLOC);
1259
1260
// quality settings
1261
logInfo("sun.java2d.renderer.curve_len_err = "
1262
+ MarlinProperties.getCurveLengthError());
1263
logInfo("sun.java2d.renderer.cubic_dec_d2 = "
1264
+ MarlinProperties.getCubicDecD2());
1265
logInfo("sun.java2d.renderer.cubic_inc_d1 = "
1266
+ MarlinProperties.getCubicIncD1());
1267
logInfo("sun.java2d.renderer.quad_dec_d2 = "
1268
+ MarlinProperties.getQuadDecD2());
1269
1270
logInfo("Renderer settings:");
1271
logInfo("CUB_DEC_BND = " + Renderer.CUB_DEC_BND);
1272
logInfo("CUB_INC_BND = " + Renderer.CUB_INC_BND);
1273
logInfo("QUAD_DEC_BND = " + Renderer.QUAD_DEC_BND);
1274
1275
logInfo("INITIAL_EDGES_CAPACITY = "
1276
+ MarlinConst.INITIAL_EDGES_CAPACITY);
1277
logInfo("INITIAL_CROSSING_COUNT = "
1278
+ Renderer.INITIAL_CROSSING_COUNT);
1279
1280
logInfo("=========================================================="
1281
+ "=====================");
1282
}
1283
1284
/**
1285
* Get the RendererContext instance dedicated to the current thread
1286
* @return RendererContext instance
1287
*/
1288
@SuppressWarnings({"unchecked"})
1289
static RendererContext getRendererContext() {
1290
final RendererContext rdrCtx = RDR_CTX_PROVIDER.acquire();
1291
if (DO_MONITORS) {
1292
rdrCtx.stats.mon_pre_getAATileGenerator.start();
1293
}
1294
return rdrCtx;
1295
}
1296
1297
/**
1298
* Reset and return the given RendererContext instance for reuse
1299
* @param rdrCtx RendererContext instance
1300
*/
1301
static void returnRendererContext(final RendererContext rdrCtx) {
1302
rdrCtx.dispose();
1303
1304
if (DO_MONITORS) {
1305
rdrCtx.stats.mon_pre_getAATileGenerator.stop();
1306
}
1307
RDR_CTX_PROVIDER.release(rdrCtx);
1308
}
1309
}
1310
1311