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