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