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