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