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