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