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(),
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
579 return Math.floor(coord) + 0.5d;
580 }
581 }
582
583 static final class NearestPixelQuarter
584 extends NormalizingPathIterator
585 {
586 NearestPixelQuarter(final double[] tmp) {
587 super(tmp);
588 }
589
590 @Override
591 double normCoord(final double coord) {
592 // round to nearest (0.25, 0.25) pixel quarter
593 return Math.floor(coord + 0.25d) + 0.25d;
594 }
595 }
596 }
597
598 private static void pathTo(final DRendererContext rdrCtx, final PathIterator pi,
599 final DPathConsumer2D pc2d)
600 {
601 // mark context as DIRTY:
602 rdrCtx.dirty = true;
603
604 final double[] coords = rdrCtx.double6;
605
606 pathToLoop(coords, pi, pc2d);
607
608 // mark context as CLEAN:
609 rdrCtx.dirty = false;
610 }
611
612 private static void pathToLoop(final double[] coords, final PathIterator pi,
613 final DPathConsumer2D pc2d)
614 {
615 // ported from DuctusRenderingEngine.feedConsumer() but simplified:
616 // - removed skip flag = !subpathStarted
617 // - removed pathClosed (ie subpathStarted not set to false)
618 boolean subpathStarted = false;
619
620 for (; !pi.isDone(); pi.next()) {
621 switch (pi.currentSegment(coords)) {
622 case PathIterator.SEG_MOVETO:
623 /* Checking SEG_MOVETO coordinates if they are out of the
624 * [LOWER_BND, UPPER_BND] range. This check also handles NaN
625 * and Infinity values. Skipping next path segment in case of
626 * invalid data.
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(),
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
608 return Math.floor(coord) + 0.5d;
609 }
610 }
611
612 static final class NearestPixelQuarter
613 extends NormalizingPathIterator
614 {
615 NearestPixelQuarter(final double[] tmp) {
616 super(tmp);
617 }
618
619 @Override
620 double normCoord(final double coord) {
621 // round to nearest (0.25, 0.25) pixel quarter
622 return Math.floor(coord + 0.25d) + 0.25d;
623 }
624 }
625 }
626
627 private static void pathTo(final DRendererContext rdrCtx, final PathIterator pi,
628 DPathConsumer2D pc2d)
629 {
630 // mark context as DIRTY:
631 rdrCtx.dirty = true;
632
633 pathToLoop(rdrCtx.double6, pi, pc2d);
634
635 // mark context as CLEAN:
636 rdrCtx.dirty = false;
637 }
638
639 private static void pathToLoop(final double[] coords, final PathIterator pi,
640 final DPathConsumer2D pc2d)
641 {
642 // ported from DuctusRenderingEngine.feedConsumer() but simplified:
643 // - removed skip flag = !subpathStarted
644 // - removed pathClosed (ie subpathStarted not set to false)
645 boolean subpathStarted = false;
646
647 for (; !pi.isDone(); pi.next()) {
648 switch (pi.currentSegment(coords)) {
649 case PathIterator.SEG_MOVETO:
650 /* Checking SEG_MOVETO coordinates if they are out of the
651 * [LOWER_BND, UPPER_BND] range. This check also handles NaN
652 * and Infinity values. Skipping next path segment in case of
653 * invalid data.
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 DRenderer r = null;
808
809 final DRendererContext rdrCtx = getRendererContext();
810 try {
811 if (DO_CLIP || (DO_CLIP_RUNTIME_ENABLE && MarlinProperties.isDoClipAtRuntime())) {
812 // Define the initial clip bounds:
813 final double[] 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 // note: Winding rule may be EvenOdd ONLY for fill operations !
836 r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),
837 clip.getWidth(), clip.getHeight(),
838 pi.getWindingRule());
839
840 DPathConsumer2D pc2d = r;
841
842 if (DO_CLIP_FILL && rdrCtx.doClip) {
843 if (DO_TRACE_PATH) {
844 // trace Filler:
845 pc2d = rdrCtx.transformerPC2D.traceFiller(pc2d);
846 }
847 pc2d = rdrCtx.transformerPC2D.pathClipper(pc2d);
848 }
849
850 if (DO_TRACE_PATH) {
851 // trace Input:
852 pc2d = rdrCtx.transformerPC2D.traceInput(pc2d);
853 }
854
855 // TODO: subdivide quad/cubic curves into monotonic curves ?
856 pathTo(rdrCtx, pi, pc2d);
857
858 } else {
859 // draw shape with given stroke:
860 r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),
861 clip.getWidth(), clip.getHeight(),
862 WIND_NON_ZERO);
863
864 strokeTo(rdrCtx, s, _at, bs, thin, norm, true, r);
865 }
866 if (r.endRendering()) {
867 ptg = rdrCtx.ptg.init();
868 ptg.getBbox(bbox);
869 // note: do not returnRendererContext(rdrCtx)
870 // as it will be called later by MarlinTileGenerator.dispose()
871 r = null;
872 }
873 } finally {
874 if (r != null) {
875 // dispose renderer and recycle the RendererContext instance:
876 r.dispose();
877 }
878 }
879
880 // Return null to cancel AA tile generation (nothing to render)
881 return ptg;
882 }
883
884 @Override
885 public AATileGenerator getAATileGenerator(double x, double y,
886 double dx1, double dy1,
887 double dx2, double dy2,
888 double lw1, double lw2,
889 Region clip,
890 int[] bbox)
891 {
892 // REMIND: Deal with large coordinates!
893 double ldx1, ldy1, ldx2, ldy2;
894 boolean innerpgram = (lw1 > 0.0d && lw2 > 0.0d);
895
896 if (innerpgram) {
897 ldx1 = dx1 * lw1;
898 ldy1 = dy1 * lw1;
899 ldx2 = dx2 * lw2;
900 ldy2 = dy2 * lw2;
901 x -= (ldx1 + ldx2) / 2.0d;
902 y -= (ldy1 + ldy2) / 2.0d;
903 dx1 += ldx1;
904 dy1 += ldy1;
905 dx2 += ldx2;
906 dy2 += ldy2;
907 if (lw1 > 1.0d && lw2 > 1.0d) {
908 // Inner parallelogram was entirely consumed by stroke...
909 innerpgram = false;
910 }
911 } else {
912 ldx1 = ldy1 = ldx2 = ldy2 = 0.0d;
913 }
914
915 MarlinTileGenerator ptg = null;
916 DRenderer r = null;
917
918 final DRendererContext rdrCtx = getRendererContext();
919 try {
920 r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),
921 clip.getWidth(), clip.getHeight(),
922 WIND_EVEN_ODD);
923
924 r.moveTo( x, y);
925 r.lineTo( (x+dx1), (y+dy1));
926 r.lineTo( (x+dx1+dx2), (y+dy1+dy2));
927 r.lineTo( (x+dx2), (y+dy2));
928 r.closePath();
929
930 if (innerpgram) {
931 x += ldx1 + ldx2;
932 y += ldy1 + ldy2;
933 dx1 -= 2.0d * ldx1;
934 dy1 -= 2.0d * ldy1;
935 dx2 -= 2.0d * ldx2;
936 dy2 -= 2.0d * ldy2;
937 r.moveTo( x, y);
938 r.lineTo( (x+dx1), (y+dy1));
939 r.lineTo( (x+dx1+dx2), (y+dy1+dy2));
940 r.lineTo( (x+dx2), (y+dy2));
941 r.closePath();
942 }
954 // dispose renderer and recycle the RendererContext instance:
955 r.dispose();
956 }
957 }
958
959 // Return null to cancel AA tile generation (nothing to render)
960 return ptg;
961 }
962
963 /**
964 * Returns the minimum pen width that the antialiasing rasterizer
965 * can represent without dropouts occuring.
966 * @since 1.7
967 */
968 @Override
969 public float getMinimumAAPenSize() {
970 return MIN_PEN_SIZE;
971 }
972
973 static {
974 if (PathIterator.WIND_NON_ZERO != WIND_NON_ZERO ||
975 PathIterator.WIND_EVEN_ODD != WIND_EVEN_ODD ||
976 BasicStroke.JOIN_MITER != JOIN_MITER ||
977 BasicStroke.JOIN_ROUND != JOIN_ROUND ||
978 BasicStroke.JOIN_BEVEL != JOIN_BEVEL ||
979 BasicStroke.CAP_BUTT != CAP_BUTT ||
980 BasicStroke.CAP_ROUND != CAP_ROUND ||
981 BasicStroke.CAP_SQUARE != CAP_SQUARE)
982 {
983 throw new InternalError("mismatched renderer constants");
984 }
985 }
986
987 // --- DRendererContext handling ---
988 // use ThreadLocal or ConcurrentLinkedQueue to get one DRendererContext
989 private static final boolean USE_THREAD_LOCAL;
990
991 // reference type stored in either TL or CLQ
992 static final int REF_TYPE;
993
994 // Per-thread DRendererContext
995 private static final ReentrantContextProvider<DRendererContext> RDR_CTX_PROVIDER;
996
997 // Static initializer to use TL or CLQ mode
998 static {
999 USE_THREAD_LOCAL = MarlinProperties.isUseThreadLocal();
1000
1001 // Soft reference by default:
1083 + MarlinConst.TILE_W_LG);
1084 logInfo("sun.java2d.renderer.blockSize_log2 = "
1085 + MarlinConst.BLOCK_SIZE_LG);
1086
1087 // RLE / blockFlags settings
1088
1089 logInfo("sun.java2d.renderer.forceRLE = "
1090 + MarlinProperties.isForceRLE());
1091 logInfo("sun.java2d.renderer.forceNoRLE = "
1092 + MarlinProperties.isForceNoRLE());
1093 logInfo("sun.java2d.renderer.useTileFlags = "
1094 + MarlinProperties.isUseTileFlags());
1095 logInfo("sun.java2d.renderer.useTileFlags.useHeuristics = "
1096 + MarlinProperties.isUseTileFlagsWithHeuristics());
1097 logInfo("sun.java2d.renderer.rleMinWidth = "
1098 + MarlinCache.RLE_MIN_WIDTH);
1099
1100 // optimisation parameters
1101 logInfo("sun.java2d.renderer.useSimplifier = "
1102 + MarlinConst.USE_SIMPLIFIER);
1103 logInfo("sun.java2d.renderer.clip = "
1104 + MarlinProperties.isDoClip());
1105 logInfo("sun.java2d.renderer.clip.runtime.enable = "
1106 + MarlinProperties.isDoClipRuntimeFlag());
1107
1108 // debugging parameters
1109 logInfo("sun.java2d.renderer.doStats = "
1110 + MarlinConst.DO_STATS);
1111 logInfo("sun.java2d.renderer.doMonitors = "
1112 + MarlinConst.DO_MONITORS);
1113 logInfo("sun.java2d.renderer.doChecks = "
1114 + MarlinConst.DO_CHECKS);
1115
1116 // logging parameters
1117 logInfo("sun.java2d.renderer.useLogger = "
1118 + MarlinConst.USE_LOGGER);
1119 logInfo("sun.java2d.renderer.logCreateContext = "
1120 + MarlinConst.LOG_CREATE_CONTEXT);
1121 logInfo("sun.java2d.renderer.logUnsafeMalloc = "
1122 + MarlinConst.LOG_UNSAFE_MALLOC);
1123
1124 // quality settings
1125 logInfo("sun.java2d.renderer.cubic_dec_d2 = "
1126 + MarlinProperties.getCubicDecD2());
|