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