25 |
25 |
26 package sun.java2d.pisces; |
26 package sun.java2d.pisces; |
27 |
27 |
28 import java.awt.Shape; |
28 import java.awt.Shape; |
29 import java.awt.BasicStroke; |
29 import java.awt.BasicStroke; |
30 import java.awt.geom.FlatteningPathIterator; |
30 import java.awt.geom.NoninvertibleTransformException; |
31 import java.awt.geom.Path2D; |
31 import java.awt.geom.Path2D; |
32 import java.awt.geom.AffineTransform; |
32 import java.awt.geom.AffineTransform; |
33 import java.awt.geom.PathIterator; |
33 import java.awt.geom.PathIterator; |
34 |
34 |
35 import sun.awt.geom.PathConsumer2D; |
35 import sun.awt.geom.PathConsumer2D; |
36 import sun.java2d.pipe.Region; |
36 import sun.java2d.pipe.Region; |
37 import sun.java2d.pipe.RenderingEngine; |
37 import sun.java2d.pipe.RenderingEngine; |
38 import sun.java2d.pipe.AATileGenerator; |
38 import sun.java2d.pipe.AATileGenerator; |
39 |
39 |
40 public class PiscesRenderingEngine extends RenderingEngine { |
40 public class PiscesRenderingEngine extends RenderingEngine { |
41 public static double defaultFlat = 0.1; |
|
42 |
|
43 private static enum NormMode {OFF, ON_NO_AA, ON_WITH_AA} |
41 private static enum NormMode {OFF, ON_NO_AA, ON_WITH_AA} |
44 |
42 |
45 /** |
43 /** |
46 * Create a widened path as specified by the parameters. |
44 * Create a widened path as specified by the parameters. |
47 * <p> |
45 * <p> |
131 final PathConsumer2D consumer) |
138 final PathConsumer2D consumer) |
132 { |
139 { |
133 NormMode norm = (normalize) ? |
140 NormMode norm = (normalize) ? |
134 ((antialias) ? NormMode.ON_WITH_AA : NormMode.ON_NO_AA) |
141 ((antialias) ? NormMode.ON_WITH_AA : NormMode.ON_NO_AA) |
135 : NormMode.OFF; |
142 : NormMode.OFF; |
136 strokeTo(src, at, bs, thin, norm, antialias, |
143 strokeTo(src, at, bs, thin, norm, antialias, consumer); |
137 new LineSink() { |
|
138 public void moveTo(float x0, float y0) { |
|
139 consumer.moveTo(x0, y0); |
|
140 } |
|
141 public void lineJoin() {} |
|
142 public void lineTo(float x1, float y1) { |
|
143 consumer.lineTo(x1, y1); |
|
144 } |
|
145 public void close() { |
|
146 consumer.closePath(); |
|
147 } |
|
148 public void end() { |
|
149 consumer.pathDone(); |
|
150 } |
|
151 }); |
|
152 } |
144 } |
153 |
145 |
154 void strokeTo(Shape src, |
146 void strokeTo(Shape src, |
155 AffineTransform at, |
147 AffineTransform at, |
156 BasicStroke bs, |
148 BasicStroke bs, |
157 boolean thin, |
149 boolean thin, |
158 NormMode normalize, |
150 NormMode normalize, |
159 boolean antialias, |
151 boolean antialias, |
160 LineSink lsink) |
152 PathConsumer2D pc2d) |
161 { |
153 { |
162 float lw; |
154 float lw; |
163 if (thin) { |
155 if (thin) { |
164 if (antialias) { |
156 if (antialias) { |
165 lw = userSpaceLineWidth(at, 0.5f); |
157 lw = userSpaceLineWidth(at, 0.5f); |
254 int caps, |
246 int caps, |
255 int join, |
247 int join, |
256 float miterlimit, |
248 float miterlimit, |
257 float dashes[], |
249 float dashes[], |
258 float dashphase, |
250 float dashphase, |
259 LineSink lsink) |
251 PathConsumer2D pc2d) |
260 { |
252 { |
261 float a00 = 1f, a01 = 0f, a10 = 0f, a11 = 1f; |
253 // We use inat and outat so that in Stroker and Dasher we can work only |
|
254 // with the pre-transformation coordinates. This will repeat a lot of |
|
255 // computations done in the path iterator, but the alternative is to |
|
256 // work with transformed paths and compute untransformed coordinates |
|
257 // as needed. This would be faster but I do not think the complexity |
|
258 // of working with both untransformed and transformed coordinates in |
|
259 // the same code is worth it. |
|
260 // However, if a path's width is constant after a transformation, |
|
261 // we can skip all this untransforming. |
|
262 |
|
263 // If normalization is off we save some transformations by not |
|
264 // transforming the input to pisces. Instead, we apply the |
|
265 // transformation after the path processing has been done. |
|
266 // We can't do this if normalization is on, because it isn't a good |
|
267 // idea to normalize before the transformation is applied. |
|
268 AffineTransform inat = null; |
|
269 AffineTransform outat = null; |
|
270 |
|
271 PathIterator pi = null; |
|
272 |
262 if (at != null && !at.isIdentity()) { |
273 if (at != null && !at.isIdentity()) { |
263 a00 = (float)at.getScaleX(); |
274 final double a = at.getScaleX(); |
264 a01 = (float)at.getShearX(); |
275 final double b = at.getShearX(); |
265 a10 = (float)at.getShearY(); |
276 final double c = at.getShearY(); |
266 a11 = (float)at.getScaleY(); |
277 final double d = at.getScaleY(); |
267 } |
278 final double det = a * d - c * b; |
268 lsink = new Stroker(lsink, width, caps, join, miterlimit, a00, a01, a10, a11); |
279 if (Math.abs(det) <= 2 * Float.MIN_VALUE) { |
|
280 // this rendering engine takes one dimensional curves and turns |
|
281 // them into 2D shapes by giving them width. |
|
282 // However, if everything is to be passed through a singular |
|
283 // transformation, these 2D shapes will be squashed down to 1D |
|
284 // again so, nothing can be drawn. |
|
285 |
|
286 // Every path needs an initial moveTo and a pathDone. If these |
|
287 // aren't there this causes a SIGSEV in libawt.so (at the time |
|
288 // of writing of this comment (September 16, 2010)). Actually, |
|
289 // I'm not sure if the moveTo is necessary to avoid the SIGSEV |
|
290 // but the pathDone is definitely needed. |
|
291 pc2d.moveTo(0, 0); |
|
292 pc2d.pathDone(); |
|
293 return; |
|
294 } |
|
295 |
|
296 // If the transform is a constant multiple of an orthogonal transformation |
|
297 // then every length is just multiplied by a constant, so we just |
|
298 // need to transform input paths to stroker and tell stroker |
|
299 // the scaled width. This condition is satisfied if |
|
300 // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we |
|
301 // leave a bit of room for error. |
|
302 if (nearZero(a*b + c*d, 2) && nearZero(a*a+c*c - (b*b+d*d), 2)) { |
|
303 double scale = Math.sqrt(a*a + c*c); |
|
304 if (dashes != null) { |
|
305 dashes = java.util.Arrays.copyOf(dashes, dashes.length); |
|
306 for (int i = 0; i < dashes.length; i++) { |
|
307 dashes[i] = (float)(scale * dashes[i]); |
|
308 } |
|
309 dashphase = (float)(scale * dashphase); |
|
310 } |
|
311 width = (float)(scale * width); |
|
312 pi = src.getPathIterator(at); |
|
313 if (normalize != NormMode.OFF) { |
|
314 pi = new NormalizingPathIterator(pi, normalize); |
|
315 } |
|
316 // leave inat and outat null. |
|
317 } else { |
|
318 // We only need the inverse if normalization is on. Otherwise |
|
319 // we just don't transform the input paths, do all the stroking |
|
320 // and then transform out output (instead of making PathIterator |
|
321 // apply the transformation, us applying the inverse, and then |
|
322 // us applying the transform again to our output). |
|
323 outat = at; |
|
324 if (normalize != NormMode.OFF) { |
|
325 try { |
|
326 inat = outat.createInverse(); |
|
327 } catch (NoninvertibleTransformException e) { |
|
328 // we made sure this can't happen |
|
329 e.printStackTrace(); |
|
330 } |
|
331 pi = src.getPathIterator(at); |
|
332 pi = new NormalizingPathIterator(pi, normalize); |
|
333 } else { |
|
334 pi = src.getPathIterator(null); |
|
335 } |
|
336 } |
|
337 } else { |
|
338 // either at is null or it's the identity. In either case |
|
339 // we don't transform the path. |
|
340 pi = src.getPathIterator(null); |
|
341 if (normalize != NormMode.OFF) { |
|
342 pi = new NormalizingPathIterator(pi, normalize); |
|
343 } |
|
344 } |
|
345 |
|
346 pc2d = TransformingPathConsumer2D.transformConsumer(pc2d, outat); |
|
347 pc2d = new Stroker(pc2d, width, caps, join, miterlimit); |
269 if (dashes != null) { |
348 if (dashes != null) { |
270 lsink = new Dasher(lsink, dashes, dashphase, a00, a01, a10, a11); |
349 pc2d = new Dasher(pc2d, dashes, dashphase); |
271 } |
350 } |
272 PathIterator pi; |
351 pc2d = TransformingPathConsumer2D.transformConsumer(pc2d, inat); |
273 if (normalize != NormMode.OFF) { |
352 |
274 pi = new FlatteningPathIterator( |
353 pathTo(pi, pc2d); |
275 new NormalizingPathIterator(src.getPathIterator(at), normalize), |
354 } |
276 defaultFlat); |
355 |
277 } else { |
356 private static boolean nearZero(double num, int nulps) { |
278 pi = src.getPathIterator(at, defaultFlat); |
357 return Math.abs(num) < nulps * Math.ulp(num); |
279 } |
|
280 pathTo(pi, lsink); |
|
281 } |
358 } |
282 |
359 |
283 private static class NormalizingPathIterator implements PathIterator { |
360 private static class NormalizingPathIterator implements PathIterator { |
284 |
361 |
285 private final PathIterator src; |
362 private final PathIterator src; |
469 BasicStroke bs, |
528 BasicStroke bs, |
470 boolean thin, |
529 boolean thin, |
471 boolean normalize, |
530 boolean normalize, |
472 int bbox[]) |
531 int bbox[]) |
473 { |
532 { |
474 PiscesCache pc = PiscesCache.createInstance(); |
|
475 Renderer r; |
533 Renderer r; |
476 NormMode norm = (normalize) ? NormMode.ON_WITH_AA : NormMode.OFF; |
534 NormMode norm = (normalize) ? NormMode.ON_WITH_AA : NormMode.OFF; |
477 if (bs == null) { |
535 if (bs == null) { |
478 PathIterator pi; |
536 PathIterator pi; |
479 if (normalize) { |
537 if (normalize) { |
480 pi = new FlatteningPathIterator( |
538 pi = new NormalizingPathIterator(s.getPathIterator(at), norm); |
481 new NormalizingPathIterator(s.getPathIterator(at), norm), |
|
482 defaultFlat); |
|
483 } else { |
539 } else { |
484 pi = s.getPathIterator(at, defaultFlat); |
540 pi = s.getPathIterator(at); |
485 } |
541 } |
486 r = new Renderer(3, 3, |
542 r = new Renderer(3, 3, |
487 clip.getLoX(), clip.getLoY(), |
543 clip.getLoX(), clip.getLoY(), |
488 clip.getWidth(), clip.getHeight(), |
544 clip.getWidth(), clip.getHeight(), |
489 pi.getWindingRule(), pc); |
545 pi.getWindingRule()); |
490 pathTo(pi, r); |
546 pathTo(pi, r); |
491 } else { |
547 } else { |
492 r = new Renderer(3, 3, |
548 r = new Renderer(3, 3, |
493 clip.getLoX(), clip.getLoY(), |
549 clip.getLoX(), clip.getLoY(), |
494 clip.getWidth(), clip.getHeight(), |
550 clip.getWidth(), clip.getHeight(), |
495 PathIterator.WIND_NON_ZERO, pc); |
551 PathIterator.WIND_NON_ZERO); |
496 strokeTo(s, at, bs, thin, norm, true, r); |
552 strokeTo(s, at, bs, thin, norm, true, r); |
497 } |
553 } |
498 r.endRendering(); |
554 r.endRendering(); |
499 PiscesTileGenerator ptg = new PiscesTileGenerator(pc, r.MAX_AA_ALPHA); |
555 PiscesTileGenerator ptg = new PiscesTileGenerator(r, r.MAX_AA_ALPHA); |
500 ptg.getBbox(bbox); |
556 ptg.getBbox(bbox); |
501 return ptg; |
557 return ptg; |
502 } |
558 } |
503 |
559 |
504 /** |
560 /** |