67 }, 68 OFF{ 69 @Override 70 PathIterator getNormalizingPathIterator(final DRendererContext rdrCtx, 71 final PathIterator src) 72 { 73 // return original path iterator if normalization is disabled: 74 return src; 75 } 76 }; 77 78 abstract PathIterator getNormalizingPathIterator(DRendererContext rdrCtx, 79 PathIterator src); 80 } 81 82 private static final float MIN_PEN_SIZE = 1.0f / NORM_SUBPIXELS; 83 84 static final double UPPER_BND = Float.MAX_VALUE / 2.0d; 85 static final double LOWER_BND = -UPPER_BND; 86 87 /** 88 * Public constructor 89 */ 90 public DMarlinRenderingEngine() { 91 super(); 92 logSettings(DMarlinRenderingEngine.class.getName()); 93 } 94 95 /** 96 * Create a widened path as specified by the parameters. 97 * <p> 98 * The specified {@code src} {@link Shape} is widened according 99 * to the specified attribute parameters as per the 100 * {@link BasicStroke} specification. 101 * 102 * @param src the source path to be widened 103 * @param width the width of the widened path as per {@code BasicStroke} 104 * @param caps the end cap decorations as per {@code BasicStroke} 105 * @param join the segment join decorations as per {@code BasicStroke} 106 * @param miterlimit the miter limit as per {@code BasicStroke} 307 float dashphase, 308 DPathConsumer2D pc2d) 309 { 310 // We use strokerat so that in Stroker and Dasher we can work only 311 // with the pre-transformation coordinates. This will repeat a lot of 312 // computations done in the path iterator, but the alternative is to 313 // work with transformed paths and compute untransformed coordinates 314 // as needed. This would be faster but I do not think the complexity 315 // of working with both untransformed and transformed coordinates in 316 // the same code is worth it. 317 // However, if a path's width is constant after a transformation, 318 // we can skip all this untransforming. 319 320 // As pathTo() will check transformed coordinates for invalid values 321 // (NaN / Infinity) to ignore such points, it is necessary to apply the 322 // transformation before the path processing. 323 AffineTransform strokerat = null; 324 325 int dashLen = -1; 326 boolean recycleDashes = false; 327 double[] dashesD = null; 328 329 // Ensure converting dashes to double precision: 330 if (dashes != null) { 331 recycleDashes = true; 332 dashLen = dashes.length; 333 dashesD = rdrCtx.dasher.copyDashArray(dashes); 334 } 335 336 if (at != null && !at.isIdentity()) { 337 final double a = at.getScaleX(); 338 final double b = at.getShearX(); 339 final double c = at.getShearY(); 340 final double d = at.getScaleY(); 341 final double det = a * d - c * b; 342 343 if (Math.abs(det) <= (2.0d * Double.MIN_VALUE)) { 344 // this rendering engine takes one dimensional curves and turns 345 // them into 2D shapes by giving them width. 346 // However, if everything is to be passed through a singular 347 // transformation, these 2D shapes will be squashed down to 1D 348 // again so, nothing can be drawn. 349 350 // Every path needs an initial moveTo and a pathDone. If these 351 // are not there this causes a SIGSEGV in libawt.so (at the time 352 // of writing of this comment (September 16, 2010)). Actually, 353 // I am not sure if the moveTo is necessary to avoid the SIGSEGV 354 // but the pathDone is definitely needed. 355 pc2d.moveTo(0.0d, 0.0d); 356 pc2d.pathDone(); 357 return; 358 } 359 360 // If the transform is a constant multiple of an orthogonal transformation 361 // then every length is just multiplied by a constant, so we just 362 // need to transform input paths to stroker and tell stroker 363 // the scaled width. This condition is satisfied if 364 // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we 365 // leave a bit of room for error. 366 if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) { 367 final double scale = Math.sqrt(a*a + c*c); 368 369 if (dashesD != null) { 370 for (int i = 0; i < dashLen; i++) { 371 dashesD[i] *= scale; 372 } 373 dashphase *= scale; 374 } 375 width *= scale; 376 377 // by now strokerat == null. Input paths to 378 // stroker (and maybe dasher) will have the full transform at 379 // applied to them and nothing will happen to the output paths. 380 } else { 381 strokerat = at; 382 383 // by now strokerat == at. Input paths to 384 // stroker (and maybe dasher) will have the full transform at 385 // applied to them, then they will be normalized, and then 386 // the inverse of *only the non translation part of at* will 387 // be applied to the normalized paths. This won't cause problems 388 // in stroker, because, suppose at = T*A, where T is just the 389 // translation part of at, and A is the rest. T*A has already 390 // been applied to Stroker/Dasher's input. Then Ainv will be 391 // applied. Ainv*T*A is not equal to T, but it is a translation, 392 // which means that none of stroker's assumptions about its 393 // input will be violated. After all this, A will be applied 394 // to stroker's output. 395 } 396 } else { 397 // either at is null or it's the identity. In either case 398 // we don't transform the path. 399 at = null; 400 } 401 402 if (USE_SIMPLIFIER) { 403 // Use simplifier after stroker before Renderer 404 // to remove collinear segments (notably due to cap square) 405 pc2d = rdrCtx.simplifier.init(pc2d); 406 } 407 408 final DTransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; 409 pc2d = transformerPC2D.deltaTransformConsumer(pc2d, strokerat); 410 411 pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit); 412 413 if (dashesD != null) { 414 pc2d = rdrCtx.dasher.init(pc2d, dashesD, dashLen, dashphase, 415 recycleDashes); 416 } 417 pc2d = transformerPC2D.inverseDeltaTransformConsumer(pc2d, strokerat); 418 419 final PathIterator pi = norm.getNormalizingPathIterator(rdrCtx, 420 src.getPathIterator(at)); 421 422 pathTo(rdrCtx, pi, pc2d); 423 424 /* 425 * Pipeline seems to be: 426 * shape.getPathIterator(at) 427 * -> (NormalizingPathIterator) 428 * -> (inverseDeltaTransformConsumer) 429 * -> (Dasher) 430 * -> Stroker 431 * -> (deltaTransformConsumer) 432 * 433 * -> (CollinearSimplifier) to remove redundant segments 434 * 435 * -> pc2d = Renderer (bounding box) 436 */ 437 } 438 788 final NormMode norm = (normalize) ? NormMode.ON_WITH_AA : NormMode.OFF; 789 790 if (bs == null) { 791 // fill shape: 792 final PathIterator pi = norm.getNormalizingPathIterator(rdrCtx, 793 s.getPathIterator(_at)); 794 795 // note: Winding rule may be EvenOdd ONLY for fill operations ! 796 r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(), 797 clip.getWidth(), clip.getHeight(), 798 pi.getWindingRule()); 799 800 // TODO: subdivide quad/cubic curves into monotonic curves ? 801 pathTo(rdrCtx, pi, r); 802 } else { 803 // draw shape with given stroke: 804 r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(), 805 clip.getWidth(), clip.getHeight(), 806 PathIterator.WIND_NON_ZERO); 807 808 strokeTo(rdrCtx, s, _at, bs, thin, norm, true, r); 809 } 810 if (r.endRendering()) { 811 ptg = rdrCtx.ptg.init(); 812 ptg.getBbox(bbox); 813 // note: do not returnRendererContext(rdrCtx) 814 // as it will be called later by MarlinTileGenerator.dispose() 815 r = null; 816 } 817 } finally { 818 if (r != null) { 819 // dispose renderer and recycle the RendererContext instance: 820 r.dispose(); 821 } 822 } 823 824 // Return null to cancel AA tile generation (nothing to render) 825 return ptg; 826 } 827 845 x -= (ldx1 + ldx2) / 2.0d; 846 y -= (ldy1 + ldy2) / 2.0d; 847 dx1 += ldx1; 848 dy1 += ldy1; 849 dx2 += ldx2; 850 dy2 += ldy2; 851 if (lw1 > 1.0d && lw2 > 1.0d) { 852 // Inner parallelogram was entirely consumed by stroke... 853 innerpgram = false; 854 } 855 } else { 856 ldx1 = ldy1 = ldx2 = ldy2 = 0.0d; 857 } 858 859 MarlinTileGenerator ptg = null; 860 DRenderer r = null; 861 862 final DRendererContext rdrCtx = getRendererContext(); 863 try { 864 r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(), 865 clip.getWidth(), clip.getHeight(), 866 DRenderer.WIND_EVEN_ODD); 867 868 r.moveTo( x, y); 869 r.lineTo( (x+dx1), (y+dy1)); 870 r.lineTo( (x+dx1+dx2), (y+dy1+dy2)); 871 r.lineTo( (x+dx2), (y+dy2)); 872 r.closePath(); 873 874 if (innerpgram) { 875 x += ldx1 + ldx2; 876 y += ldy1 + ldy2; 877 dx1 -= 2.0d * ldx1; 878 dy1 -= 2.0d * ldy1; 879 dx2 -= 2.0d * ldx2; 880 dy2 -= 2.0d * ldy2; 881 r.moveTo( x, y); 882 r.lineTo( (x+dx1), (y+dy1)); 883 r.lineTo( (x+dx1+dx2), (y+dy1+dy2)); 884 r.lineTo( (x+dx2), (y+dy2)); 885 r.closePath(); 886 } 1027 + MarlinConst.TILE_W_LG); 1028 logInfo("sun.java2d.renderer.blockSize_log2 = " 1029 + MarlinConst.BLOCK_SIZE_LG); 1030 1031 // RLE / blockFlags settings 1032 1033 logInfo("sun.java2d.renderer.forceRLE = " 1034 + MarlinProperties.isForceRLE()); 1035 logInfo("sun.java2d.renderer.forceNoRLE = " 1036 + MarlinProperties.isForceNoRLE()); 1037 logInfo("sun.java2d.renderer.useTileFlags = " 1038 + MarlinProperties.isUseTileFlags()); 1039 logInfo("sun.java2d.renderer.useTileFlags.useHeuristics = " 1040 + MarlinProperties.isUseTileFlagsWithHeuristics()); 1041 logInfo("sun.java2d.renderer.rleMinWidth = " 1042 + MarlinCache.RLE_MIN_WIDTH); 1043 1044 // optimisation parameters 1045 logInfo("sun.java2d.renderer.useSimplifier = " 1046 + MarlinConst.USE_SIMPLIFIER); 1047 1048 // debugging parameters 1049 logInfo("sun.java2d.renderer.doStats = " 1050 + MarlinConst.DO_STATS); 1051 logInfo("sun.java2d.renderer.doMonitors = " 1052 + MarlinConst.DO_MONITORS); 1053 logInfo("sun.java2d.renderer.doChecks = " 1054 + MarlinConst.DO_CHECKS); 1055 1056 // logging parameters 1057 logInfo("sun.java2d.renderer.useLogger = " 1058 + MarlinConst.USE_LOGGER); 1059 logInfo("sun.java2d.renderer.logCreateContext = " 1060 + MarlinConst.LOG_CREATE_CONTEXT); 1061 logInfo("sun.java2d.renderer.logUnsafeMalloc = " 1062 + MarlinConst.LOG_UNSAFE_MALLOC); 1063 1064 // quality settings 1065 logInfo("sun.java2d.renderer.cubic_dec_d2 = " 1066 + MarlinProperties.getCubicDecD2()); | 67 }, 68 OFF{ 69 @Override 70 PathIterator getNormalizingPathIterator(final DRendererContext rdrCtx, 71 final PathIterator src) 72 { 73 // return original path iterator if normalization is disabled: 74 return src; 75 } 76 }; 77 78 abstract PathIterator getNormalizingPathIterator(DRendererContext rdrCtx, 79 PathIterator src); 80 } 81 82 private static final float MIN_PEN_SIZE = 1.0f / NORM_SUBPIXELS; 83 84 static final double UPPER_BND = Float.MAX_VALUE / 2.0d; 85 static final double LOWER_BND = -UPPER_BND; 86 87 static final boolean DO_CLIP = MarlinProperties.isDoClip(); 88 static final boolean DO_TRACE = false; 89 90 /** 91 * Public constructor 92 */ 93 public DMarlinRenderingEngine() { 94 super(); 95 logSettings(DMarlinRenderingEngine.class.getName()); 96 } 97 98 /** 99 * Create a widened path as specified by the parameters. 100 * <p> 101 * The specified {@code src} {@link Shape} is widened according 102 * to the specified attribute parameters as per the 103 * {@link BasicStroke} specification. 104 * 105 * @param src the source path to be widened 106 * @param width the width of the widened path as per {@code BasicStroke} 107 * @param caps the end cap decorations as per {@code BasicStroke} 108 * @param join the segment join decorations as per {@code BasicStroke} 109 * @param miterlimit the miter limit as per {@code BasicStroke} 310 float dashphase, 311 DPathConsumer2D pc2d) 312 { 313 // We use strokerat so that in Stroker and Dasher we can work only 314 // with the pre-transformation coordinates. This will repeat a lot of 315 // computations done in the path iterator, but the alternative is to 316 // work with transformed paths and compute untransformed coordinates 317 // as needed. This would be faster but I do not think the complexity 318 // of working with both untransformed and transformed coordinates in 319 // the same code is worth it. 320 // However, if a path's width is constant after a transformation, 321 // we can skip all this untransforming. 322 323 // As pathTo() will check transformed coordinates for invalid values 324 // (NaN / Infinity) to ignore such points, it is necessary to apply the 325 // transformation before the path processing. 326 AffineTransform strokerat = null; 327 328 int dashLen = -1; 329 boolean recycleDashes = false; 330 double scale = 1.0d; 331 double[] dashesD = null; 332 333 // Ensure converting dashes to double precision: 334 if (dashes != null) { 335 recycleDashes = true; 336 dashLen = dashes.length; 337 dashesD = rdrCtx.dasher.copyDashArray(dashes); 338 } 339 340 if (at != null && !at.isIdentity()) { 341 final double a = at.getScaleX(); 342 final double b = at.getShearX(); 343 final double c = at.getShearY(); 344 final double d = at.getScaleY(); 345 final double det = a * d - c * b; 346 347 if (Math.abs(det) <= (2.0d * Double.MIN_VALUE)) { 348 // this rendering engine takes one dimensional curves and turns 349 // them into 2D shapes by giving them width. 350 // However, if everything is to be passed through a singular 351 // transformation, these 2D shapes will be squashed down to 1D 352 // again so, nothing can be drawn. 353 354 // Every path needs an initial moveTo and a pathDone. If these 355 // are not there this causes a SIGSEGV in libawt.so (at the time 356 // of writing of this comment (September 16, 2010)). Actually, 357 // I am not sure if the moveTo is necessary to avoid the SIGSEGV 358 // but the pathDone is definitely needed. 359 pc2d.moveTo(0.0d, 0.0d); 360 pc2d.pathDone(); 361 return; 362 } 363 364 // If the transform is a constant multiple of an orthogonal transformation 365 // then every length is just multiplied by a constant, so we just 366 // need to transform input paths to stroker and tell stroker 367 // the scaled width. This condition is satisfied if 368 // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we 369 // leave a bit of room for error. 370 if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) { 371 scale = Math.sqrt(a*a + c*c); 372 373 if (dashesD != null) { 374 for (int i = 0; i < dashLen; i++) { 375 dashesD[i] *= scale; 376 } 377 dashphase *= scale; 378 } 379 width *= scale; 380 381 // by now strokerat == null. Input paths to 382 // stroker (and maybe dasher) will have the full transform at 383 // applied to them and nothing will happen to the output paths. 384 } else { 385 strokerat = at; 386 387 // by now strokerat == at. Input paths to 388 // stroker (and maybe dasher) will have the full transform at 389 // applied to them, then they will be normalized, and then 390 // the inverse of *only the non translation part of at* will 391 // be applied to the normalized paths. This won't cause problems 392 // in stroker, because, suppose at = T*A, where T is just the 393 // translation part of at, and A is the rest. T*A has already 394 // been applied to Stroker/Dasher's input. Then Ainv will be 395 // applied. Ainv*T*A is not equal to T, but it is a translation, 396 // which means that none of stroker's assumptions about its 397 // input will be violated. After all this, A will be applied 398 // to stroker's output. 399 } 400 } else { 401 // either at is null or it's the identity. In either case 402 // we don't transform the path. 403 at = null; 404 } 405 406 final DTransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; 407 408 if (DO_TRACE) { 409 // trace Stroker: 410 pc2d = transformerPC2D.traceStroker(pc2d); 411 } 412 413 if (USE_SIMPLIFIER) { 414 // Use simplifier after stroker before Renderer 415 // to remove collinear segments (notably due to cap square) 416 pc2d = rdrCtx.simplifier.init(pc2d); 417 } 418 419 // deltaTransformConsumer may adjust the clip rectangle: 420 pc2d = transformerPC2D.deltaTransformConsumer(pc2d, strokerat); 421 422 // stroker will adjust the clip rectangle (width / miter limit): 423 pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit, scale); 424 425 if (dashesD != null) { 426 pc2d = rdrCtx.dasher.init(pc2d, dashesD, dashLen, dashphase, 427 recycleDashes); 428 } else if (rdrCtx.doClip && (caps != Stroker.CAP_BUTT)) { 429 if (DO_TRACE) { 430 pc2d = transformerPC2D.traceClosedPathDetector(pc2d); 431 } 432 433 // If no dash and clip is enabled: 434 // detect closedPaths (polygons) for caps 435 pc2d = transformerPC2D.detectClosedPath(pc2d); 436 } 437 pc2d = transformerPC2D.inverseDeltaTransformConsumer(pc2d, strokerat); 438 439 if (DO_TRACE) { 440 // trace Input: 441 pc2d = transformerPC2D.traceInput(pc2d); 442 } 443 444 final PathIterator pi = norm.getNormalizingPathIterator(rdrCtx, 445 src.getPathIterator(at)); 446 447 pathTo(rdrCtx, pi, pc2d); 448 449 /* 450 * Pipeline seems to be: 451 * shape.getPathIterator(at) 452 * -> (NormalizingPathIterator) 453 * -> (inverseDeltaTransformConsumer) 454 * -> (Dasher) 455 * -> Stroker 456 * -> (deltaTransformConsumer) 457 * 458 * -> (CollinearSimplifier) to remove redundant segments 459 * 460 * -> pc2d = Renderer (bounding box) 461 */ 462 } 463 813 final NormMode norm = (normalize) ? NormMode.ON_WITH_AA : NormMode.OFF; 814 815 if (bs == null) { 816 // fill shape: 817 final PathIterator pi = norm.getNormalizingPathIterator(rdrCtx, 818 s.getPathIterator(_at)); 819 820 // note: Winding rule may be EvenOdd ONLY for fill operations ! 821 r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(), 822 clip.getWidth(), clip.getHeight(), 823 pi.getWindingRule()); 824 825 // TODO: subdivide quad/cubic curves into monotonic curves ? 826 pathTo(rdrCtx, pi, r); 827 } else { 828 // draw shape with given stroke: 829 r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(), 830 clip.getWidth(), clip.getHeight(), 831 PathIterator.WIND_NON_ZERO); 832 833 if (DO_CLIP) { 834 // Define the initial clip bounds: 835 final double[] clipRect = rdrCtx.clipRect; 836 clipRect[0] = clip.getLoY(); 837 clipRect[1] = clip.getLoY() + clip.getHeight(); 838 clipRect[2] = clip.getLoX(); 839 clipRect[3] = clip.getLoX() + clip.getWidth(); 840 841 // Enable clipping: 842 rdrCtx.doClip = true; 843 } 844 845 strokeTo(rdrCtx, s, _at, bs, thin, norm, true, r); 846 } 847 if (r.endRendering()) { 848 ptg = rdrCtx.ptg.init(); 849 ptg.getBbox(bbox); 850 // note: do not returnRendererContext(rdrCtx) 851 // as it will be called later by MarlinTileGenerator.dispose() 852 r = null; 853 } 854 } finally { 855 if (r != null) { 856 // dispose renderer and recycle the RendererContext instance: 857 r.dispose(); 858 } 859 } 860 861 // Return null to cancel AA tile generation (nothing to render) 862 return ptg; 863 } 864 882 x -= (ldx1 + ldx2) / 2.0d; 883 y -= (ldy1 + ldy2) / 2.0d; 884 dx1 += ldx1; 885 dy1 += ldy1; 886 dx2 += ldx2; 887 dy2 += ldy2; 888 if (lw1 > 1.0d && lw2 > 1.0d) { 889 // Inner parallelogram was entirely consumed by stroke... 890 innerpgram = false; 891 } 892 } else { 893 ldx1 = ldy1 = ldx2 = ldy2 = 0.0d; 894 } 895 896 MarlinTileGenerator ptg = null; 897 DRenderer r = null; 898 899 final DRendererContext rdrCtx = getRendererContext(); 900 try { 901 r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(), 902 clip.getWidth(), clip.getHeight(), 903 DRenderer.WIND_EVEN_ODD); 904 905 r.moveTo( x, y); 906 r.lineTo( (x+dx1), (y+dy1)); 907 r.lineTo( (x+dx1+dx2), (y+dy1+dy2)); 908 r.lineTo( (x+dx2), (y+dy2)); 909 r.closePath(); 910 911 if (innerpgram) { 912 x += ldx1 + ldx2; 913 y += ldy1 + ldy2; 914 dx1 -= 2.0d * ldx1; 915 dy1 -= 2.0d * ldy1; 916 dx2 -= 2.0d * ldx2; 917 dy2 -= 2.0d * ldy2; 918 r.moveTo( x, y); 919 r.lineTo( (x+dx1), (y+dy1)); 920 r.lineTo( (x+dx1+dx2), (y+dy1+dy2)); 921 r.lineTo( (x+dx2), (y+dy2)); 922 r.closePath(); 923 } 1064 + MarlinConst.TILE_W_LG); 1065 logInfo("sun.java2d.renderer.blockSize_log2 = " 1066 + MarlinConst.BLOCK_SIZE_LG); 1067 1068 // RLE / blockFlags settings 1069 1070 logInfo("sun.java2d.renderer.forceRLE = " 1071 + MarlinProperties.isForceRLE()); 1072 logInfo("sun.java2d.renderer.forceNoRLE = " 1073 + MarlinProperties.isForceNoRLE()); 1074 logInfo("sun.java2d.renderer.useTileFlags = " 1075 + MarlinProperties.isUseTileFlags()); 1076 logInfo("sun.java2d.renderer.useTileFlags.useHeuristics = " 1077 + MarlinProperties.isUseTileFlagsWithHeuristics()); 1078 logInfo("sun.java2d.renderer.rleMinWidth = " 1079 + MarlinCache.RLE_MIN_WIDTH); 1080 1081 // optimisation parameters 1082 logInfo("sun.java2d.renderer.useSimplifier = " 1083 + MarlinConst.USE_SIMPLIFIER); 1084 logInfo("sun.java2d.renderer.clip = " 1085 + MarlinProperties.isDoClip()); 1086 1087 // debugging parameters 1088 logInfo("sun.java2d.renderer.doStats = " 1089 + MarlinConst.DO_STATS); 1090 logInfo("sun.java2d.renderer.doMonitors = " 1091 + MarlinConst.DO_MONITORS); 1092 logInfo("sun.java2d.renderer.doChecks = " 1093 + MarlinConst.DO_CHECKS); 1094 1095 // logging parameters 1096 logInfo("sun.java2d.renderer.useLogger = " 1097 + MarlinConst.USE_LOGGER); 1098 logInfo("sun.java2d.renderer.logCreateContext = " 1099 + MarlinConst.LOG_CREATE_CONTEXT); 1100 logInfo("sun.java2d.renderer.logUnsafeMalloc = " 1101 + MarlinConst.LOG_UNSAFE_MALLOC); 1102 1103 // quality settings 1104 logInfo("sun.java2d.renderer.cubic_dec_d2 = " 1105 + MarlinProperties.getCubicDecD2()); |