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
104 * @param width the width of the widened path as per {@code BasicStroke}
105 * @param caps the end cap decorations as per {@code BasicStroke}
106 * @param join the segment join decorations as per {@code BasicStroke}
107 * @param miterlimit the miter limit as per {@code BasicStroke}
117 int join,
118 float miterlimit,
119 float[] dashes,
120 float dashphase)
121 {
122 final RendererContext rdrCtx = getRendererContext();
123 try {
124 // initialize a large copyable Path2D to avoid a lot of array growing:
125 final Path2D.Float p2d = rdrCtx.getPath2D();
126
127 strokeTo(rdrCtx,
128 src,
129 null,
130 width,
131 NormMode.OFF,
132 caps,
133 join,
134 miterlimit,
135 dashes,
136 dashphase,
137 rdrCtx.transformerPC2D.wrapPath2d(p2d)
138 );
139
140 // Use Path2D copy constructor (trim)
141 return new Path2D.Float(p2d);
142
143 } finally {
144 // recycle the RendererContext instance
145 returnRendererContext(rdrCtx);
146 }
147 }
148
149 /**
150 * Sends the geometry for a widened path as specified by the parameters
151 * to the specified consumer.
152 * <p>
153 * The specified {@code src} {@link Shape} is widened according
154 * to the parameters specified by the {@link BasicStroke} object.
155 * Adjustments are made to the path as appropriate for the
156 * {@link java.awt.RenderingHints#VALUE_STROKE_NORMALIZE} hint if the
157 * {@code normalize} boolean parameter is true.
178 AffineTransform at,
179 BasicStroke bs,
180 boolean thin,
181 boolean normalize,
182 boolean antialias,
183 final PathConsumer2D consumer)
184 {
185 final NormMode norm = (normalize) ?
186 ((antialias) ? NormMode.ON_WITH_AA : NormMode.ON_NO_AA)
187 : NormMode.OFF;
188
189 final RendererContext rdrCtx = getRendererContext();
190 try {
191 strokeTo(rdrCtx, src, at, bs, thin, norm, antialias, consumer);
192 } finally {
193 // recycle the RendererContext instance
194 returnRendererContext(rdrCtx);
195 }
196 }
197
198 final void strokeTo(final RendererContext rdrCtx,
199 Shape src,
200 AffineTransform at,
201 BasicStroke bs,
202 boolean thin,
203 NormMode normalize,
204 boolean antialias,
205 PathConsumer2D pc2d)
206 {
207 float lw;
208 if (thin) {
209 if (antialias) {
210 lw = userSpaceLineWidth(at, MIN_PEN_SIZE);
211 } else {
212 lw = userSpaceLineWidth(at, 1.0f);
213 }
214 } else {
215 lw = bs.getLineWidth();
216 }
217 strokeTo(rdrCtx,
218 src,
219 at,
220 lw,
221 normalize,
222 bs.getEndCap(),
223 bs.getLineJoin(),
224 bs.getMiterLimit(),
225 bs.getDashArray(),
226 bs.getDashPhase(),
227 pc2d);
228 }
229
230 private final float userSpaceLineWidth(AffineTransform at, float lw) {
231
232 float widthScale;
233
234 if (at == null) {
235 widthScale = 1.0f;
236 } else if ((at.getType() & (AffineTransform.TYPE_GENERAL_TRANSFORM |
237 AffineTransform.TYPE_GENERAL_SCALE)) != 0) {
238 widthScale = (float)Math.sqrt(at.getDeterminant());
239 } else {
240 // First calculate the "maximum scale" of this transform.
241 double A = at.getScaleX(); // m00
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'
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
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
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
385 // been applied to Stroker/Dasher's input. Then Ainv will be
386 // applied. Ainv*T*A is not equal to T, but it is a translation,
387 // which means that none of stroker's assumptions about its
388 // input will be violated. After all this, A will be applied
389 // to stroker's output.
390 }
391 } else {
392 // either at is null or it's the identity. In either case
393 // we don't transform the path.
394 at = null;
395 }
396
397 if (USE_SIMPLIFIER) {
398 // Use simplifier after stroker before Renderer
399 // to remove collinear segments (notably due to cap square)
400 pc2d = rdrCtx.simplifier.init(pc2d);
401 }
402
403 final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;
404 pc2d = transformerPC2D.deltaTransformConsumer(pc2d, strokerat);
405
406 pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit);
407
408 if (dashes != null) {
409 if (!recycleDashes) {
410 dashLen = dashes.length;
411 }
412 pc2d = rdrCtx.dasher.init(pc2d, dashes, dashLen, dashphase,
413 recycleDashes);
414 }
415 pc2d = transformerPC2D.inverseDeltaTransformConsumer(pc2d, strokerat);
416
417 final PathIterator pi = norm.getNormalizingPathIterator(rdrCtx,
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
762 * {@code RenderingHint} is in effect
763 * @param bbox returns the bounds of the iteration
764 * @return the {@code AATileGenerator} instance to be consulted
765 * for tile coverages, or null if there is no output to render
766 * @since 1.7
767 */
768 @Override
769 public AATileGenerator getAATileGenerator(Shape s,
770 AffineTransform at,
771 Region clip,
772 BasicStroke bs,
773 boolean thin,
774 boolean normalize,
775 int[] bbox)
776 {
777 MarlinTileGenerator ptg = null;
778 Renderer r = null;
779
780 final RendererContext rdrCtx = getRendererContext();
781 try {
782 // Test if at is identity:
783 final AffineTransform _at = (at != null && !at.isIdentity()) ? at
784 : null;
785
786 final NormMode norm = (normalize) ? NormMode.ON_WITH_AA : NormMode.OFF;
787
788 if (bs == null) {
789 // fill shape:
790 final PathIterator pi = norm.getNormalizingPathIterator(rdrCtx,
791 s.getPathIterator(_at));
792
793 // note: Winding rule may be EvenOdd ONLY for fill operations !
794 r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),
795 clip.getWidth(), clip.getHeight(),
796 pi.getWindingRule());
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 }
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 ||
918 BasicStroke.JOIN_MITER != Stroker.JOIN_MITER ||
919 BasicStroke.JOIN_ROUND != Stroker.JOIN_ROUND ||
920 BasicStroke.JOIN_BEVEL != Stroker.JOIN_BEVEL ||
921 BasicStroke.CAP_BUTT != Stroker.CAP_BUTT ||
922 BasicStroke.CAP_ROUND != Stroker.CAP_ROUND ||
923 BasicStroke.CAP_SQUARE != Stroker.CAP_SQUARE)
924 {
925 throw new InternalError("mismatched renderer constants");
926 }
927 }
928
929 // --- RendererContext handling ---
930 // use ThreadLocal or ConcurrentLinkedQueue to get one RendererContext
931 private static final boolean USE_THREAD_LOCAL;
932
933 // reference type stored in either TL or CLQ
934 static final int REF_TYPE;
935
936 // Per-thread RendererContext
937 private static final ReentrantContextProvider<RendererContext> RDR_CTX_PROVIDER;
938
939 // Static initializer to use TL or CLQ mode
940 static {
941 USE_THREAD_LOCAL = MarlinProperties.isUseThreadLocal();
942
943 // Soft reference by default:
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());
|
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 static final boolean DO_CLIP = MarlinProperties.isDoClip();
89 static final boolean DO_CLIP_FILL = true;
90
91 static final boolean DO_TRACE_PATH = false;
92
93 static final boolean DO_CLIP_RUNTIME_ENABLE = MarlinProperties.isDoClipRuntimeFlag();
94
95 /**
96 * Public constructor
97 */
98 public MarlinRenderingEngine() {
99 super();
100 logSettings(MarlinRenderingEngine.class.getName());
101 }
102
103 /**
104 * Create a widened path as specified by the parameters.
105 * <p>
106 * The specified {@code src} {@link Shape} is widened according
107 * to the specified attribute parameters as per the
108 * {@link BasicStroke} specification.
109 *
110 * @param src the source path to be widened
111 * @param width the width of the widened path as per {@code BasicStroke}
112 * @param caps the end cap decorations as per {@code BasicStroke}
113 * @param join the segment join decorations as per {@code BasicStroke}
114 * @param miterlimit the miter limit as per {@code BasicStroke}
124 int join,
125 float miterlimit,
126 float[] dashes,
127 float dashphase)
128 {
129 final RendererContext rdrCtx = getRendererContext();
130 try {
131 // initialize a large copyable Path2D to avoid a lot of array growing:
132 final Path2D.Float p2d = rdrCtx.getPath2D();
133
134 strokeTo(rdrCtx,
135 src,
136 null,
137 width,
138 NormMode.OFF,
139 caps,
140 join,
141 miterlimit,
142 dashes,
143 dashphase,
144 rdrCtx.transformerPC2D.wrapPath2D(p2d)
145 );
146
147 // Use Path2D copy constructor (trim)
148 return new Path2D.Float(p2d);
149
150 } finally {
151 // recycle the RendererContext instance
152 returnRendererContext(rdrCtx);
153 }
154 }
155
156 /**
157 * Sends the geometry for a widened path as specified by the parameters
158 * to the specified consumer.
159 * <p>
160 * The specified {@code src} {@link Shape} is widened according
161 * to the parameters specified by the {@link BasicStroke} object.
162 * Adjustments are made to the path as appropriate for the
163 * {@link java.awt.RenderingHints#VALUE_STROKE_NORMALIZE} hint if the
164 * {@code normalize} boolean parameter is true.
185 AffineTransform at,
186 BasicStroke bs,
187 boolean thin,
188 boolean normalize,
189 boolean antialias,
190 final PathConsumer2D consumer)
191 {
192 final NormMode norm = (normalize) ?
193 ((antialias) ? NormMode.ON_WITH_AA : NormMode.ON_NO_AA)
194 : NormMode.OFF;
195
196 final RendererContext rdrCtx = getRendererContext();
197 try {
198 strokeTo(rdrCtx, src, at, bs, thin, norm, antialias, consumer);
199 } finally {
200 // recycle the RendererContext instance
201 returnRendererContext(rdrCtx);
202 }
203 }
204
205 void strokeTo(final RendererContext rdrCtx,
206 Shape src,
207 AffineTransform at,
208 BasicStroke bs,
209 boolean thin,
210 NormMode normalize,
211 boolean antialias,
212 PathConsumer2D pc2d)
213 {
214 float lw;
215 if (thin) {
216 if (antialias) {
217 lw = userSpaceLineWidth(at, MIN_PEN_SIZE);
218 } else {
219 lw = userSpaceLineWidth(at, 1.0f);
220 }
221 } else {
222 lw = bs.getLineWidth();
223 }
224 strokeTo(rdrCtx,
225 src,
226 at,
227 lw,
228 normalize,
229 bs.getEndCap(),
230 bs.getLineJoin(),
231 bs.getMiterLimit(),
232 bs.getDashArray(),
233 bs.getDashPhase(),
234 pc2d);
235 }
236
237 private float userSpaceLineWidth(AffineTransform at, float lw) {
238
239 float widthScale;
240
241 if (at == null) {
242 widthScale = 1.0f;
243 } else if ((at.getType() & (AffineTransform.TYPE_GENERAL_TRANSFORM |
244 AffineTransform.TYPE_GENERAL_SCALE)) != 0) {
245 widthScale = (float)Math.sqrt(at.getDeterminant());
246 } else {
247 // First calculate the "maximum scale" of this transform.
248 double A = at.getScaleX(); // m00
249 double C = at.getShearX(); // m01
250 double B = at.getShearY(); // m10
251 double D = at.getScaleY(); // m11
252
253 /*
254 * Given a 2 x 2 affine matrix [ A B ] such that
255 * [ C D ]
256 * v' = [x' y'] = [Ax + Cy, Bx + Dy], we want to
257 * find the maximum magnitude (norm) of the vector v'
285 * of rotation.)
286 *
287 * In the calculus, the ratio of the EB and (EA-EC) terms
288 * ends up being the tangent of 2*theta where theta is
289 * the angle that the long axis of the ellipse makes
290 * with the horizontal axis. Thus, this equation is
291 * calculating the length of the hypotenuse of a triangle
292 * along that axis.
293 */
294
295 double hypot = Math.sqrt(EB*EB + (EA-EC)*(EA-EC));
296 // sqrt omitted, compare to squared limits below.
297 double widthsquared = ((EA + EC + hypot) / 2.0d);
298
299 widthScale = (float)Math.sqrt(widthsquared);
300 }
301
302 return (lw / widthScale);
303 }
304
305 void strokeTo(final RendererContext rdrCtx,
306 Shape src,
307 AffineTransform at,
308 float width,
309 NormMode norm,
310 int caps,
311 int join,
312 float miterlimit,
313 float[] dashes,
314 float dashphase,
315 PathConsumer2D pc2d)
316 {
317 // We use strokerat so that in Stroker and Dasher we can work only
318 // with the pre-transformation coordinates. This will repeat a lot of
319 // computations done in the path iterator, but the alternative is to
320 // work with transformed paths and compute untransformed coordinates
321 // as needed. This would be faster but I do not think the complexity
322 // of working with both untransformed and transformed coordinates in
323 // the same code is worth it.
324 // However, if a path's width is constant after a transformation,
325 // we can skip all this untransforming.
326
327 // As pathTo() will check transformed coordinates for invalid values
328 // (NaN / Infinity) to ignore such points, it is necessary to apply the
329 // transformation before the path processing.
330 AffineTransform strokerat = null;
331
332 int dashLen = -1;
333 boolean recycleDashes = false;
334 float scale = 1.0f;
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.0f * Float.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.0f, 0.0f);
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 scale = (float) Math.sqrt(a*a + c*c);
368
369 if (dashes != null) {
370 recycleDashes = true;
371 dashLen = dashes.length;
372 dashes = rdrCtx.dasher.copyDashArray(dashes);
373 for (int i = 0; i < dashLen; i++) {
374 dashes[i] *= scale;
375 }
376 dashphase *= scale;
377 }
378 width *= scale;
379
380 // by now strokerat == null. Input paths to
381 // stroker (and maybe dasher) will have the full transform at
382 // applied to them and nothing will happen to the output paths.
383 } else {
384 strokerat = at;
385
386 // by now strokerat == at. Input paths to
387 // stroker (and maybe dasher) will have the full transform at
388 // applied to them, then they will be normalized, and then
389 // the inverse of *only the non translation part of at* will
390 // be applied to the normalized paths. This won't cause problems
391 // in stroker, because, suppose at = T*A, where T is just the
392 // translation part of at, and A is the rest. T*A has already
393 // been applied to Stroker/Dasher's input. Then Ainv will be
394 // applied. Ainv*T*A is not equal to T, but it is a translation,
395 // which means that none of stroker's assumptions about its
396 // input will be violated. After all this, A will be applied
397 // to stroker's output.
398 }
399 } else {
400 // either at is null or it's the identity. In either case
401 // we don't transform the path.
402 at = null;
403 }
404
405 final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;
406
407 if (DO_TRACE_PATH) {
408 // trace Stroker:
409 pc2d = transformerPC2D.traceStroker(pc2d);
410 }
411
412 if (USE_SIMPLIFIER) {
413 // Use simplifier after stroker before Renderer
414 // to remove collinear segments (notably due to cap square)
415 pc2d = rdrCtx.simplifier.init(pc2d);
416 }
417
418 // deltaTransformConsumer may adjust the clip rectangle:
419 pc2d = transformerPC2D.deltaTransformConsumer(pc2d, strokerat);
420
421 // stroker will adjust the clip rectangle (width / miter limit):
422 pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit, scale);
423
424 if (dashes != null) {
425 if (!recycleDashes) {
426 dashLen = dashes.length;
427 }
428 pc2d = rdrCtx.dasher.init(pc2d, dashes, dashLen, dashphase,
429 recycleDashes);
430 } else if (rdrCtx.doClip && (caps != Stroker.CAP_BUTT)) {
431 if (DO_TRACE_PATH) {
432 pc2d = transformerPC2D.traceClosedPathDetector(pc2d);
433 }
434
435 // If no dash and clip is enabled:
436 // detect closedPaths (polygons) for caps
437 pc2d = transformerPC2D.detectClosedPath(pc2d);
438 }
439 pc2d = transformerPC2D.inverseDeltaTransformConsumer(pc2d, strokerat);
440
441 if (DO_TRACE_PATH) {
442 // trace Input:
443 pc2d = transformerPC2D.traceInput(pc2d);
444 }
445
446 final PathIterator pi = norm.getNormalizingPathIterator(rdrCtx,
447 src.getPathIterator(at));
448
449 pathTo(rdrCtx, pi, pc2d);
450
451 /*
452 * Pipeline seems to be:
453 * shape.getPathIterator(at)
454 * -> (NormalizingPathIterator)
455 * -> (inverseDeltaTransformConsumer)
456 * -> (Dasher)
457 * -> Stroker
458 * -> (deltaTransformConsumer)
459 *
460 * -> (CollinearSimplifier) to remove redundant segments
461 *
462 * -> pc2d = Renderer (bounding box)
463 */
464 }
465
791 * {@code RenderingHint} is in effect
792 * @param bbox returns the bounds of the iteration
793 * @return the {@code AATileGenerator} instance to be consulted
794 * for tile coverages, or null if there is no output to render
795 * @since 1.7
796 */
797 @Override
798 public AATileGenerator getAATileGenerator(Shape s,
799 AffineTransform at,
800 Region clip,
801 BasicStroke bs,
802 boolean thin,
803 boolean normalize,
804 int[] bbox)
805 {
806 MarlinTileGenerator ptg = null;
807 Renderer r = null;
808
809 final RendererContext rdrCtx = getRendererContext();
810 try {
811 if (DO_CLIP || (DO_CLIP_RUNTIME_ENABLE && MarlinProperties.isDoClipAtRuntime())) {
812 // Define the initial clip bounds:
813 final float[] clipRect = rdrCtx.clipRect;
814
815 clipRect[0] = clip.getLoY();
816 clipRect[1] = clip.getLoY() + clip.getHeight();
817 clipRect[2] = clip.getLoX();
818 clipRect[3] = clip.getLoX() + clip.getWidth();
819
820 // Enable clipping:
821 rdrCtx.doClip = true;
822 }
823
824 // Test if at is identity:
825 final AffineTransform _at = (at != null && !at.isIdentity()) ? at
826 : null;
827
828 final NormMode norm = (normalize) ? NormMode.ON_WITH_AA : NormMode.OFF;
829
830 if (bs == null) {
831 // fill shape:
832 final PathIterator pi = norm.getNormalizingPathIterator(rdrCtx,
833 s.getPathIterator(_at));
834
835 final int windingRule = pi.getWindingRule();
836
837 // note: Winding rule may be EvenOdd ONLY for fill operations !
838 r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),
839 clip.getWidth(), clip.getHeight(),
840 windingRule);
841
842 PathConsumer2D pc2d = r;
843
844 if (DO_CLIP_FILL && rdrCtx.doClip) {
845 if (DO_TRACE_PATH) {
846 // trace Filler:
847 pc2d = rdrCtx.transformerPC2D.traceFiller(pc2d);
848 }
849 pc2d = rdrCtx.transformerPC2D.pathClipper(pc2d);
850 }
851
852 if (DO_TRACE_PATH) {
853 // trace Input:
854 pc2d = rdrCtx.transformerPC2D.traceInput(pc2d);
855 }
856
857 // TODO: subdivide quad/cubic curves into monotonic curves ?
858 pathTo(rdrCtx, pi, pc2d);
859
860 } else {
861 // draw shape with given stroke:
862 r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),
863 clip.getWidth(), clip.getHeight(),
864 WIND_NON_ZERO);
865
866 strokeTo(rdrCtx, s, _at, bs, thin, norm, true, r);
867 }
868 if (r.endRendering()) {
869 ptg = rdrCtx.ptg.init();
870 ptg.getBbox(bbox);
871 // note: do not returnRendererContext(rdrCtx)
872 // as it will be called later by MarlinTileGenerator.dispose()
873 r = null;
874 }
875 } finally {
876 if (r != null) {
877 // dispose renderer and recycle the RendererContext instance:
878 r.dispose();
879 }
880 }
881
882 // Return null to cancel AA tile generation (nothing to render)
883 return ptg;
884 }
885
886 @Override
887 public AATileGenerator getAATileGenerator(double x, double y,
888 double dx1, double dy1,
889 double dx2, double dy2,
890 double lw1, double lw2,
891 Region clip,
892 int[] bbox)
893 {
894 // REMIND: Deal with large coordinates!
895 double ldx1, ldy1, ldx2, ldy2;
896 boolean innerpgram = (lw1 > 0.0d && lw2 > 0.0d);
897
898 if (innerpgram) {
899 ldx1 = dx1 * lw1;
900 ldy1 = dy1 * lw1;
901 ldx2 = dx2 * lw2;
902 ldy2 = dy2 * lw2;
903 x -= (ldx1 + ldx2) / 2.0d;
904 y -= (ldy1 + ldy2) / 2.0d;
905 dx1 += ldx1;
906 dy1 += ldy1;
907 dx2 += ldx2;
908 dy2 += ldy2;
909 if (lw1 > 1.0d && lw2 > 1.0d) {
910 // Inner parallelogram was entirely consumed by stroke...
911 innerpgram = false;
912 }
913 } else {
914 ldx1 = ldy1 = ldx2 = ldy2 = 0.0d;
915 }
916
917 MarlinTileGenerator ptg = null;
918 Renderer r = null;
919
920 final RendererContext rdrCtx = getRendererContext();
921 try {
922 r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),
923 clip.getWidth(), clip.getHeight(),
924 WIND_EVEN_ODD);
925
926 r.moveTo((float) x, (float) y);
927 r.lineTo((float) (x+dx1), (float) (y+dy1));
928 r.lineTo((float) (x+dx1+dx2), (float) (y+dy1+dy2));
929 r.lineTo((float) (x+dx2), (float) (y+dy2));
930 r.closePath();
931
932 if (innerpgram) {
933 x += ldx1 + ldx2;
934 y += ldy1 + ldy2;
935 dx1 -= 2.0d * ldx1;
936 dy1 -= 2.0d * ldy1;
937 dx2 -= 2.0d * ldx2;
938 dy2 -= 2.0d * ldy2;
939 r.moveTo((float) x, (float) y);
940 r.lineTo((float) (x+dx1), (float) (y+dy1));
941 r.lineTo((float) (x+dx1+dx2), (float) (y+dy1+dy2));
942 r.lineTo((float) (x+dx2), (float) (y+dy2));
943 r.closePath();
944 }
956 // dispose renderer and recycle the RendererContext instance:
957 r.dispose();
958 }
959 }
960
961 // Return null to cancel AA tile generation (nothing to render)
962 return ptg;
963 }
964
965 /**
966 * Returns the minimum pen width that the antialiasing rasterizer
967 * can represent without dropouts occuring.
968 * @since 1.7
969 */
970 @Override
971 public float getMinimumAAPenSize() {
972 return MIN_PEN_SIZE;
973 }
974
975 static {
976 if (PathIterator.WIND_NON_ZERO != WIND_NON_ZERO ||
977 PathIterator.WIND_EVEN_ODD != WIND_EVEN_ODD ||
978 BasicStroke.JOIN_MITER != JOIN_MITER ||
979 BasicStroke.JOIN_ROUND != JOIN_ROUND ||
980 BasicStroke.JOIN_BEVEL != JOIN_BEVEL ||
981 BasicStroke.CAP_BUTT != CAP_BUTT ||
982 BasicStroke.CAP_ROUND != CAP_ROUND ||
983 BasicStroke.CAP_SQUARE != CAP_SQUARE)
984 {
985 throw new InternalError("mismatched renderer constants");
986 }
987 }
988
989 // --- RendererContext handling ---
990 // use ThreadLocal or ConcurrentLinkedQueue to get one RendererContext
991 private static final boolean USE_THREAD_LOCAL;
992
993 // reference type stored in either TL or CLQ
994 static final int REF_TYPE;
995
996 // Per-thread RendererContext
997 private static final ReentrantContextProvider<RendererContext> RDR_CTX_PROVIDER;
998
999 // Static initializer to use TL or CLQ mode
1000 static {
1001 USE_THREAD_LOCAL = MarlinProperties.isUseThreadLocal();
1002
1003 // Soft reference by default:
1088 + MarlinConst.TILE_W_LG);
1089 logInfo("sun.java2d.renderer.blockSize_log2 = "
1090 + MarlinConst.BLOCK_SIZE_LG);
1091
1092 // RLE / blockFlags settings
1093
1094 logInfo("sun.java2d.renderer.forceRLE = "
1095 + MarlinProperties.isForceRLE());
1096 logInfo("sun.java2d.renderer.forceNoRLE = "
1097 + MarlinProperties.isForceNoRLE());
1098 logInfo("sun.java2d.renderer.useTileFlags = "
1099 + MarlinProperties.isUseTileFlags());
1100 logInfo("sun.java2d.renderer.useTileFlags.useHeuristics = "
1101 + MarlinProperties.isUseTileFlagsWithHeuristics());
1102 logInfo("sun.java2d.renderer.rleMinWidth = "
1103 + MarlinCache.RLE_MIN_WIDTH);
1104
1105 // optimisation parameters
1106 logInfo("sun.java2d.renderer.useSimplifier = "
1107 + MarlinConst.USE_SIMPLIFIER);
1108 logInfo("sun.java2d.renderer.clip = "
1109 + MarlinProperties.isDoClip());
1110
1111 // debugging parameters
1112 logInfo("sun.java2d.renderer.doStats = "
1113 + MarlinConst.DO_STATS);
1114 logInfo("sun.java2d.renderer.doMonitors = "
1115 + MarlinConst.DO_MONITORS);
1116 logInfo("sun.java2d.renderer.doChecks = "
1117 + MarlinConst.DO_CHECKS);
1118
1119 // logging parameters
1120 logInfo("sun.java2d.renderer.useLogger = "
1121 + MarlinConst.USE_LOGGER);
1122 logInfo("sun.java2d.renderer.logCreateContext = "
1123 + MarlinConst.LOG_CREATE_CONTEXT);
1124 logInfo("sun.java2d.renderer.logUnsafeMalloc = "
1125 + MarlinConst.LOG_UNSAFE_MALLOC);
1126
1127 // quality settings
1128 logInfo("sun.java2d.renderer.cubic_dec_d2 = "
1129 + MarlinProperties.getCubicDecD2());
|