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