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