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