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