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