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