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 getNormalizingPathIterator(final DRendererContext rdrCtx, final PathIterator src)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 getNormalizingPathIterator(final DRendererContext rdrCtx, final PathIterator src)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 getNormalizingPathIterator(final DRendererContext rdrCtx, final PathIterator src)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 getNormalizingPathIterator(DRendererContext rdrCtx, PathIterator src)94 abstract PathIterator getNormalizingPathIterator(DRendererContext rdrCtx, 95 PathIterator src); 96 } 97 98 /** 99 * Public constructor 100 */ DMarlinRenderingEngine()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 createStrokedShape(Shape src, float width, int caps, int join, float miterlimit, float[] dashes, float dashphase)124 public Shape createStrokedShape(Shape src, 125 float width, 126 int caps, 127 int join, 128 float miterlimit, 129 float[] dashes, 130 float dashphase) 131 { 132 final 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 strokeTo(Shape src, AffineTransform at, BasicStroke bs, boolean thin, boolean normalize, boolean antialias, final PathConsumer2D consumer)187 public void strokeTo(Shape src, 188 AffineTransform at, 189 BasicStroke bs, 190 boolean thin, 191 boolean normalize, 192 boolean antialias, 193 final PathConsumer2D consumer) 194 { 195 final NormMode norm = (normalize) ? 196 ((antialias) ? NormMode.ON_WITH_AA : NormMode.ON_NO_AA) 197 : NormMode.OFF; 198 199 final 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 strokeTo(final DRendererContext rdrCtx, Shape src, AffineTransform at, BasicStroke bs, boolean thin, NormMode normalize, boolean antialias, DPathConsumer2D pc2d)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 userSpaceLineWidth(AffineTransform at, double lw)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 strokeTo(final DRendererContext rdrCtx, Shape src, AffineTransform at, double width, NormMode norm, int caps, int join, float miterlimit, float[] dashes, float dashphase, DPathConsumer2D pc2d)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 nearZero(final double num)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 NormalizingPathIterator(final double[] tmp)500 NormalizingPathIterator(final double[] tmp) { 501 this.tmp = tmp; 502 } 503 init(final PathIterator src)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 */ dispose()513 final void dispose() { 514 // free source PathIterator: 515 this.src = null; 516 } 517 518 @Override currentSegment(final double[] coords)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 normCoord(final double coord)583 abstract double normCoord(final double coord); 584 585 @Override currentSegment(final float[] coords)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 getWindingRule()596 public final int getWindingRule() { 597 return src.getWindingRule(); 598 } 599 600 @Override isDone()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 next()611 public final void next() { 612 src.next(); 613 } 614 615 static final class NearestPixelCenter 616 extends NormalizingPathIterator 617 { NearestPixelCenter(final double[] tmp)618 NearestPixelCenter(final double[] tmp) { 619 super(tmp); 620 } 621 622 @Override normCoord(final double coord)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 { NearestPixelQuarter(final double[] tmp)632 NearestPixelQuarter(final double[] tmp) { 633 super(tmp); 634 } 635 636 @Override normCoord(final double coord)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 pathTo(final DRendererContext rdrCtx, final PathIterator pi, DPathConsumer2D pc2d)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 pathToLoop(final double[] coords, final PathIterator pi, final DPathConsumer2D pc2d)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 getAATileGenerator(Shape s, AffineTransform at, Region clip, BasicStroke bs, boolean thin, boolean normalize, int[] bbox)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 getAATileGenerator(double x, double y, double dx1, double dy1, double dx2, double dy2, double lw1, double lw2, Region clip, int[] bbox)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 getMinimumAAPenSize()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 logSettings(final String reClass)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"}) getRendererContext()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 */ returnRendererContext(final DRendererContext rdrCtx)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 } 1226