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 {
51 ON_WITH_AA {
52 @Override
53 PathIterator getNormalizingPathIterator(final RendererContext rdrCtx,
54 final PathIterator src)
55 {
56 // NormalizingPathIterator NearestPixelCenter:
57 return rdrCtx.nPCPathIterator.init(src);
58 }
59 },
60 ON_NO_AA{
61 @Override
62 PathIterator getNormalizingPathIterator(final RendererContext rdrCtx,
63 final PathIterator src)
64 {
65 // NearestPixel NormalizingPathIterator:
66 return rdrCtx.nPQPathIterator.init(src);
67 }
68 },
69 OFF{
70 @Override
71 PathIterator getNormalizingPathIterator(final RendererContext rdrCtx,
72 final PathIterator src)
73 {
74 // return original path iterator if normalization is disabled:
75 return src;
76 }
77 };
78
79 abstract PathIterator getNormalizingPathIterator(RendererContext rdrCtx,
80 PathIterator src);
81 }
82
83 private static final float MIN_PEN_SIZE = 1f / NORM_SUBPIXELS;
84
85 static final float UPPER_BND = Float.MAX_VALUE / 2.0f;
86 static final float LOWER_BND = -UPPER_BND;
87
88 /**
89 * Public constructor
90 */
91 public MarlinRenderingEngine() {
92 super();
93 logSettings(MarlinRenderingEngine.class.getName());
94 }
95
96 /**
97 * Create a widened path as specified by the parameters.
98 * <p>
99 * The specified {@code src} {@link Shape} is widened according
100 * to the specified attribute parameters as per the
101 * {@link BasicStroke} specification.
102 *
103 * @param src the source path to be widened
242 double C = at.getShearX(); // m01
243 double B = at.getShearY(); // m10
244 double D = at.getScaleY(); // m11
245
246 /*
247 * Given a 2 x 2 affine matrix [ A B ] such that
248 * [ C D ]
249 * v' = [x' y'] = [Ax + Cy, Bx + Dy], we want to
250 * find the maximum magnitude (norm) of the vector v'
251 * with the constraint (x^2 + y^2 = 1).
252 * The equation to maximize is
253 * |v'| = sqrt((Ax+Cy)^2+(Bx+Dy)^2)
254 * or |v'| = sqrt((AA+BB)x^2 + 2(AC+BD)xy + (CC+DD)y^2).
255 * Since sqrt is monotonic we can maximize |v'|^2
256 * instead and plug in the substitution y = sqrt(1 - x^2).
257 * Trigonometric equalities can then be used to get
258 * rid of most of the sqrt terms.
259 */
260
261 double EA = A*A + B*B; // x^2 coefficient
262 double EB = 2.0*(A*C + B*D); // xy coefficient
263 double EC = C*C + D*D; // y^2 coefficient
264
265 /*
266 * There is a lot of calculus omitted here.
267 *
268 * Conceptually, in the interests of understanding the
269 * terms that the calculus produced we can consider
270 * that EA and EC end up providing the lengths along
271 * the major axes and the hypot term ends up being an
272 * adjustment for the additional length along the off-axis
273 * angle of rotated or sheared ellipses as well as an
274 * adjustment for the fact that the equation below
275 * averages the two major axis lengths. (Notice that
276 * the hypot term contains a part which resolves to the
277 * difference of these two axis lengths in the absence
278 * of rotation.)
279 *
280 * In the calculus, the ratio of the EB and (EA-EC) terms
281 * ends up being the tangent of 2*theta where theta is
282 * the angle that the long axis of the ellipse makes
283 * with the horizontal axis. Thus, this equation is
284 * calculating the length of the hypotenuse of a triangle
285 * along that axis.
286 */
287
288 double hypot = Math.sqrt(EB*EB + (EA-EC)*(EA-EC));
289 // sqrt omitted, compare to squared limits below.
290 double widthsquared = ((EA + EC + hypot)/2.0);
291
292 widthScale = (float)Math.sqrt(widthsquared);
293 }
294
295 return (lw / widthScale);
296 }
297
298 final void strokeTo(final RendererContext rdrCtx,
299 Shape src,
300 AffineTransform at,
301 float width,
302 NormMode norm,
303 int caps,
304 int join,
305 float miterlimit,
306 float[] dashes,
307 float dashphase,
308 PathConsumer2D pc2d)
309 {
310 // We use strokerat so that in Stroker and Dasher we can work only
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
328 if (at != null && !at.isIdentity()) {
329 final double a = at.getScaleX();
330 final double b = at.getShearX();
331 final double c = at.getShearY();
332 final double d = at.getScaleY();
333 final double det = a * d - c * b;
334
335 if (Math.abs(det) <= (2f * Float.MIN_VALUE)) {
336 // this rendering engine takes one dimensional curves and turns
337 // them into 2D shapes by giving them width.
338 // However, if everything is to be passed through a singular
339 // transformation, these 2D shapes will be squashed down to 1D
340 // again so, nothing can be drawn.
341
342 // Every path needs an initial moveTo and a pathDone. If these
343 // are not there this causes a SIGSEGV in libawt.so (at the time
344 // of writing of this comment (September 16, 2010)). Actually,
345 // I am not sure if the moveTo is necessary to avoid the SIGSEGV
346 // but the pathDone is definitely needed.
347 pc2d.moveTo(0f, 0f);
348 pc2d.pathDone();
349 return;
350 }
351
352 // If the transform is a constant multiple of an orthogonal transformation
353 // then every length is just multiplied by a constant, so we just
354 // need to transform input paths to stroker and tell stroker
355 // the scaled width. This condition is satisfied if
356 // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we
357 // leave a bit of room for error.
358 if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) {
359 final float scale = (float) Math.sqrt(a*a + c*c);
360
361 if (dashes != null) {
362 recycleDashes = true;
363 dashLen = dashes.length;
364 final float[] newDashes;
365 if (dashLen <= INITIAL_ARRAY) {
366 newDashes = rdrCtx.dasher.dashes_ref.initial;
367 } else {
368 if (DO_STATS) {
369 rdrCtx.stats.stat_array_dasher_dasher.add(dashLen);
370 }
371 newDashes = rdrCtx.dasher.dashes_ref.getArray(dashLen);
372 }
373 System.arraycopy(dashes, 0, newDashes, 0, dashLen);
374 dashes = newDashes;
375 for (int i = 0; i < dashLen; i++) {
376 dashes[i] *= scale;
377 }
378 dashphase *= scale;
379 }
380 width *= scale;
381
382 // by now strokerat == null. Input paths to
383 // stroker (and maybe dasher) will have the full transform at
384 // applied to them and nothing will happen to the output paths.
385 } else {
386 strokerat = at;
387
388 // by now strokerat == at. Input paths to
389 // stroker (and maybe dasher) will have the full transform at
390 // applied to them, then they will be normalized, and then
391 // the inverse of *only the non translation part of at* will
392 // be applied to the normalized paths. This won't cause problems
393 // in stroker, because, suppose at = T*A, where T is just the
394 // translation part of at, and A is the rest. T*A has already
428 src.getPathIterator(at));
429
430 pathTo(rdrCtx, pi, pc2d);
431
432 /*
433 * Pipeline seems to be:
434 * shape.getPathIterator(at)
435 * -> (NormalizingPathIterator)
436 * -> (inverseDeltaTransformConsumer)
437 * -> (Dasher)
438 * -> Stroker
439 * -> (deltaTransformConsumer)
440 *
441 * -> (CollinearSimplifier) to remove redundant segments
442 *
443 * -> pc2d = Renderer (bounding box)
444 */
445 }
446
447 private static boolean nearZero(final double num) {
448 return Math.abs(num) < 2.0 * Math.ulp(num);
449 }
450
451 abstract static class NormalizingPathIterator implements PathIterator {
452
453 private PathIterator src;
454
455 // the adjustment applied to the current position.
456 private float curx_adjust, cury_adjust;
457 // the adjustment applied to the last moveTo position.
458 private float movx_adjust, movy_adjust;
459
460 private final float[] tmp;
461
462 NormalizingPathIterator(final float[] tmp) {
463 this.tmp = tmp;
464 }
465
466 final NormalizingPathIterator init(final PathIterator src) {
467 this.src = src;
468 return this; // fluent API
507
508 coord = coords[lastCoord];
509 x_adjust = normCoord(coord); // new coord
510 coords[lastCoord] = x_adjust;
511 x_adjust -= coord;
512
513 coord = coords[lastCoord + 1];
514 y_adjust = normCoord(coord); // new coord
515 coords[lastCoord + 1] = y_adjust;
516 y_adjust -= coord;
517
518 // now that the end points are done, normalize the control points
519 switch(type) {
520 case PathIterator.SEG_MOVETO:
521 movx_adjust = x_adjust;
522 movy_adjust = y_adjust;
523 break;
524 case PathIterator.SEG_LINETO:
525 break;
526 case PathIterator.SEG_QUADTO:
527 coords[0] += (curx_adjust + x_adjust) / 2f;
528 coords[1] += (cury_adjust + y_adjust) / 2f;
529 break;
530 case PathIterator.SEG_CUBICTO:
531 coords[0] += curx_adjust;
532 coords[1] += cury_adjust;
533 coords[2] += x_adjust;
534 coords[3] += y_adjust;
535 break;
536 case PathIterator.SEG_CLOSE:
537 // handled earlier
538 default:
539 }
540 curx_adjust = x_adjust;
541 cury_adjust = y_adjust;
542 return type;
543 }
544
545 abstract float normCoord(final float coord);
546
547 @Override
548 public final int currentSegment(final double[] coords) {
807
808 // TODO: subdivide quad/cubic curves into monotonic curves ?
809 pathTo(rdrCtx, pi, r);
810 } else {
811 // draw shape with given stroke:
812 r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),
813 clip.getWidth(), clip.getHeight(),
814 PathIterator.WIND_NON_ZERO);
815
816 strokeTo(rdrCtx, s, _at, bs, thin, norm, true, r);
817 }
818 if (r.endRendering()) {
819 ptg = rdrCtx.ptg.init();
820 ptg.getBbox(bbox);
821 // note: do not returnRendererContext(rdrCtx)
822 // as it will be called later by MarlinTileGenerator.dispose()
823 r = null;
824 }
825 } finally {
826 if (r != null) {
827 // dispose renderer:
828 r.dispose();
829 // recycle the RendererContext instance
830 MarlinRenderingEngine.returnRendererContext(rdrCtx);
831 }
832 }
833
834 // Return null to cancel AA tile generation (nothing to render)
835 return ptg;
836 }
837
838 @Override
839 public final AATileGenerator getAATileGenerator(double x, double y,
840 double dx1, double dy1,
841 double dx2, double dy2,
842 double lw1, double lw2,
843 Region clip,
844 int[] bbox)
845 {
846 // REMIND: Deal with large coordinates!
847 double ldx1, ldy1, ldx2, ldy2;
848 boolean innerpgram = (lw1 > 0.0 && lw2 > 0.0);
849
850 if (innerpgram) {
851 ldx1 = dx1 * lw1;
852 ldy1 = dy1 * lw1;
853 ldx2 = dx2 * lw2;
854 ldy2 = dy2 * lw2;
855 x -= (ldx1 + ldx2) / 2.0;
856 y -= (ldy1 + ldy2) / 2.0;
857 dx1 += ldx1;
858 dy1 += ldy1;
859 dx2 += ldx2;
860 dy2 += ldy2;
861 if (lw1 > 1.0 && lw2 > 1.0) {
862 // Inner parallelogram was entirely consumed by stroke...
863 innerpgram = false;
864 }
865 } else {
866 ldx1 = ldy1 = ldx2 = ldy2 = 0.0;
867 }
868
869 MarlinTileGenerator ptg = null;
870 Renderer r = null;
871
872 final RendererContext rdrCtx = getRendererContext();
873 try {
874 r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),
875 clip.getWidth(), clip.getHeight(),
876 Renderer.WIND_EVEN_ODD);
877
878 r.moveTo((float) x, (float) y);
879 r.lineTo((float) (x+dx1), (float) (y+dy1));
880 r.lineTo((float) (x+dx1+dx2), (float) (y+dy1+dy2));
881 r.lineTo((float) (x+dx2), (float) (y+dy2));
882 r.closePath();
883
884 if (innerpgram) {
885 x += ldx1 + ldx2;
886 y += ldy1 + ldy2;
887 dx1 -= 2.0 * ldx1;
888 dy1 -= 2.0 * ldy1;
889 dx2 -= 2.0 * ldx2;
890 dy2 -= 2.0 * ldy2;
891 r.moveTo((float) x, (float) y);
892 r.lineTo((float) (x+dx1), (float) (y+dy1));
893 r.lineTo((float) (x+dx1+dx2), (float) (y+dy1+dy2));
894 r.lineTo((float) (x+dx2), (float) (y+dy2));
895 r.closePath();
896 }
897 r.pathDone();
898
899 if (r.endRendering()) {
900 ptg = rdrCtx.ptg.init();
901 ptg.getBbox(bbox);
902 // note: do not returnRendererContext(rdrCtx)
903 // as it will be called later by MarlinTileGenerator.dispose()
904 r = null;
905 }
906 } finally {
907 if (r != null) {
908 // dispose renderer:
909 r.dispose();
910 // recycle the RendererContext instance
911 MarlinRenderingEngine.returnRendererContext(rdrCtx);
912 }
913 }
914
915 // Return null to cancel AA tile generation (nothing to render)
916 return ptg;
917 }
918
919 /**
920 * Returns the minimum pen width that the antialiasing rasterizer
921 * can represent without dropouts occuring.
922 * @since 1.7
923 */
924 @Override
925 public float getMinimumAAPenSize() {
926 return MIN_PEN_SIZE;
927 }
928
929 static {
930 if (PathIterator.WIND_NON_ZERO != Renderer.WIND_NON_ZERO ||
931 PathIterator.WIND_EVEN_ODD != Renderer.WIND_EVEN_ODD ||
1018
1019 logInfo("Marlin software rasterizer = ENABLED");
1020 logInfo("Version = ["
1021 + Version.getVersion() + "]");
1022 logInfo("sun.java2d.renderer = "
1023 + reClass);
1024 logInfo("sun.java2d.renderer.useThreadLocal = "
1025 + USE_THREAD_LOCAL);
1026 logInfo("sun.java2d.renderer.useRef = "
1027 + refType);
1028
1029 logInfo("sun.java2d.renderer.edges = "
1030 + MarlinConst.INITIAL_EDGES_COUNT);
1031 logInfo("sun.java2d.renderer.pixelsize = "
1032 + MarlinConst.INITIAL_PIXEL_DIM);
1033
1034 logInfo("sun.java2d.renderer.subPixel_log2_X = "
1035 + MarlinConst.SUBPIXEL_LG_POSITIONS_X);
1036 logInfo("sun.java2d.renderer.subPixel_log2_Y = "
1037 + MarlinConst.SUBPIXEL_LG_POSITIONS_Y);
1038 logInfo("sun.java2d.renderer.tileSize_log2 = "
1039 + MarlinConst.TILE_SIZE_LG);
1040
1041 logInfo("sun.java2d.renderer.blockSize_log2 = "
1042 + MarlinConst.BLOCK_SIZE_LG);
1043
1044 logInfo("sun.java2d.renderer.blockSize_log2 = "
1045 + MarlinConst.BLOCK_SIZE_LG);
1046
1047 // RLE / blockFlags settings
1048
1049 logInfo("sun.java2d.renderer.forceRLE = "
1050 + MarlinProperties.isForceRLE());
1051 logInfo("sun.java2d.renderer.forceNoRLE = "
1052 + MarlinProperties.isForceNoRLE());
1053 logInfo("sun.java2d.renderer.useTileFlags = "
1054 + MarlinProperties.isUseTileFlags());
1055 logInfo("sun.java2d.renderer.useTileFlags.useHeuristics = "
1056 + MarlinProperties.isUseTileFlagsWithHeuristics());
1057 logInfo("sun.java2d.renderer.rleMinWidth = "
1058 + MarlinCache.RLE_MIN_WIDTH);
1059
1060 // optimisation parameters
1061 logInfo("sun.java2d.renderer.useSimplifier = "
1062 + MarlinConst.USE_SIMPLIFIER);
1063
1064 // debugging parameters
1065 logInfo("sun.java2d.renderer.doStats = "
1066 + MarlinConst.DO_STATS);
1067 logInfo("sun.java2d.renderer.doMonitors = "
1068 + MarlinConst.DO_MONITORS);
1069 logInfo("sun.java2d.renderer.doChecks = "
1070 + MarlinConst.DO_CHECKS);
1071
1072 // logging parameters
1073 logInfo("sun.java2d.renderer.useLogger = "
1074 + MarlinConst.USE_LOGGER);
1075 logInfo("sun.java2d.renderer.logCreateContext = "
1076 + MarlinConst.LOG_CREATE_CONTEXT);
1077 logInfo("sun.java2d.renderer.logUnsafeMalloc = "
1078 + MarlinConst.LOG_UNSAFE_MALLOC);
1079
1080 // quality settings
1081 logInfo("Renderer settings:");
1082 logInfo("CUB_COUNT_LG = " + Renderer.CUB_COUNT_LG);
1083 logInfo("CUB_DEC_BND = " + Renderer.CUB_DEC_BND);
1084 logInfo("CUB_INC_BND = " + Renderer.CUB_INC_BND);
1085 logInfo("QUAD_DEC_BND = " + Renderer.QUAD_DEC_BND);
1086
1087 logInfo("INITIAL_EDGES_CAPACITY = "
1088 + MarlinConst.INITIAL_EDGES_CAPACITY);
1089 logInfo("INITIAL_CROSSING_COUNT = "
1090 + Renderer.INITIAL_CROSSING_COUNT);
1091
1092 logInfo("=========================================================="
1093 + "=====================");
1094 }
1095
1096 /**
1097 * Get the RendererContext instance dedicated to the current thread
1098 * @return RendererContext instance
1099 */
1100 @SuppressWarnings({"unchecked"})
1101 static RendererContext getRendererContext() {
1102 final RendererContext rdrCtx = RDR_CTX_PROVIDER.acquire();
|
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 final class MarlinRenderingEngine extends RenderingEngine
48 implements MarlinConst
49 {
50 private static enum NormMode {
51 ON_WITH_AA {
52 @Override
53 PathIterator getNormalizingPathIterator(final RendererContext rdrCtx,
54 final PathIterator src)
55 {
56 // NormalizingPathIterator NearestPixelCenter:
57 return rdrCtx.nPCPathIterator.init(src);
58 }
59 },
60 ON_NO_AA{
61 @Override
62 PathIterator getNormalizingPathIterator(final RendererContext rdrCtx,
63 final PathIterator src)
64 {
65 // NearestPixel NormalizingPathIterator:
66 return rdrCtx.nPQPathIterator.init(src);
67 }
68 },
69 OFF{
70 @Override
71 PathIterator getNormalizingPathIterator(final RendererContext rdrCtx,
72 final PathIterator src)
73 {
74 // return original path iterator if normalization is disabled:
75 return src;
76 }
77 };
78
79 abstract PathIterator getNormalizingPathIterator(RendererContext rdrCtx,
80 PathIterator src);
81 }
82
83 private static final float MIN_PEN_SIZE = 1.0f / NORM_SUBPIXELS;
84
85 static final float UPPER_BND = Float.MAX_VALUE / 2.0f;
86 static final float LOWER_BND = -UPPER_BND;
87
88 /**
89 * Public constructor
90 */
91 public MarlinRenderingEngine() {
92 super();
93 logSettings(MarlinRenderingEngine.class.getName());
94 }
95
96 /**
97 * Create a widened path as specified by the parameters.
98 * <p>
99 * The specified {@code src} {@link Shape} is widened according
100 * to the specified attribute parameters as per the
101 * {@link BasicStroke} specification.
102 *
103 * @param src the source path to be widened
242 double C = at.getShearX(); // m01
243 double B = at.getShearY(); // m10
244 double D = at.getScaleY(); // m11
245
246 /*
247 * Given a 2 x 2 affine matrix [ A B ] such that
248 * [ C D ]
249 * v' = [x' y'] = [Ax + Cy, Bx + Dy], we want to
250 * find the maximum magnitude (norm) of the vector v'
251 * with the constraint (x^2 + y^2 = 1).
252 * The equation to maximize is
253 * |v'| = sqrt((Ax+Cy)^2+(Bx+Dy)^2)
254 * or |v'| = sqrt((AA+BB)x^2 + 2(AC+BD)xy + (CC+DD)y^2).
255 * Since sqrt is monotonic we can maximize |v'|^2
256 * instead and plug in the substitution y = sqrt(1 - x^2).
257 * Trigonometric equalities can then be used to get
258 * rid of most of the sqrt terms.
259 */
260
261 double EA = A*A + B*B; // x^2 coefficient
262 double EB = 2.0d * (A*C + B*D); // xy coefficient
263 double EC = C*C + D*D; // y^2 coefficient
264
265 /*
266 * There is a lot of calculus omitted here.
267 *
268 * Conceptually, in the interests of understanding the
269 * terms that the calculus produced we can consider
270 * that EA and EC end up providing the lengths along
271 * the major axes and the hypot term ends up being an
272 * adjustment for the additional length along the off-axis
273 * angle of rotated or sheared ellipses as well as an
274 * adjustment for the fact that the equation below
275 * averages the two major axis lengths. (Notice that
276 * the hypot term contains a part which resolves to the
277 * difference of these two axis lengths in the absence
278 * of rotation.)
279 *
280 * In the calculus, the ratio of the EB and (EA-EC) terms
281 * ends up being the tangent of 2*theta where theta is
282 * the angle that the long axis of the ellipse makes
283 * with the horizontal axis. Thus, this equation is
284 * calculating the length of the hypotenuse of a triangle
285 * along that axis.
286 */
287
288 double hypot = Math.sqrt(EB*EB + (EA-EC)*(EA-EC));
289 // sqrt omitted, compare to squared limits below.
290 double widthsquared = ((EA + EC + hypot) / 2.0d);
291
292 widthScale = (float)Math.sqrt(widthsquared);
293 }
294
295 return (lw / widthScale);
296 }
297
298 final void strokeTo(final RendererContext rdrCtx,
299 Shape src,
300 AffineTransform at,
301 float width,
302 NormMode norm,
303 int caps,
304 int join,
305 float miterlimit,
306 float[] dashes,
307 float dashphase,
308 PathConsumer2D pc2d)
309 {
310 // We use strokerat so that in Stroker and Dasher we can work only
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
328 if (at != null && !at.isIdentity()) {
329 final double a = at.getScaleX();
330 final double b = at.getShearX();
331 final double c = at.getShearY();
332 final double d = at.getScaleY();
333 final double det = a * d - c * b;
334
335 if (Math.abs(det) <= (2.0f * Float.MIN_VALUE)) {
336 // this rendering engine takes one dimensional curves and turns
337 // them into 2D shapes by giving them width.
338 // However, if everything is to be passed through a singular
339 // transformation, these 2D shapes will be squashed down to 1D
340 // again so, nothing can be drawn.
341
342 // Every path needs an initial moveTo and a pathDone. If these
343 // are not there this causes a SIGSEGV in libawt.so (at the time
344 // of writing of this comment (September 16, 2010)). Actually,
345 // I am not sure if the moveTo is necessary to avoid the SIGSEGV
346 // but the pathDone is definitely needed.
347 pc2d.moveTo(0.0f, 0.0f);
348 pc2d.pathDone();
349 return;
350 }
351
352 // If the transform is a constant multiple of an orthogonal transformation
353 // then every length is just multiplied by a constant, so we just
354 // need to transform input paths to stroker and tell stroker
355 // the scaled width. This condition is satisfied if
356 // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we
357 // leave a bit of room for error.
358 if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) {
359 final float scale = (float) Math.sqrt(a*a + c*c);
360
361 if (dashes != null) {
362 recycleDashes = true;
363 dashLen = dashes.length;
364 dashes = rdrCtx.dasher.copyDashArray(dashes);
365 for (int i = 0; i < dashLen; i++) {
366 dashes[i] *= scale;
367 }
368 dashphase *= scale;
369 }
370 width *= scale;
371
372 // by now strokerat == null. Input paths to
373 // stroker (and maybe dasher) will have the full transform at
374 // applied to them and nothing will happen to the output paths.
375 } else {
376 strokerat = at;
377
378 // by now strokerat == at. Input paths to
379 // stroker (and maybe dasher) will have the full transform at
380 // applied to them, then they will be normalized, and then
381 // the inverse of *only the non translation part of at* will
382 // be applied to the normalized paths. This won't cause problems
383 // in stroker, because, suppose at = T*A, where T is just the
384 // translation part of at, and A is the rest. T*A has already
418 src.getPathIterator(at));
419
420 pathTo(rdrCtx, pi, pc2d);
421
422 /*
423 * Pipeline seems to be:
424 * shape.getPathIterator(at)
425 * -> (NormalizingPathIterator)
426 * -> (inverseDeltaTransformConsumer)
427 * -> (Dasher)
428 * -> Stroker
429 * -> (deltaTransformConsumer)
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.0d * Math.ulp(num);
439 }
440
441 abstract static class NormalizingPathIterator implements PathIterator {
442
443 private PathIterator src;
444
445 // the adjustment applied to the current position.
446 private float curx_adjust, cury_adjust;
447 // the adjustment applied to the last moveTo position.
448 private float movx_adjust, movy_adjust;
449
450 private final float[] tmp;
451
452 NormalizingPathIterator(final float[] tmp) {
453 this.tmp = tmp;
454 }
455
456 final NormalizingPathIterator init(final PathIterator src) {
457 this.src = src;
458 return this; // fluent API
497
498 coord = coords[lastCoord];
499 x_adjust = normCoord(coord); // new coord
500 coords[lastCoord] = x_adjust;
501 x_adjust -= coord;
502
503 coord = coords[lastCoord + 1];
504 y_adjust = normCoord(coord); // new coord
505 coords[lastCoord + 1] = y_adjust;
506 y_adjust -= coord;
507
508 // now that the end points are done, normalize the control points
509 switch(type) {
510 case PathIterator.SEG_MOVETO:
511 movx_adjust = x_adjust;
512 movy_adjust = y_adjust;
513 break;
514 case PathIterator.SEG_LINETO:
515 break;
516 case PathIterator.SEG_QUADTO:
517 coords[0] += (curx_adjust + x_adjust) / 2.0f;
518 coords[1] += (cury_adjust + y_adjust) / 2.0f;
519 break;
520 case PathIterator.SEG_CUBICTO:
521 coords[0] += curx_adjust;
522 coords[1] += cury_adjust;
523 coords[2] += x_adjust;
524 coords[3] += y_adjust;
525 break;
526 case PathIterator.SEG_CLOSE:
527 // handled earlier
528 default:
529 }
530 curx_adjust = x_adjust;
531 cury_adjust = y_adjust;
532 return type;
533 }
534
535 abstract float normCoord(final float coord);
536
537 @Override
538 public final int currentSegment(final double[] coords) {
797
798 // TODO: subdivide quad/cubic curves into monotonic curves ?
799 pathTo(rdrCtx, pi, r);
800 } else {
801 // draw shape with given stroke:
802 r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),
803 clip.getWidth(), clip.getHeight(),
804 PathIterator.WIND_NON_ZERO);
805
806 strokeTo(rdrCtx, s, _at, bs, thin, norm, true, r);
807 }
808 if (r.endRendering()) {
809 ptg = rdrCtx.ptg.init();
810 ptg.getBbox(bbox);
811 // note: do not returnRendererContext(rdrCtx)
812 // as it will be called later by MarlinTileGenerator.dispose()
813 r = null;
814 }
815 } finally {
816 if (r != null) {
817 // dispose renderer and recycle the RendererContext instance:
818 r.dispose();
819 }
820 }
821
822 // Return null to cancel AA tile generation (nothing to render)
823 return ptg;
824 }
825
826 @Override
827 public final AATileGenerator getAATileGenerator(double x, double y,
828 double dx1, double dy1,
829 double dx2, double dy2,
830 double lw1, double lw2,
831 Region clip,
832 int[] bbox)
833 {
834 // REMIND: Deal with large coordinates!
835 double ldx1, ldy1, ldx2, ldy2;
836 boolean innerpgram = (lw1 > 0.0d && lw2 > 0.0d);
837
838 if (innerpgram) {
839 ldx1 = dx1 * lw1;
840 ldy1 = dy1 * lw1;
841 ldx2 = dx2 * lw2;
842 ldy2 = dy2 * lw2;
843 x -= (ldx1 + ldx2) / 2.0d;
844 y -= (ldy1 + ldy2) / 2.0d;
845 dx1 += ldx1;
846 dy1 += ldy1;
847 dx2 += ldx2;
848 dy2 += ldy2;
849 if (lw1 > 1.0d && lw2 > 1.0d) {
850 // Inner parallelogram was entirely consumed by stroke...
851 innerpgram = false;
852 }
853 } else {
854 ldx1 = ldy1 = ldx2 = ldy2 = 0.0d;
855 }
856
857 MarlinTileGenerator ptg = null;
858 Renderer r = null;
859
860 final RendererContext rdrCtx = getRendererContext();
861 try {
862 r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),
863 clip.getWidth(), clip.getHeight(),
864 Renderer.WIND_EVEN_ODD);
865
866 r.moveTo((float) x, (float) y);
867 r.lineTo((float) (x+dx1), (float) (y+dy1));
868 r.lineTo((float) (x+dx1+dx2), (float) (y+dy1+dy2));
869 r.lineTo((float) (x+dx2), (float) (y+dy2));
870 r.closePath();
871
872 if (innerpgram) {
873 x += ldx1 + ldx2;
874 y += ldy1 + ldy2;
875 dx1 -= 2.0d * ldx1;
876 dy1 -= 2.0d * ldy1;
877 dx2 -= 2.0d * ldx2;
878 dy2 -= 2.0d * ldy2;
879 r.moveTo((float) x, (float) y);
880 r.lineTo((float) (x+dx1), (float) (y+dy1));
881 r.lineTo((float) (x+dx1+dx2), (float) (y+dy1+dy2));
882 r.lineTo((float) (x+dx2), (float) (y+dy2));
883 r.closePath();
884 }
885 r.pathDone();
886
887 if (r.endRendering()) {
888 ptg = rdrCtx.ptg.init();
889 ptg.getBbox(bbox);
890 // note: do not returnRendererContext(rdrCtx)
891 // as it will be called later by MarlinTileGenerator.dispose()
892 r = null;
893 }
894 } finally {
895 if (r != null) {
896 // dispose renderer and recycle the RendererContext instance:
897 r.dispose();
898 }
899 }
900
901 // Return null to cancel AA tile generation (nothing to render)
902 return ptg;
903 }
904
905 /**
906 * Returns the minimum pen width that the antialiasing rasterizer
907 * can represent without dropouts occuring.
908 * @since 1.7
909 */
910 @Override
911 public float getMinimumAAPenSize() {
912 return MIN_PEN_SIZE;
913 }
914
915 static {
916 if (PathIterator.WIND_NON_ZERO != Renderer.WIND_NON_ZERO ||
917 PathIterator.WIND_EVEN_ODD != Renderer.WIND_EVEN_ODD ||
1004
1005 logInfo("Marlin software rasterizer = ENABLED");
1006 logInfo("Version = ["
1007 + Version.getVersion() + "]");
1008 logInfo("sun.java2d.renderer = "
1009 + reClass);
1010 logInfo("sun.java2d.renderer.useThreadLocal = "
1011 + USE_THREAD_LOCAL);
1012 logInfo("sun.java2d.renderer.useRef = "
1013 + refType);
1014
1015 logInfo("sun.java2d.renderer.edges = "
1016 + MarlinConst.INITIAL_EDGES_COUNT);
1017 logInfo("sun.java2d.renderer.pixelsize = "
1018 + MarlinConst.INITIAL_PIXEL_DIM);
1019
1020 logInfo("sun.java2d.renderer.subPixel_log2_X = "
1021 + MarlinConst.SUBPIXEL_LG_POSITIONS_X);
1022 logInfo("sun.java2d.renderer.subPixel_log2_Y = "
1023 + MarlinConst.SUBPIXEL_LG_POSITIONS_Y);
1024
1025 logInfo("sun.java2d.renderer.tileSize_log2 = "
1026 + MarlinConst.TILE_H_LG);
1027 logInfo("sun.java2d.renderer.tileWidth_log2 = "
1028 + MarlinConst.TILE_W_LG);
1029 logInfo("sun.java2d.renderer.blockSize_log2 = "
1030 + MarlinConst.BLOCK_SIZE_LG);
1031
1032 // RLE / blockFlags settings
1033
1034 logInfo("sun.java2d.renderer.forceRLE = "
1035 + MarlinProperties.isForceRLE());
1036 logInfo("sun.java2d.renderer.forceNoRLE = "
1037 + MarlinProperties.isForceNoRLE());
1038 logInfo("sun.java2d.renderer.useTileFlags = "
1039 + MarlinProperties.isUseTileFlags());
1040 logInfo("sun.java2d.renderer.useTileFlags.useHeuristics = "
1041 + MarlinProperties.isUseTileFlagsWithHeuristics());
1042 logInfo("sun.java2d.renderer.rleMinWidth = "
1043 + MarlinCache.RLE_MIN_WIDTH);
1044
1045 // optimisation parameters
1046 logInfo("sun.java2d.renderer.useSimplifier = "
1047 + MarlinConst.USE_SIMPLIFIER);
1048
1049 // debugging parameters
1050 logInfo("sun.java2d.renderer.doStats = "
1051 + MarlinConst.DO_STATS);
1052 logInfo("sun.java2d.renderer.doMonitors = "
1053 + MarlinConst.DO_MONITORS);
1054 logInfo("sun.java2d.renderer.doChecks = "
1055 + MarlinConst.DO_CHECKS);
1056
1057 // logging parameters
1058 logInfo("sun.java2d.renderer.useLogger = "
1059 + MarlinConst.USE_LOGGER);
1060 logInfo("sun.java2d.renderer.logCreateContext = "
1061 + MarlinConst.LOG_CREATE_CONTEXT);
1062 logInfo("sun.java2d.renderer.logUnsafeMalloc = "
1063 + MarlinConst.LOG_UNSAFE_MALLOC);
1064
1065 // quality settings
1066 logInfo("sun.java2d.renderer.cubic_dec_d2 = "
1067 + MarlinProperties.getCubicDecD2());
1068 logInfo("sun.java2d.renderer.cubic_inc_d1 = "
1069 + MarlinProperties.getCubicIncD1());
1070 logInfo("sun.java2d.renderer.quad_dec_d2 = "
1071 + MarlinProperties.getQuadDecD2());
1072
1073 logInfo("Renderer settings:");
1074 logInfo("CUB_DEC_BND = " + Renderer.CUB_DEC_BND);
1075 logInfo("CUB_INC_BND = " + Renderer.CUB_INC_BND);
1076 logInfo("QUAD_DEC_BND = " + Renderer.QUAD_DEC_BND);
1077
1078 logInfo("INITIAL_EDGES_CAPACITY = "
1079 + MarlinConst.INITIAL_EDGES_CAPACITY);
1080 logInfo("INITIAL_CROSSING_COUNT = "
1081 + Renderer.INITIAL_CROSSING_COUNT);
1082
1083 logInfo("=========================================================="
1084 + "=====================");
1085 }
1086
1087 /**
1088 * Get the RendererContext instance dedicated to the current thread
1089 * @return RendererContext instance
1090 */
1091 @SuppressWarnings({"unchecked"})
1092 static RendererContext getRendererContext() {
1093 final RendererContext rdrCtx = RDR_CTX_PROVIDER.acquire();
|