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