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