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