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} 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); 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 | 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} 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); 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 |