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 }