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