26 package sun.java2d.marlin; |
26 package sun.java2d.marlin; |
27 |
27 |
28 import java.util.Arrays; |
28 import java.util.Arrays; |
29 |
29 |
30 import sun.awt.geom.PathConsumer2D; |
30 import sun.awt.geom.PathConsumer2D; |
|
31 import sun.java2d.marlin.Helpers.PolyStack; |
31 |
32 |
32 // TODO: some of the arithmetic here is too verbose and prone to hard to |
33 // TODO: some of the arithmetic here is too verbose and prone to hard to |
33 // debug typos. We should consider making a small Point/Vector class that |
34 // debug typos. We should consider making a small Point/Vector class that |
34 // has methods like plus(Point), minus(Point), dot(Point), cross(Point)and such |
35 // has methods like plus(Point), minus(Point), dot(Point), cross(Point)and such |
35 final class Stroker implements PathConsumer2D, MarlinConst { |
36 final class Stroker implements PathConsumer2D, MarlinConst { |
36 |
37 |
37 private static final int MOVE_TO = 0; |
38 private static final int MOVE_TO = 0; |
38 private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad |
39 private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad |
39 private static final int CLOSE = 2; |
40 private static final int CLOSE = 2; |
40 |
41 |
41 /** |
|
42 * Constant value for join style. |
|
43 */ |
|
44 public static final int JOIN_MITER = 0; |
|
45 |
|
46 /** |
|
47 * Constant value for join style. |
|
48 */ |
|
49 public static final int JOIN_ROUND = 1; |
|
50 |
|
51 /** |
|
52 * Constant value for join style. |
|
53 */ |
|
54 public static final int JOIN_BEVEL = 2; |
|
55 |
|
56 /** |
|
57 * Constant value for end cap style. |
|
58 */ |
|
59 public static final int CAP_BUTT = 0; |
|
60 |
|
61 /** |
|
62 * Constant value for end cap style. |
|
63 */ |
|
64 public static final int CAP_ROUND = 1; |
|
65 |
|
66 /** |
|
67 * Constant value for end cap style. |
|
68 */ |
|
69 public static final int CAP_SQUARE = 2; |
|
70 |
|
71 // pisces used to use fixed point arithmetic with 16 decimal digits. I |
42 // pisces used to use fixed point arithmetic with 16 decimal digits. I |
72 // didn't want to change the values of the constant below when I converted |
43 // didn't want to change the values of the constant below when I converted |
73 // it to floating point, so that's why the divisions by 2^16 are there. |
44 // it to floating point, so that's why the divisions by 2^16 are there. |
74 private static final float ROUND_JOIN_THRESHOLD = 1000.0f/65536.0f; |
45 private static final float ROUND_JOIN_THRESHOLD = 1000.0f/65536.0f; |
75 |
46 |
76 private static final float C = 0.5522847498307933f; |
47 // kappa = (4/3) * (SQRT(2) - 1) |
|
48 private static final float C = (float)(4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d); |
|
49 |
|
50 // SQRT(2) |
|
51 private static final float SQRT_2 = (float)Math.sqrt(2.0d); |
77 |
52 |
78 private static final int MAX_N_CURVES = 11; |
53 private static final int MAX_N_CURVES = 11; |
79 |
54 |
80 private PathConsumer2D out; |
55 private PathConsumer2D out; |
81 |
56 |
118 final RendererContext rdrCtx; |
93 final RendererContext rdrCtx; |
119 |
94 |
120 // dirty curve |
95 // dirty curve |
121 final Curve curve; |
96 final Curve curve; |
122 |
97 |
|
98 // Bounds of the drawing region, at pixel precision. |
|
99 private float[] clipRect; |
|
100 |
|
101 // the outcode of the current point |
|
102 private int cOutCode = 0; |
|
103 |
|
104 // the outcode of the starting point |
|
105 private int sOutCode = 0; |
|
106 |
|
107 // flag indicating if the path is opened (clipped) |
|
108 private boolean opened = false; |
|
109 // flag indicating if the starting point's cap is done |
|
110 private boolean capStart = false; |
|
111 |
123 /** |
112 /** |
124 * Constructs a <code>Stroker</code>. |
113 * Constructs a <code>Stroker</code>. |
125 * @param rdrCtx per-thread renderer context |
114 * @param rdrCtx per-thread renderer context |
126 */ |
115 */ |
127 Stroker(final RendererContext rdrCtx) { |
116 Stroker(final RendererContext rdrCtx) { |
128 this.rdrCtx = rdrCtx; |
117 this.rdrCtx = rdrCtx; |
129 |
118 |
130 this.reverse = new PolyStack(rdrCtx); |
119 this.reverse = (rdrCtx.stats != null) ? |
|
120 new PolyStack(rdrCtx, |
|
121 rdrCtx.stats.stat_str_polystack_types, |
|
122 rdrCtx.stats.stat_str_polystack_curves, |
|
123 rdrCtx.stats.hist_str_polystack_curves, |
|
124 rdrCtx.stats.stat_array_str_polystack_curves, |
|
125 rdrCtx.stats.stat_array_str_polystack_types) |
|
126 : new PolyStack(rdrCtx); |
|
127 |
131 this.curve = rdrCtx.curve; |
128 this.curve = rdrCtx.curve; |
132 } |
129 } |
133 |
130 |
134 /** |
131 /** |
135 * Inits the <code>Stroker</code>. |
132 * Inits the <code>Stroker</code>. |
141 * <code>CAP_SQUARE</code>. |
138 * <code>CAP_SQUARE</code>. |
142 * @param joinStyle the desired line join style, one of |
139 * @param joinStyle the desired line join style, one of |
143 * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or |
140 * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or |
144 * <code>JOIN_BEVEL</code>. |
141 * <code>JOIN_BEVEL</code>. |
145 * @param miterLimit the desired miter limit |
142 * @param miterLimit the desired miter limit |
|
143 * @param scale scaling factor applied to clip boundaries |
146 * @return this instance |
144 * @return this instance |
147 */ |
145 */ |
148 Stroker init(PathConsumer2D pc2d, |
146 Stroker init(final PathConsumer2D pc2d, |
149 float lineWidth, |
147 final float lineWidth, |
150 int capStyle, |
148 final int capStyle, |
151 int joinStyle, |
149 final int joinStyle, |
152 float miterLimit) |
150 final float miterLimit, |
|
151 final float scale) |
153 { |
152 { |
154 this.out = pc2d; |
153 this.out = pc2d; |
155 |
154 |
156 this.lineWidth2 = lineWidth / 2.0f; |
155 this.lineWidth2 = lineWidth / 2.0f; |
157 this.invHalfLineWidth2Sq = 1.0f / (2.0f * lineWidth2 * lineWidth2); |
156 this.invHalfLineWidth2Sq = 1.0f / (2.0f * lineWidth2 * lineWidth2); |
158 this.capStyle = capStyle; |
157 this.capStyle = capStyle; |
159 this.joinStyle = joinStyle; |
158 this.joinStyle = joinStyle; |
160 |
159 |
161 float limit = miterLimit * lineWidth2; |
160 final float limit = miterLimit * lineWidth2; |
162 this.miterLimitSq = limit * limit; |
161 this.miterLimitSq = limit * limit; |
163 |
162 |
164 this.prev = CLOSE; |
163 this.prev = CLOSE; |
165 |
164 |
166 rdrCtx.stroking = 1; |
165 rdrCtx.stroking = 1; |
167 |
166 |
|
167 if (rdrCtx.doClip) { |
|
168 // Adjust the clipping rectangle with the stroker margin (miter limit, width) |
|
169 float rdrOffX = 0.0f, rdrOffY = 0.0f; |
|
170 float margin = lineWidth2; |
|
171 |
|
172 if (capStyle == CAP_SQUARE) { |
|
173 margin *= SQRT_2; |
|
174 } |
|
175 if ((joinStyle == JOIN_MITER) && (margin < limit)) { |
|
176 margin = limit; |
|
177 } |
|
178 if (scale != 1.0f) { |
|
179 margin *= scale; |
|
180 rdrOffX = scale * Renderer.RDR_OFFSET_X; |
|
181 rdrOffY = scale * Renderer.RDR_OFFSET_Y; |
|
182 } |
|
183 // add a small rounding error: |
|
184 margin += 1e-3f; |
|
185 |
|
186 // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY |
|
187 // adjust clip rectangle (ymin, ymax, xmin, xmax): |
|
188 final float[] _clipRect = rdrCtx.clipRect; |
|
189 _clipRect[0] -= margin - rdrOffY; |
|
190 _clipRect[1] += margin + rdrOffY; |
|
191 _clipRect[2] -= margin - rdrOffX; |
|
192 _clipRect[3] += margin + rdrOffX; |
|
193 this.clipRect = _clipRect; |
|
194 } else { |
|
195 this.clipRect = null; |
|
196 this.cOutCode = 0; |
|
197 this.sOutCode = 0; |
|
198 } |
168 return this; // fluent API |
199 return this; // fluent API |
169 } |
200 } |
170 |
201 |
171 /** |
202 /** |
172 * Disposes this stroker: |
203 * Disposes this stroker: |
173 * clean up before reusing this instance |
204 * clean up before reusing this instance |
174 */ |
205 */ |
175 void dispose() { |
206 void dispose() { |
176 reverse.dispose(); |
207 reverse.dispose(); |
|
208 |
|
209 opened = false; |
|
210 capStart = false; |
177 |
211 |
178 if (DO_CLEAN_DIRTY) { |
212 if (DO_CLEAN_DIRTY) { |
179 // Force zero-fill dirty arrays: |
213 // Force zero-fill dirty arrays: |
180 Arrays.fill(offset0, 0.0f); |
214 Arrays.fill(offset0, 0.0f); |
181 Arrays.fill(offset1, 0.0f); |
215 Arrays.fill(offset1, 0.0f); |
443 emitLineTo(miterX, miterY, rev); |
477 emitLineTo(miterX, miterY, rev); |
444 } |
478 } |
445 } |
479 } |
446 |
480 |
447 @Override |
481 @Override |
448 public void moveTo(float x0, float y0) { |
482 public void moveTo(final float x0, final float y0) { |
449 if (prev == DRAWING_OP_TO) { |
483 moveTo(x0, y0, cOutCode); |
450 finish(); |
484 // update starting point: |
451 } |
485 this.sx0 = x0; |
452 this.sx0 = this.cx0 = x0; |
486 this.sy0 = y0; |
453 this.sy0 = this.cy0 = y0; |
487 this.sdx = 1.0f; |
454 this.cdx = this.sdx = 1.0f; |
488 this.sdy = 0.0f; |
455 this.cdy = this.sdy = 0.0f; |
489 this.opened = false; |
456 this.prev = MOVE_TO; |
490 this.capStart = false; |
|
491 |
|
492 if (clipRect != null) { |
|
493 final int outcode = Helpers.outcode(x0, y0, clipRect); |
|
494 this.cOutCode = outcode; |
|
495 this.sOutCode = outcode; |
|
496 } |
|
497 } |
|
498 |
|
499 private void moveTo(final float x0, final float y0, |
|
500 final int outcode) |
|
501 { |
|
502 if (prev == MOVE_TO) { |
|
503 this.cx0 = x0; |
|
504 this.cy0 = y0; |
|
505 } else { |
|
506 if (prev == DRAWING_OP_TO) { |
|
507 finish(outcode); |
|
508 } |
|
509 this.prev = MOVE_TO; |
|
510 this.cx0 = x0; |
|
511 this.cy0 = y0; |
|
512 this.cdx = 1.0f; |
|
513 this.cdy = 0.0f; |
|
514 } |
457 } |
515 } |
458 |
516 |
459 @Override |
517 @Override |
460 public void lineTo(float x1, float y1) { |
518 public void lineTo(final float x1, final float y1) { |
|
519 lineTo(x1, y1, false); |
|
520 } |
|
521 |
|
522 private void lineTo(final float x1, final float y1, |
|
523 final boolean force) |
|
524 { |
|
525 final int outcode0 = this.cOutCode; |
|
526 if (!force && clipRect != null) { |
|
527 final int outcode1 = Helpers.outcode(x1, y1, clipRect); |
|
528 this.cOutCode = outcode1; |
|
529 |
|
530 // basic rejection criteria |
|
531 if ((outcode0 & outcode1) != 0) { |
|
532 moveTo(x1, y1, outcode0); |
|
533 opened = true; |
|
534 return; |
|
535 } |
|
536 } |
|
537 |
461 float dx = x1 - cx0; |
538 float dx = x1 - cx0; |
462 float dy = y1 - cy0; |
539 float dy = y1 - cy0; |
463 if (dx == 0.0f && dy == 0.0f) { |
540 if (dx == 0.0f && dy == 0.0f) { |
464 dx = 1.0f; |
541 dx = 1.0f; |
465 } |
542 } |
466 computeOffset(dx, dy, lineWidth2, offset0); |
543 computeOffset(dx, dy, lineWidth2, offset0); |
467 final float mx = offset0[0]; |
544 final float mx = offset0[0]; |
468 final float my = offset0[1]; |
545 final float my = offset0[1]; |
469 |
546 |
470 drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my); |
547 drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my, outcode0); |
471 |
548 |
472 emitLineTo(cx0 + mx, cy0 + my); |
549 emitLineTo(cx0 + mx, cy0 + my); |
473 emitLineTo( x1 + mx, y1 + my); |
550 emitLineTo( x1 + mx, y1 + my); |
474 |
551 |
475 emitLineToRev(cx0 - mx, cy0 - my); |
552 emitLineToRev(cx0 - mx, cy0 - my); |
476 emitLineToRev( x1 - mx, y1 - my); |
553 emitLineToRev( x1 - mx, y1 - my); |
477 |
554 |
|
555 this.prev = DRAWING_OP_TO; |
|
556 this.cx0 = x1; |
|
557 this.cy0 = y1; |
|
558 this.cdx = dx; |
|
559 this.cdy = dy; |
478 this.cmx = mx; |
560 this.cmx = mx; |
479 this.cmy = my; |
561 this.cmy = my; |
480 this.cdx = dx; |
|
481 this.cdy = dy; |
|
482 this.cx0 = x1; |
|
483 this.cy0 = y1; |
|
484 this.prev = DRAWING_OP_TO; |
|
485 } |
562 } |
486 |
563 |
487 @Override |
564 @Override |
488 public void closePath() { |
565 public void closePath() { |
489 if (prev != DRAWING_OP_TO) { |
566 // distinguish empty path at all vs opened path ? |
|
567 if (prev != DRAWING_OP_TO && !opened) { |
490 if (prev == CLOSE) { |
568 if (prev == CLOSE) { |
491 return; |
569 return; |
492 } |
570 } |
493 emitMoveTo(cx0, cy0 - lineWidth2); |
571 emitMoveTo(cx0, cy0 - lineWidth2); |
494 this.cmx = this.smx = 0.0f; |
572 |
495 this.cmy = this.smy = -lineWidth2; |
573 this.sdx = 1.0f; |
496 this.cdx = this.sdx = 1.0f; |
574 this.sdy = 0.0f; |
497 this.cdy = this.sdy = 0.0f; |
575 this.cdx = 1.0f; |
498 finish(); |
576 this.cdy = 0.0f; |
|
577 |
|
578 this.smx = 0.0f; |
|
579 this.smy = -lineWidth2; |
|
580 this.cmx = 0.0f; |
|
581 this.cmy = -lineWidth2; |
|
582 |
|
583 finish(cOutCode); |
499 return; |
584 return; |
500 } |
585 } |
501 |
586 |
502 if (cx0 != sx0 || cy0 != sy0) { |
587 // basic acceptance criteria |
503 lineTo(sx0, sy0); |
588 if ((sOutCode & cOutCode) == 0) { |
504 } |
589 if (cx0 != sx0 || cy0 != sy0) { |
505 |
590 lineTo(sx0, sy0, true); |
506 drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy); |
591 } |
507 |
592 |
508 emitLineTo(sx0 + smx, sy0 + smy); |
593 drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy, sOutCode); |
509 |
594 |
510 emitMoveTo(sx0 - smx, sy0 - smy); |
595 emitLineTo(sx0 + smx, sy0 + smy); |
|
596 |
|
597 if (opened) { |
|
598 emitLineTo(sx0 - smx, sy0 - smy); |
|
599 } else { |
|
600 emitMoveTo(sx0 - smx, sy0 - smy); |
|
601 } |
|
602 } |
|
603 // Ignore caps like finish(false) |
511 emitReverse(); |
604 emitReverse(); |
512 |
605 |
513 this.prev = CLOSE; |
606 this.prev = CLOSE; |
514 emitClose(); |
607 |
|
608 if (opened) { |
|
609 // do not emit close |
|
610 opened = false; |
|
611 } else { |
|
612 emitClose(); |
|
613 } |
515 } |
614 } |
516 |
615 |
517 private void emitReverse() { |
616 private void emitReverse() { |
518 reverse.popAll(out); |
617 reverse.popAll(out); |
519 } |
618 } |
520 |
619 |
521 @Override |
620 @Override |
522 public void pathDone() { |
621 public void pathDone() { |
523 if (prev == DRAWING_OP_TO) { |
622 if (prev == DRAWING_OP_TO) { |
524 finish(); |
623 finish(cOutCode); |
525 } |
624 } |
526 |
625 |
527 out.pathDone(); |
626 out.pathDone(); |
528 |
627 |
529 // this shouldn't matter since this object won't be used |
628 // this shouldn't matter since this object won't be used |
532 |
631 |
533 // Dispose this instance: |
632 // Dispose this instance: |
534 dispose(); |
633 dispose(); |
535 } |
634 } |
536 |
635 |
537 private void finish() { |
636 private void finish(final int outcode) { |
538 if (capStyle == CAP_ROUND) { |
637 // Problem: impossible to guess if the path will be closed in advance |
539 drawRoundCap(cx0, cy0, cmx, cmy); |
638 // i.e. if caps must be drawn or not ? |
540 } else if (capStyle == CAP_SQUARE) { |
639 // Solution: use the ClosedPathDetector before Stroker to determine |
541 emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy); |
640 // if the path is a closed path or not |
542 emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy); |
641 if (!rdrCtx.closedPath) { |
543 } |
642 if (outcode == 0) { |
544 |
643 // current point = end's cap: |
545 emitReverse(); |
644 if (capStyle == CAP_ROUND) { |
546 |
645 drawRoundCap(cx0, cy0, cmx, cmy); |
547 if (capStyle == CAP_ROUND) { |
646 } else if (capStyle == CAP_SQUARE) { |
548 drawRoundCap(sx0, sy0, -smx, -smy); |
647 emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy); |
549 } else if (capStyle == CAP_SQUARE) { |
648 emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy); |
550 emitLineTo(sx0 + smy - smx, sy0 - smx - smy); |
649 } |
551 emitLineTo(sx0 + smy + smx, sy0 - smx + smy); |
650 } |
552 } |
651 emitReverse(); |
553 |
652 |
|
653 if (!capStart) { |
|
654 capStart = true; |
|
655 |
|
656 if (sOutCode == 0) { |
|
657 // starting point = initial cap: |
|
658 if (capStyle == CAP_ROUND) { |
|
659 drawRoundCap(sx0, sy0, -smx, -smy); |
|
660 } else if (capStyle == CAP_SQUARE) { |
|
661 emitLineTo(sx0 + smy - smx, sy0 - smx - smy); |
|
662 emitLineTo(sx0 + smy + smx, sy0 - smx + smy); |
|
663 } |
|
664 } |
|
665 } |
|
666 } else { |
|
667 emitReverse(); |
|
668 } |
554 emitClose(); |
669 emitClose(); |
555 } |
670 } |
556 |
671 |
557 private void emitMoveTo(final float x0, final float y0) { |
672 private void emitMoveTo(final float x0, final float y0) { |
558 out.moveTo(x0, y0); |
673 out.moveTo(x0, y0); |
620 |
735 |
621 private void drawJoin(float pdx, float pdy, |
736 private void drawJoin(float pdx, float pdy, |
622 float x0, float y0, |
737 float x0, float y0, |
623 float dx, float dy, |
738 float dx, float dy, |
624 float omx, float omy, |
739 float omx, float omy, |
625 float mx, float my) |
740 float mx, float my, |
|
741 final int outcode) |
626 { |
742 { |
627 if (prev != DRAWING_OP_TO) { |
743 if (prev != DRAWING_OP_TO) { |
628 emitMoveTo(x0 + mx, y0 + my); |
744 emitMoveTo(x0 + mx, y0 + my); |
629 this.sdx = dx; |
745 if (!opened) { |
630 this.sdy = dy; |
746 this.sdx = dx; |
631 this.smx = mx; |
747 this.sdy = dy; |
632 this.smy = my; |
748 this.smx = mx; |
|
749 this.smy = my; |
|
750 } |
633 } else { |
751 } else { |
634 boolean cw = isCW(pdx, pdy, dx, dy); |
752 final boolean cw = isCW(pdx, pdy, dx, dy); |
635 if (joinStyle == JOIN_MITER) { |
753 if (outcode == 0) { |
636 drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw); |
754 if (joinStyle == JOIN_MITER) { |
637 } else if (joinStyle == JOIN_ROUND) { |
755 drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw); |
638 drawRoundJoin(x0, y0, |
756 } else if (joinStyle == JOIN_ROUND) { |
639 omx, omy, |
757 drawRoundJoin(x0, y0, |
640 mx, my, cw, |
758 omx, omy, |
641 ROUND_JOIN_THRESHOLD); |
759 mx, my, cw, |
|
760 ROUND_JOIN_THRESHOLD); |
|
761 } |
642 } |
762 } |
643 emitLineTo(x0, y0, !cw); |
763 emitLineTo(x0, y0, !cw); |
644 } |
764 } |
645 prev = DRAWING_OP_TO; |
765 prev = DRAWING_OP_TO; |
646 } |
766 } |
941 ret = Helpers.filterOutNotInAB(ts, 0, ret, 0.0001f, 0.9999f); |
1061 ret = Helpers.filterOutNotInAB(ts, 0, ret, 0.0001f, 0.9999f); |
942 Helpers.isort(ts, 0, ret); |
1062 Helpers.isort(ts, 0, ret); |
943 return ret; |
1063 return ret; |
944 } |
1064 } |
945 |
1065 |
946 @Override public void curveTo(float x1, float y1, |
1066 @Override |
947 float x2, float y2, |
1067 public void curveTo(final float x1, final float y1, |
948 float x3, float y3) |
1068 final float x2, final float y2, |
949 { |
1069 final float x3, final float y3) |
|
1070 { |
|
1071 final int outcode0 = this.cOutCode; |
|
1072 if (clipRect != null) { |
|
1073 final int outcode3 = Helpers.outcode(x3, y3, clipRect); |
|
1074 this.cOutCode = outcode3; |
|
1075 |
|
1076 if ((outcode0 & outcode3) != 0) { |
|
1077 final int outcode1 = Helpers.outcode(x1, y1, clipRect); |
|
1078 final int outcode2 = Helpers.outcode(x2, y2, clipRect); |
|
1079 |
|
1080 // basic rejection criteria |
|
1081 if ((outcode0 & outcode1 & outcode2 & outcode3) != 0) { |
|
1082 moveTo(x3, y3, outcode0); |
|
1083 opened = true; |
|
1084 return; |
|
1085 } |
|
1086 } |
|
1087 } |
|
1088 |
950 final float[] mid = middle; |
1089 final float[] mid = middle; |
951 |
1090 |
952 mid[0] = cx0; mid[1] = cy0; |
1091 mid[0] = cx0; mid[1] = cy0; |
953 mid[2] = x1; mid[3] = y1; |
1092 mid[2] = x1; mid[3] = y1; |
954 mid[4] = x2; mid[5] = y2; |
1093 mid[4] = x2; mid[5] = y2; |
955 mid[6] = x3; mid[7] = y3; |
1094 mid[6] = x3; mid[7] = y3; |
956 |
1095 |
957 // need these so we can update the state at the end of this method |
1096 // need these so we can update the state at the end of this method |
958 final float xf = mid[6], yf = mid[7]; |
1097 final float xf = x3, yf = y3; |
959 float dxs = mid[2] - mid[0]; |
1098 float dxs = mid[2] - mid[0]; |
960 float dys = mid[3] - mid[1]; |
1099 float dys = mid[3] - mid[1]; |
961 float dxf = mid[6] - mid[4]; |
1100 float dxf = mid[6] - mid[4]; |
962 float dyf = mid[7] - mid[5]; |
1101 float dyf = mid[7] - mid[5]; |
963 |
1102 |
997 dxf /= len; |
1140 dxf /= len; |
998 dyf /= len; |
1141 dyf /= len; |
999 } |
1142 } |
1000 |
1143 |
1001 computeOffset(dxs, dys, lineWidth2, offset0); |
1144 computeOffset(dxs, dys, lineWidth2, offset0); |
1002 drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]); |
1145 drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0); |
1003 |
1146 |
1004 final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2); |
1147 final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2); |
1005 |
1148 |
1006 float prevT = 0.0f; |
1149 float prevT = 0.0f; |
1007 for (int i = 0, off = 0; i < nSplits; i++, off += 6) { |
1150 for (int i = 0, off = 0; i < nSplits; i++, off += 6) { |
1032 default: |
1175 default: |
1033 } |
1176 } |
1034 emitLineToRev(r[kind - 2], r[kind - 1]); |
1177 emitLineToRev(r[kind - 2], r[kind - 1]); |
1035 } |
1178 } |
1036 |
1179 |
|
1180 this.prev = DRAWING_OP_TO; |
|
1181 this.cx0 = xf; |
|
1182 this.cy0 = yf; |
|
1183 this.cdx = dxf; |
|
1184 this.cdy = dyf; |
1037 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f; |
1185 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f; |
1038 this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f; |
1186 this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f; |
1039 this.cdx = dxf; |
1187 } |
1040 this.cdy = dyf; |
1188 |
1041 this.cx0 = xf; |
1189 @Override |
1042 this.cy0 = yf; |
1190 public void quadTo(final float x1, final float y1, |
1043 this.prev = DRAWING_OP_TO; |
1191 final float x2, final float y2) |
1044 } |
1192 { |
1045 |
1193 final int outcode0 = this.cOutCode; |
1046 @Override public void quadTo(float x1, float y1, float x2, float y2) { |
1194 if (clipRect != null) { |
|
1195 final int outcode2 = Helpers.outcode(x2, y2, clipRect); |
|
1196 this.cOutCode = outcode2; |
|
1197 |
|
1198 if ((outcode0 & outcode2) != 0) { |
|
1199 final int outcode1 = Helpers.outcode(x1, y1, clipRect); |
|
1200 |
|
1201 // basic rejection criteria |
|
1202 if ((outcode0 & outcode1 & outcode2) != 0) { |
|
1203 moveTo(x2, y2, outcode0); |
|
1204 opened = true; |
|
1205 return; |
|
1206 } |
|
1207 } |
|
1208 } |
|
1209 |
1047 final float[] mid = middle; |
1210 final float[] mid = middle; |
1048 |
1211 |
1049 mid[0] = cx0; mid[1] = cy0; |
1212 mid[0] = cx0; mid[1] = cy0; |
1050 mid[2] = x1; mid[3] = y1; |
1213 mid[2] = x1; mid[3] = y1; |
1051 mid[4] = x2; mid[5] = y2; |
1214 mid[4] = x2; mid[5] = y2; |
1052 |
1215 |
1053 // need these so we can update the state at the end of this method |
1216 // need these so we can update the state at the end of this method |
1054 final float xf = mid[4], yf = mid[5]; |
1217 final float xf = x2, yf = y2; |
1055 float dxs = mid[2] - mid[0]; |
1218 float dxs = mid[2] - mid[0]; |
1056 float dys = mid[3] - mid[1]; |
1219 float dys = mid[3] - mid[1]; |
1057 float dxf = mid[4] - mid[2]; |
1220 float dxf = mid[4] - mid[2]; |
1058 float dyf = mid[5] - mid[3]; |
1221 float dyf = mid[5] - mid[3]; |
1059 if ((dxs == 0.0f && dys == 0.0f) || (dxf == 0.0f && dyf == 0.0f)) { |
1222 if ((dxs == 0.0f && dys == 0.0f) || (dxf == 0.0f && dyf == 0.0f)) { |
1060 dxs = dxf = mid[4] - mid[0]; |
1223 dxs = dxf = mid[4] - mid[0]; |
1061 dys = dyf = mid[5] - mid[1]; |
1224 dys = dyf = mid[5] - mid[1]; |
1062 } |
1225 } |
1063 if (dxs == 0.0f && dys == 0.0f) { |
1226 if (dxs == 0.0f && dys == 0.0f) { |
1064 // this happens if the "curve" is just a point |
1227 // this happens if the "curve" is just a point |
|
1228 // fix outcode0 for lineTo() call: |
|
1229 if (clipRect != null) { |
|
1230 this.cOutCode = outcode0; |
|
1231 } |
1065 lineTo(mid[0], mid[1]); |
1232 lineTo(mid[0], mid[1]); |
1066 return; |
1233 return; |
1067 } |
1234 } |
1068 // if these vectors are too small, normalize them, to avoid future |
1235 // if these vectors are too small, normalize them, to avoid future |
1069 // precision problems. |
1236 // precision problems. |
1077 dxf /= len; |
1244 dxf /= len; |
1078 dyf /= len; |
1245 dyf /= len; |
1079 } |
1246 } |
1080 |
1247 |
1081 computeOffset(dxs, dys, lineWidth2, offset0); |
1248 computeOffset(dxs, dys, lineWidth2, offset0); |
1082 drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]); |
1249 drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0); |
1083 |
1250 |
1084 int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2); |
1251 int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2); |
1085 |
1252 |
1086 float prevt = 0.0f; |
1253 float prevt = 0.0f; |
1087 for (int i = 0, off = 0; i < nSplits; i++, off += 4) { |
1254 for (int i = 0, off = 0; i < nSplits; i++, off += 4) { |
1112 default: |
1279 default: |
1113 } |
1280 } |
1114 emitLineToRev(r[kind - 2], r[kind - 1]); |
1281 emitLineToRev(r[kind - 2], r[kind - 1]); |
1115 } |
1282 } |
1116 |
1283 |
|
1284 this.prev = DRAWING_OP_TO; |
|
1285 this.cx0 = xf; |
|
1286 this.cy0 = yf; |
|
1287 this.cdx = dxf; |
|
1288 this.cdy = dyf; |
1117 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f; |
1289 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f; |
1118 this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f; |
1290 this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f; |
1119 this.cdx = dxf; |
|
1120 this.cdy = dyf; |
|
1121 this.cx0 = xf; |
|
1122 this.cy0 = yf; |
|
1123 this.prev = DRAWING_OP_TO; |
|
1124 } |
1291 } |
1125 |
1292 |
1126 @Override public long getNativeConsumer() { |
1293 @Override public long getNativeConsumer() { |
1127 throw new InternalError("Stroker doesn't use a native consumer"); |
1294 throw new InternalError("Stroker doesn't use a native consumer"); |
1128 } |
1295 } |
1129 |
|
1130 // a stack of polynomial curves where each curve shares endpoints with |
|
1131 // adjacent ones. |
|
1132 static final class PolyStack { |
|
1133 private static final byte TYPE_LINETO = (byte) 0; |
|
1134 private static final byte TYPE_QUADTO = (byte) 1; |
|
1135 private static final byte TYPE_CUBICTO = (byte) 2; |
|
1136 |
|
1137 // curves capacity = edges count (8192) = edges x 2 (coords) |
|
1138 private static final int INITIAL_CURVES_COUNT = INITIAL_EDGES_COUNT << 1; |
|
1139 |
|
1140 // types capacity = edges count (4096) |
|
1141 private static final int INITIAL_TYPES_COUNT = INITIAL_EDGES_COUNT; |
|
1142 |
|
1143 float[] curves; |
|
1144 int end; |
|
1145 byte[] curveTypes; |
|
1146 int numCurves; |
|
1147 |
|
1148 // per-thread renderer context |
|
1149 final RendererContext rdrCtx; |
|
1150 |
|
1151 // curves ref (dirty) |
|
1152 final FloatArrayCache.Reference curves_ref; |
|
1153 // curveTypes ref (dirty) |
|
1154 final ByteArrayCache.Reference curveTypes_ref; |
|
1155 |
|
1156 // used marks (stats only) |
|
1157 int curveTypesUseMark; |
|
1158 int curvesUseMark; |
|
1159 |
|
1160 /** |
|
1161 * Constructor |
|
1162 * @param rdrCtx per-thread renderer context |
|
1163 */ |
|
1164 PolyStack(final RendererContext rdrCtx) { |
|
1165 this.rdrCtx = rdrCtx; |
|
1166 |
|
1167 curves_ref = rdrCtx.newDirtyFloatArrayRef(INITIAL_CURVES_COUNT); // 32K |
|
1168 curves = curves_ref.initial; |
|
1169 |
|
1170 curveTypes_ref = rdrCtx.newDirtyByteArrayRef(INITIAL_TYPES_COUNT); // 4K |
|
1171 curveTypes = curveTypes_ref.initial; |
|
1172 numCurves = 0; |
|
1173 end = 0; |
|
1174 |
|
1175 if (DO_STATS) { |
|
1176 curveTypesUseMark = 0; |
|
1177 curvesUseMark = 0; |
|
1178 } |
|
1179 } |
|
1180 |
|
1181 /** |
|
1182 * Disposes this PolyStack: |
|
1183 * clean up before reusing this instance |
|
1184 */ |
|
1185 void dispose() { |
|
1186 end = 0; |
|
1187 numCurves = 0; |
|
1188 |
|
1189 if (DO_STATS) { |
|
1190 rdrCtx.stats.stat_rdr_poly_stack_types.add(curveTypesUseMark); |
|
1191 rdrCtx.stats.stat_rdr_poly_stack_curves.add(curvesUseMark); |
|
1192 rdrCtx.stats.hist_rdr_poly_stack_curves.add(curvesUseMark); |
|
1193 |
|
1194 // reset marks |
|
1195 curveTypesUseMark = 0; |
|
1196 curvesUseMark = 0; |
|
1197 } |
|
1198 |
|
1199 // Return arrays: |
|
1200 // curves and curveTypes are kept dirty |
|
1201 curves = curves_ref.putArray(curves); |
|
1202 curveTypes = curveTypes_ref.putArray(curveTypes); |
|
1203 } |
|
1204 |
|
1205 private void ensureSpace(final int n) { |
|
1206 // use substraction to avoid integer overflow: |
|
1207 if (curves.length - end < n) { |
|
1208 if (DO_STATS) { |
|
1209 rdrCtx.stats.stat_array_stroker_polystack_curves |
|
1210 .add(end + n); |
|
1211 } |
|
1212 curves = curves_ref.widenArray(curves, end, end + n); |
|
1213 } |
|
1214 if (curveTypes.length <= numCurves) { |
|
1215 if (DO_STATS) { |
|
1216 rdrCtx.stats.stat_array_stroker_polystack_curveTypes |
|
1217 .add(numCurves + 1); |
|
1218 } |
|
1219 curveTypes = curveTypes_ref.widenArray(curveTypes, |
|
1220 numCurves, |
|
1221 numCurves + 1); |
|
1222 } |
|
1223 } |
|
1224 |
|
1225 void pushCubic(float x0, float y0, |
|
1226 float x1, float y1, |
|
1227 float x2, float y2) |
|
1228 { |
|
1229 ensureSpace(6); |
|
1230 curveTypes[numCurves++] = TYPE_CUBICTO; |
|
1231 // we reverse the coordinate order to make popping easier |
|
1232 final float[] _curves = curves; |
|
1233 int e = end; |
|
1234 _curves[e++] = x2; _curves[e++] = y2; |
|
1235 _curves[e++] = x1; _curves[e++] = y1; |
|
1236 _curves[e++] = x0; _curves[e++] = y0; |
|
1237 end = e; |
|
1238 } |
|
1239 |
|
1240 void pushQuad(float x0, float y0, |
|
1241 float x1, float y1) |
|
1242 { |
|
1243 ensureSpace(4); |
|
1244 curveTypes[numCurves++] = TYPE_QUADTO; |
|
1245 final float[] _curves = curves; |
|
1246 int e = end; |
|
1247 _curves[e++] = x1; _curves[e++] = y1; |
|
1248 _curves[e++] = x0; _curves[e++] = y0; |
|
1249 end = e; |
|
1250 } |
|
1251 |
|
1252 void pushLine(float x, float y) { |
|
1253 ensureSpace(2); |
|
1254 curveTypes[numCurves++] = TYPE_LINETO; |
|
1255 curves[end++] = x; curves[end++] = y; |
|
1256 } |
|
1257 |
|
1258 void popAll(PathConsumer2D io) { |
|
1259 if (DO_STATS) { |
|
1260 // update used marks: |
|
1261 if (numCurves > curveTypesUseMark) { |
|
1262 curveTypesUseMark = numCurves; |
|
1263 } |
|
1264 if (end > curvesUseMark) { |
|
1265 curvesUseMark = end; |
|
1266 } |
|
1267 } |
|
1268 final byte[] _curveTypes = curveTypes; |
|
1269 final float[] _curves = curves; |
|
1270 int nc = numCurves; |
|
1271 int e = end; |
|
1272 |
|
1273 while (nc != 0) { |
|
1274 switch(_curveTypes[--nc]) { |
|
1275 case TYPE_LINETO: |
|
1276 e -= 2; |
|
1277 io.lineTo(_curves[e], _curves[e+1]); |
|
1278 continue; |
|
1279 case TYPE_QUADTO: |
|
1280 e -= 4; |
|
1281 io.quadTo(_curves[e+0], _curves[e+1], |
|
1282 _curves[e+2], _curves[e+3]); |
|
1283 continue; |
|
1284 case TYPE_CUBICTO: |
|
1285 e -= 6; |
|
1286 io.curveTo(_curves[e+0], _curves[e+1], |
|
1287 _curves[e+2], _curves[e+3], |
|
1288 _curves[e+4], _curves[e+5]); |
|
1289 continue; |
|
1290 default: |
|
1291 } |
|
1292 } |
|
1293 numCurves = 0; |
|
1294 end = 0; |
|
1295 } |
|
1296 |
|
1297 @Override |
|
1298 public String toString() { |
|
1299 String ret = ""; |
|
1300 int nc = numCurves; |
|
1301 int last = end; |
|
1302 int len; |
|
1303 while (nc != 0) { |
|
1304 switch(curveTypes[--nc]) { |
|
1305 case TYPE_LINETO: |
|
1306 len = 2; |
|
1307 ret += "line: "; |
|
1308 break; |
|
1309 case TYPE_QUADTO: |
|
1310 len = 4; |
|
1311 ret += "quad: "; |
|
1312 break; |
|
1313 case TYPE_CUBICTO: |
|
1314 len = 6; |
|
1315 ret += "cubic: "; |
|
1316 break; |
|
1317 default: |
|
1318 len = 0; |
|
1319 } |
|
1320 last -= len; |
|
1321 ret += Arrays.toString(Arrays.copyOfRange(curves, last, last+len)) |
|
1322 + "\n"; |
|
1323 } |
|
1324 return ret; |
|
1325 } |
|
1326 } |
|
1327 } |
1296 } |