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}
116 int join,
117 float miterlimit,
118 float[] dashes,
119 float dashphase)
120 {
121 final DRendererContext rdrCtx = getRendererContext();
122 try {
123 // initialize a large copyable Path2D to avoid a lot of array growing:
124 final Path2D.Double p2d = rdrCtx.getPath2D();
125
126 strokeTo(rdrCtx,
127 src,
128 null,
129 width,
130 NormMode.OFF,
131 caps,
132 join,
133 miterlimit,
134 dashes,
135 dashphase,
136 rdrCtx.transformerPC2D.wrapPath2d(p2d)
137 );
138
139 // Use Path2D copy constructor (trim)
140 return new Path2D.Double(p2d);
141
142 } finally {
143 // recycle the DRendererContext instance
144 returnRendererContext(rdrCtx);
145 }
146 }
147
148 /**
149 * Sends the geometry for a widened path as specified by the parameters
150 * to the specified consumer.
151 * <p>
152 * The specified {@code src} {@link Shape} is widened according
153 * to the parameters specified by the {@link BasicStroke} object.
154 * Adjustments are made to the path as appropriate for the
155 * {@link java.awt.RenderingHints#VALUE_STROKE_NORMALIZE} hint if the
156 * {@code normalize} boolean parameter is true.
178 BasicStroke bs,
179 boolean thin,
180 boolean normalize,
181 boolean antialias,
182 final sun.awt.geom.PathConsumer2D consumer)
183 {
184 final NormMode norm = (normalize) ?
185 ((antialias) ? NormMode.ON_WITH_AA : NormMode.ON_NO_AA)
186 : NormMode.OFF;
187
188 final DRendererContext rdrCtx = getRendererContext();
189 try {
190 strokeTo(rdrCtx, src, at, bs, thin, norm, antialias,
191 rdrCtx.p2dAdapter.init(consumer));
192 } finally {
193 // recycle the DRendererContext instance
194 returnRendererContext(rdrCtx);
195 }
196 }
197
198 final void strokeTo(final DRendererContext rdrCtx,
199 Shape src,
200 AffineTransform at,
201 BasicStroke bs,
202 boolean thin,
203 NormMode normalize,
204 boolean antialias,
205 DPathConsumer2D pc2d)
206 {
207 double lw;
208 if (thin) {
209 if (antialias) {
210 lw = userSpaceLineWidth(at, MIN_PEN_SIZE);
211 } else {
212 lw = userSpaceLineWidth(at, 1.0d);
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 double userSpaceLineWidth(AffineTransform at, double lw) {
231
232 double widthScale;
233
234 if (at == null) {
235 widthScale = 1.0d;
236 } else if ((at.getType() & (AffineTransform.TYPE_GENERAL_TRANSFORM |
237 AffineTransform.TYPE_GENERAL_SCALE)) != 0) {
238 widthScale = 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 = Math.sqrt(widthsquared);
293 }
294
295 return (lw / widthScale);
296 }
297
298 final void strokeTo(final DRendererContext rdrCtx,
299 Shape src,
300 AffineTransform at,
301 double width,
302 NormMode norm,
303 int caps,
304 int join,
305 float miterlimit,
306 float[] dashes,
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
764 * {@code RenderingHint} is in effect
765 * @param bbox returns the bounds of the iteration
766 * @return the {@code AATileGenerator} instance to be consulted
767 * for tile coverages, or null if there is no output to render
768 * @since 1.7
769 */
770 @Override
771 public AATileGenerator getAATileGenerator(Shape s,
772 AffineTransform at,
773 Region clip,
774 BasicStroke bs,
775 boolean thin,
776 boolean normalize,
777 int[] bbox)
778 {
779 MarlinTileGenerator ptg = null;
780 DRenderer r = null;
781
782 final DRendererContext rdrCtx = getRendererContext();
783 try {
784 // Test if at is identity:
785 final AffineTransform _at = (at != null && !at.isIdentity()) ? at
786 : null;
787
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
828 @Override
829 public final AATileGenerator getAATileGenerator(double x, double y,
830 double dx1, double dy1,
831 double dx2, double dy2,
832 double lw1, double lw2,
833 Region clip,
834 int[] bbox)
835 {
836 // REMIND: Deal with large coordinates!
837 double ldx1, ldy1, ldx2, ldy2;
838 boolean innerpgram = (lw1 > 0.0d && lw2 > 0.0d);
839
840 if (innerpgram) {
841 ldx1 = dx1 * lw1;
842 ldy1 = dy1 * lw1;
843 ldx2 = dx2 * lw2;
844 ldy2 = dy2 * lw2;
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 }
898 // dispose renderer and recycle the RendererContext instance:
899 r.dispose();
900 }
901 }
902
903 // Return null to cancel AA tile generation (nothing to render)
904 return ptg;
905 }
906
907 /**
908 * Returns the minimum pen width that the antialiasing rasterizer
909 * can represent without dropouts occuring.
910 * @since 1.7
911 */
912 @Override
913 public float getMinimumAAPenSize() {
914 return MIN_PEN_SIZE;
915 }
916
917 static {
918 if (PathIterator.WIND_NON_ZERO != DRenderer.WIND_NON_ZERO ||
919 PathIterator.WIND_EVEN_ODD != DRenderer.WIND_EVEN_ODD ||
920 BasicStroke.JOIN_MITER != DStroker.JOIN_MITER ||
921 BasicStroke.JOIN_ROUND != DStroker.JOIN_ROUND ||
922 BasicStroke.JOIN_BEVEL != DStroker.JOIN_BEVEL ||
923 BasicStroke.CAP_BUTT != DStroker.CAP_BUTT ||
924 BasicStroke.CAP_ROUND != DStroker.CAP_ROUND ||
925 BasicStroke.CAP_SQUARE != DStroker.CAP_SQUARE)
926 {
927 throw new InternalError("mismatched renderer constants");
928 }
929 }
930
931 // --- DRendererContext handling ---
932 // use ThreadLocal or ConcurrentLinkedQueue to get one DRendererContext
933 private static final boolean USE_THREAD_LOCAL;
934
935 // reference type stored in either TL or CLQ
936 static final int REF_TYPE;
937
938 // Per-thread DRendererContext
939 private static final ReentrantContextProvider<DRendererContext> RDR_CTX_PROVIDER;
940
941 // Static initializer to use TL or CLQ mode
942 static {
943 USE_THREAD_LOCAL = MarlinProperties.isUseThreadLocal();
944
945 // Soft reference by default:
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_CLIP_FILL = true;
89
90 static final boolean DO_TRACE_PATH = false;
91
92 static final boolean DO_CLIP_RUNTIME_ENABLE = MarlinProperties.isDoClipRuntimeFlag();
93
94 /**
95 * Public constructor
96 */
97 public DMarlinRenderingEngine() {
98 super();
99 logSettings(DMarlinRenderingEngine.class.getName());
100 }
101
102 /**
103 * Create a widened path as specified by the parameters.
104 * <p>
105 * The specified {@code src} {@link Shape} is widened according
106 * to the specified attribute parameters as per the
107 * {@link BasicStroke} specification.
108 *
109 * @param src the source path to be widened
110 * @param width the width of the widened path as per {@code BasicStroke}
111 * @param caps the end cap decorations as per {@code BasicStroke}
112 * @param join the segment join decorations as per {@code BasicStroke}
113 * @param miterlimit the miter limit as per {@code BasicStroke}
123 int join,
124 float miterlimit,
125 float[] dashes,
126 float dashphase)
127 {
128 final DRendererContext rdrCtx = getRendererContext();
129 try {
130 // initialize a large copyable Path2D to avoid a lot of array growing:
131 final Path2D.Double p2d = rdrCtx.getPath2D();
132
133 strokeTo(rdrCtx,
134 src,
135 null,
136 width,
137 NormMode.OFF,
138 caps,
139 join,
140 miterlimit,
141 dashes,
142 dashphase,
143 rdrCtx.transformerPC2D.wrapPath2D(p2d)
144 );
145
146 // Use Path2D copy constructor (trim)
147 return new Path2D.Double(p2d);
148
149 } finally {
150 // recycle the DRendererContext instance
151 returnRendererContext(rdrCtx);
152 }
153 }
154
155 /**
156 * Sends the geometry for a widened path as specified by the parameters
157 * to the specified consumer.
158 * <p>
159 * The specified {@code src} {@link Shape} is widened according
160 * to the parameters specified by the {@link BasicStroke} object.
161 * Adjustments are made to the path as appropriate for the
162 * {@link java.awt.RenderingHints#VALUE_STROKE_NORMALIZE} hint if the
163 * {@code normalize} boolean parameter is true.
185 BasicStroke bs,
186 boolean thin,
187 boolean normalize,
188 boolean antialias,
189 final sun.awt.geom.PathConsumer2D consumer)
190 {
191 final NormMode norm = (normalize) ?
192 ((antialias) ? NormMode.ON_WITH_AA : NormMode.ON_NO_AA)
193 : NormMode.OFF;
194
195 final DRendererContext rdrCtx = getRendererContext();
196 try {
197 strokeTo(rdrCtx, src, at, bs, thin, norm, antialias,
198 rdrCtx.p2dAdapter.init(consumer));
199 } finally {
200 // recycle the DRendererContext instance
201 returnRendererContext(rdrCtx);
202 }
203 }
204
205 void strokeTo(final DRendererContext rdrCtx,
206 Shape src,
207 AffineTransform at,
208 BasicStroke bs,
209 boolean thin,
210 NormMode normalize,
211 boolean antialias,
212 DPathConsumer2D pc2d)
213 {
214 double lw;
215 if (thin) {
216 if (antialias) {
217 lw = userSpaceLineWidth(at, MIN_PEN_SIZE);
218 } else {
219 lw = userSpaceLineWidth(at, 1.0d);
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 double userSpaceLineWidth(AffineTransform at, double lw) {
238
239 double widthScale;
240
241 if (at == null) {
242 widthScale = 1.0d;
243 } else if ((at.getType() & (AffineTransform.TYPE_GENERAL_TRANSFORM |
244 AffineTransform.TYPE_GENERAL_SCALE)) != 0) {
245 widthScale = 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 = Math.sqrt(widthsquared);
300 }
301
302 return (lw / widthScale);
303 }
304
305 void strokeTo(final DRendererContext rdrCtx,
306 Shape src,
307 AffineTransform at,
308 double width,
309 NormMode norm,
310 int caps,
311 int join,
312 float miterlimit,
313 float[] dashes,
314 float dashphase,
315 DPathConsumer2D 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 double scale = 1.0d;
335 double[] dashesD = null;
336
337 // Ensure converting dashes to double precision:
338 if (dashes != null) {
339 recycleDashes = true;
340 dashLen = dashes.length;
341 dashesD = rdrCtx.dasher.copyDashArray(dashes);
342 }
343
344 if (at != null && !at.isIdentity()) {
345 final double a = at.getScaleX();
346 final double b = at.getShearX();
347 final double c = at.getShearY();
348 final double d = at.getScaleY();
349 final double det = a * d - c * b;
350
351 if (Math.abs(det) <= (2.0d * Double.MIN_VALUE)) {
352 // this rendering engine takes one dimensional curves and turns
353 // them into 2D shapes by giving them width.
354 // However, if everything is to be passed through a singular
355 // transformation, these 2D shapes will be squashed down to 1D
356 // again so, nothing can be drawn.
357
358 // Every path needs an initial moveTo and a pathDone. If these
359 // are not there this causes a SIGSEGV in libawt.so (at the time
360 // of writing of this comment (September 16, 2010)). Actually,
361 // I am not sure if the moveTo is necessary to avoid the SIGSEGV
362 // but the pathDone is definitely needed.
363 pc2d.moveTo(0.0d, 0.0d);
364 pc2d.pathDone();
365 return;
366 }
367
368 // If the transform is a constant multiple of an orthogonal transformation
369 // then every length is just multiplied by a constant, so we just
370 // need to transform input paths to stroker and tell stroker
371 // the scaled width. This condition is satisfied if
372 // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we
373 // leave a bit of room for error.
374 if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) {
375 scale = Math.sqrt(a*a + c*c);
376
377 if (dashesD != null) {
378 for (int i = 0; i < dashLen; i++) {
379 dashesD[i] *= scale;
380 }
381 dashphase *= scale;
382 }
383 width *= scale;
384
385 // by now strokerat == null. Input paths to
386 // stroker (and maybe dasher) will have the full transform at
387 // applied to them and nothing will happen to the output paths.
388 } else {
389 strokerat = at;
390
391 // by now strokerat == at. Input paths to
392 // stroker (and maybe dasher) will have the full transform at
393 // applied to them, then they will be normalized, and then
394 // the inverse of *only the non translation part of at* will
395 // be applied to the normalized paths. This won't cause problems
396 // in stroker, because, suppose at = T*A, where T is just the
397 // translation part of at, and A is the rest. T*A has already
398 // been applied to Stroker/Dasher's input. Then Ainv will be
399 // applied. Ainv*T*A is not equal to T, but it is a translation,
400 // which means that none of stroker's assumptions about its
401 // input will be violated. After all this, A will be applied
402 // to stroker's output.
403 }
404 } else {
405 // either at is null or it's the identity. In either case
406 // we don't transform the path.
407 at = null;
408 }
409
410 final DTransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;
411
412 if (DO_TRACE_PATH) {
413 // trace Stroker:
414 pc2d = transformerPC2D.traceStroker(pc2d);
415 }
416
417 if (USE_SIMPLIFIER) {
418 // Use simplifier after stroker before Renderer
419 // to remove collinear segments (notably due to cap square)
420 pc2d = rdrCtx.simplifier.init(pc2d);
421 }
422
423 // deltaTransformConsumer may adjust the clip rectangle:
424 pc2d = transformerPC2D.deltaTransformConsumer(pc2d, strokerat);
425
426 // stroker will adjust the clip rectangle (width / miter limit):
427 pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit, scale);
428
429 if (dashesD != null) {
430 pc2d = rdrCtx.dasher.init(pc2d, dashesD, dashLen, dashphase,
431 recycleDashes);
432 } else if (rdrCtx.doClip && (caps != Stroker.CAP_BUTT)) {
433 if (DO_TRACE_PATH) {
434 pc2d = transformerPC2D.traceClosedPathDetector(pc2d);
435 }
436
437 // If no dash and clip is enabled:
438 // detect closedPaths (polygons) for caps
439 pc2d = transformerPC2D.detectClosedPath(pc2d);
440 }
441 pc2d = transformerPC2D.inverseDeltaTransformConsumer(pc2d, strokerat);
442
443 if (DO_TRACE_PATH) {
444 // trace Input:
445 pc2d = transformerPC2D.traceInput(pc2d);
446 }
447
448 final PathIterator pi = norm.getNormalizingPathIterator(rdrCtx,
449 src.getPathIterator(at));
450
451 pathTo(rdrCtx, pi, pc2d);
452
453 /*
454 * Pipeline seems to be:
455 * shape.getPathIterator(at)
456 * -> (NormalizingPathIterator)
457 * -> (inverseDeltaTransformConsumer)
458 * -> (Dasher)
459 * -> Stroker
460 * -> (deltaTransformConsumer)
461 *
462 * -> (CollinearSimplifier) to remove redundant segments
463 *
464 * -> pc2d = Renderer (bounding box)
465 */
466 }
467
793 * {@code RenderingHint} is in effect
794 * @param bbox returns the bounds of the iteration
795 * @return the {@code AATileGenerator} instance to be consulted
796 * for tile coverages, or null if there is no output to render
797 * @since 1.7
798 */
799 @Override
800 public AATileGenerator getAATileGenerator(Shape s,
801 AffineTransform at,
802 Region clip,
803 BasicStroke bs,
804 boolean thin,
805 boolean normalize,
806 int[] bbox)
807 {
808 MarlinTileGenerator ptg = null;
809 DRenderer r = null;
810
811 final DRendererContext rdrCtx = getRendererContext();
812 try {
813 if (DO_CLIP || (DO_CLIP_RUNTIME_ENABLE && MarlinProperties.isDoClipAtRuntime())) {
814 // Define the initial clip bounds:
815 final double[] clipRect = rdrCtx.clipRect;
816
817 clipRect[0] = clip.getLoY();
818 clipRect[1] = clip.getLoY() + clip.getHeight();
819 clipRect[2] = clip.getLoX();
820 clipRect[3] = clip.getLoX() + clip.getWidth();
821
822 // Enable clipping:
823 rdrCtx.doClip = true;
824 }
825
826 // Test if at is identity:
827 final AffineTransform _at = (at != null && !at.isIdentity()) ? at
828 : null;
829
830 final NormMode norm = (normalize) ? NormMode.ON_WITH_AA : NormMode.OFF;
831
832 if (bs == null) {
833 // fill shape:
834 final PathIterator pi = norm.getNormalizingPathIterator(rdrCtx,
835 s.getPathIterator(_at));
836
837 final int windingRule = pi.getWindingRule();
838
839 // note: Winding rule may be EvenOdd ONLY for fill operations !
840 r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),
841 clip.getWidth(), clip.getHeight(),
842 windingRule);
843
844 DPathConsumer2D pc2d = r;
845
846 if (DO_CLIP_FILL && rdrCtx.doClip) {
847 if (DO_TRACE_PATH) {
848 // trace Filler:
849 pc2d = rdrCtx.transformerPC2D.traceFiller(pc2d);
850 }
851 pc2d = rdrCtx.transformerPC2D.pathClipper(pc2d);
852 }
853
854 if (DO_TRACE_PATH) {
855 // trace Input:
856 pc2d = rdrCtx.transformerPC2D.traceInput(pc2d);
857 }
858
859 // TODO: subdivide quad/cubic curves into monotonic curves ?
860 pathTo(rdrCtx, pi, pc2d);
861
862 } else {
863 // draw shape with given stroke:
864 r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),
865 clip.getWidth(), clip.getHeight(),
866 WIND_NON_ZERO);
867
868 strokeTo(rdrCtx, s, _at, bs, thin, norm, true, r);
869 }
870 if (r.endRendering()) {
871 ptg = rdrCtx.ptg.init();
872 ptg.getBbox(bbox);
873 // note: do not returnRendererContext(rdrCtx)
874 // as it will be called later by MarlinTileGenerator.dispose()
875 r = null;
876 }
877 } finally {
878 if (r != null) {
879 // dispose renderer and recycle the RendererContext instance:
880 r.dispose();
881 }
882 }
883
884 // Return null to cancel AA tile generation (nothing to render)
885 return ptg;
886 }
887
888 @Override
889 public AATileGenerator getAATileGenerator(double x, double y,
890 double dx1, double dy1,
891 double dx2, double dy2,
892 double lw1, double lw2,
893 Region clip,
894 int[] bbox)
895 {
896 // REMIND: Deal with large coordinates!
897 double ldx1, ldy1, ldx2, ldy2;
898 boolean innerpgram = (lw1 > 0.0d && lw2 > 0.0d);
899
900 if (innerpgram) {
901 ldx1 = dx1 * lw1;
902 ldy1 = dy1 * lw1;
903 ldx2 = dx2 * lw2;
904 ldy2 = dy2 * lw2;
905 x -= (ldx1 + ldx2) / 2.0d;
906 y -= (ldy1 + ldy2) / 2.0d;
907 dx1 += ldx1;
908 dy1 += ldy1;
909 dx2 += ldx2;
910 dy2 += ldy2;
911 if (lw1 > 1.0d && lw2 > 1.0d) {
912 // Inner parallelogram was entirely consumed by stroke...
913 innerpgram = false;
914 }
915 } else {
916 ldx1 = ldy1 = ldx2 = ldy2 = 0.0d;
917 }
918
919 MarlinTileGenerator ptg = null;
920 DRenderer r = null;
921
922 final DRendererContext rdrCtx = getRendererContext();
923 try {
924 r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),
925 clip.getWidth(), clip.getHeight(),
926 WIND_EVEN_ODD);
927
928 r.moveTo( x, y);
929 r.lineTo( (x+dx1), (y+dy1));
930 r.lineTo( (x+dx1+dx2), (y+dy1+dy2));
931 r.lineTo( (x+dx2), (y+dy2));
932 r.closePath();
933
934 if (innerpgram) {
935 x += ldx1 + ldx2;
936 y += ldy1 + ldy2;
937 dx1 -= 2.0d * ldx1;
938 dy1 -= 2.0d * ldy1;
939 dx2 -= 2.0d * ldx2;
940 dy2 -= 2.0d * ldy2;
941 r.moveTo( x, y);
942 r.lineTo( (x+dx1), (y+dy1));
943 r.lineTo( (x+dx1+dx2), (y+dy1+dy2));
944 r.lineTo( (x+dx2), (y+dy2));
945 r.closePath();
946 }
958 // dispose renderer and recycle the RendererContext instance:
959 r.dispose();
960 }
961 }
962
963 // Return null to cancel AA tile generation (nothing to render)
964 return ptg;
965 }
966
967 /**
968 * Returns the minimum pen width that the antialiasing rasterizer
969 * can represent without dropouts occuring.
970 * @since 1.7
971 */
972 @Override
973 public float getMinimumAAPenSize() {
974 return MIN_PEN_SIZE;
975 }
976
977 static {
978 if (PathIterator.WIND_NON_ZERO != WIND_NON_ZERO ||
979 PathIterator.WIND_EVEN_ODD != WIND_EVEN_ODD ||
980 BasicStroke.JOIN_MITER != JOIN_MITER ||
981 BasicStroke.JOIN_ROUND != JOIN_ROUND ||
982 BasicStroke.JOIN_BEVEL != JOIN_BEVEL ||
983 BasicStroke.CAP_BUTT != CAP_BUTT ||
984 BasicStroke.CAP_ROUND != CAP_ROUND ||
985 BasicStroke.CAP_SQUARE != CAP_SQUARE)
986 {
987 throw new InternalError("mismatched renderer constants");
988 }
989 }
990
991 // --- DRendererContext handling ---
992 // use ThreadLocal or ConcurrentLinkedQueue to get one DRendererContext
993 private static final boolean USE_THREAD_LOCAL;
994
995 // reference type stored in either TL or CLQ
996 static final int REF_TYPE;
997
998 // Per-thread DRendererContext
999 private static final ReentrantContextProvider<DRendererContext> RDR_CTX_PROVIDER;
1000
1001 // Static initializer to use TL or CLQ mode
1002 static {
1003 USE_THREAD_LOCAL = MarlinProperties.isUseThreadLocal();
1004
1005 // Soft reference by default:
1087 + MarlinConst.TILE_W_LG);
1088 logInfo("sun.java2d.renderer.blockSize_log2 = "
1089 + MarlinConst.BLOCK_SIZE_LG);
1090
1091 // RLE / blockFlags settings
1092
1093 logInfo("sun.java2d.renderer.forceRLE = "
1094 + MarlinProperties.isForceRLE());
1095 logInfo("sun.java2d.renderer.forceNoRLE = "
1096 + MarlinProperties.isForceNoRLE());
1097 logInfo("sun.java2d.renderer.useTileFlags = "
1098 + MarlinProperties.isUseTileFlags());
1099 logInfo("sun.java2d.renderer.useTileFlags.useHeuristics = "
1100 + MarlinProperties.isUseTileFlagsWithHeuristics());
1101 logInfo("sun.java2d.renderer.rleMinWidth = "
1102 + MarlinCache.RLE_MIN_WIDTH);
1103
1104 // optimisation parameters
1105 logInfo("sun.java2d.renderer.useSimplifier = "
1106 + MarlinConst.USE_SIMPLIFIER);
1107 logInfo("sun.java2d.renderer.clip = "
1108 + MarlinProperties.isDoClip());
1109
1110 // debugging parameters
1111 logInfo("sun.java2d.renderer.doStats = "
1112 + MarlinConst.DO_STATS);
1113 logInfo("sun.java2d.renderer.doMonitors = "
1114 + MarlinConst.DO_MONITORS);
1115 logInfo("sun.java2d.renderer.doChecks = "
1116 + MarlinConst.DO_CHECKS);
1117
1118 // logging parameters
1119 logInfo("sun.java2d.renderer.useLogger = "
1120 + MarlinConst.USE_LOGGER);
1121 logInfo("sun.java2d.renderer.logCreateContext = "
1122 + MarlinConst.LOG_CREATE_CONTEXT);
1123 logInfo("sun.java2d.renderer.logUnsafeMalloc = "
1124 + MarlinConst.LOG_UNSAFE_MALLOC);
1125
1126 // quality settings
1127 logInfo("sun.java2d.renderer.cubic_dec_d2 = "
1128 + MarlinProperties.getCubicDecD2());
|