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