11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package sun.java2d.marlin;
27
28 import java.util.Arrays;
29
30 import sun.awt.geom.PathConsumer2D;
31
32 // 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 // has methods like plus(Point), minus(Point), dot(Point), cross(Point)and such
35 final class Stroker implements PathConsumer2D, MarlinConst {
36
37 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 CLOSE = 2;
40
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
72 // 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.
74 private static final float ROUND_JOIN_THRESHOLD = 1000.0f/65536.0f;
75
76 private static final float C = 0.5522847498307933f;
77
78 private static final int MAX_N_CURVES = 11;
79
80 private PathConsumer2D out;
81
82 private int capStyle;
83 private int joinStyle;
84
85 private float lineWidth2;
86 private float invHalfLineWidth2Sq;
87
88 private final float[] offset0 = new float[2];
89 private final float[] offset1 = new float[2];
90 private final float[] offset2 = new float[2];
91 private final float[] miter = new float[2];
92 private float miterLimitSq;
93
94 private int prev;
95
96 // The starting point of the path, and the slope there.
103 // original path (thought they may have different directions), so these
104 // could be computed from sdx,sdy and cdx,cdy (and vice versa), but that
105 // would be error prone and hard to read, so we keep these anyway.
106 private float smx, smy, cmx, cmy;
107
108 private final PolyStack reverse;
109
110 // This is where the curve to be processed is put. We give it
111 // enough room to store all curves.
112 private final float[] middle = new float[MAX_N_CURVES * 6 + 2];
113 private final float[] lp = new float[8];
114 private final float[] rp = new float[8];
115 private final float[] subdivTs = new float[MAX_N_CURVES - 1];
116
117 // per-thread renderer context
118 final RendererContext rdrCtx;
119
120 // dirty curve
121 final Curve curve;
122
123 /**
124 * Constructs a <code>Stroker</code>.
125 * @param rdrCtx per-thread renderer context
126 */
127 Stroker(final RendererContext rdrCtx) {
128 this.rdrCtx = rdrCtx;
129
130 this.reverse = new PolyStack(rdrCtx);
131 this.curve = rdrCtx.curve;
132 }
133
134 /**
135 * Inits the <code>Stroker</code>.
136 *
137 * @param pc2d an output <code>PathConsumer2D</code>.
138 * @param lineWidth the desired line width in pixels
139 * @param capStyle the desired end cap style, one of
140 * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or
141 * <code>CAP_SQUARE</code>.
142 * @param joinStyle the desired line join style, one of
143 * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or
144 * <code>JOIN_BEVEL</code>.
145 * @param miterLimit the desired miter limit
146 * @return this instance
147 */
148 Stroker init(PathConsumer2D pc2d,
149 float lineWidth,
150 int capStyle,
151 int joinStyle,
152 float miterLimit)
153 {
154 this.out = pc2d;
155
156 this.lineWidth2 = lineWidth / 2.0f;
157 this.invHalfLineWidth2Sq = 1.0f / (2.0f * lineWidth2 * lineWidth2);
158 this.capStyle = capStyle;
159 this.joinStyle = joinStyle;
160
161 float limit = miterLimit * lineWidth2;
162 this.miterLimitSq = limit * limit;
163
164 this.prev = CLOSE;
165
166 rdrCtx.stroking = 1;
167
168 return this; // fluent API
169 }
170
171 /**
172 * Disposes this stroker:
173 * clean up before reusing this instance
174 */
175 void dispose() {
176 reverse.dispose();
177
178 if (DO_CLEAN_DIRTY) {
179 // Force zero-fill dirty arrays:
180 Arrays.fill(offset0, 0.0f);
181 Arrays.fill(offset1, 0.0f);
182 Arrays.fill(offset2, 0.0f);
183 Arrays.fill(miter, 0.0f);
184 Arrays.fill(middle, 0.0f);
185 Arrays.fill(lp, 0.0f);
186 Arrays.fill(rp, 0.0f);
187 Arrays.fill(subdivTs, 0.0f);
188 }
189 }
190
191 private static void computeOffset(final float lx, final float ly,
192 final float w, final float[] m)
193 {
194 float len = lx*lx + ly*ly;
195 if (len == 0.0f) {
196 m[0] = 0.0f;
197 m[1] = 0.0f;
428
429 computeMiter((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy,
430 (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my,
431 miter, 0);
432
433 final float miterX = miter[0];
434 final float miterY = miter[1];
435 float lenSq = (miterX-x0)*(miterX-x0) + (miterY-y0)*(miterY-y0);
436
437 // If the lines are parallel, lenSq will be either NaN or +inf
438 // (actually, I'm not sure if the latter is possible. The important
439 // thing is that -inf is not possible, because lenSq is a square).
440 // For both of those values, the comparison below will fail and
441 // no miter will be drawn, which is correct.
442 if (lenSq < miterLimitSq) {
443 emitLineTo(miterX, miterY, rev);
444 }
445 }
446
447 @Override
448 public void moveTo(float x0, float y0) {
449 if (prev == DRAWING_OP_TO) {
450 finish();
451 }
452 this.sx0 = this.cx0 = x0;
453 this.sy0 = this.cy0 = y0;
454 this.cdx = this.sdx = 1.0f;
455 this.cdy = this.sdy = 0.0f;
456 this.prev = MOVE_TO;
457 }
458
459 @Override
460 public void lineTo(float x1, float y1) {
461 float dx = x1 - cx0;
462 float dy = y1 - cy0;
463 if (dx == 0.0f && dy == 0.0f) {
464 dx = 1.0f;
465 }
466 computeOffset(dx, dy, lineWidth2, offset0);
467 final float mx = offset0[0];
468 final float my = offset0[1];
469
470 drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my);
471
472 emitLineTo(cx0 + mx, cy0 + my);
473 emitLineTo( x1 + mx, y1 + my);
474
475 emitLineToRev(cx0 - mx, cy0 - my);
476 emitLineToRev( x1 - mx, y1 - my);
477
478 this.cmx = mx;
479 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 }
486
487 @Override
488 public void closePath() {
489 if (prev != DRAWING_OP_TO) {
490 if (prev == CLOSE) {
491 return;
492 }
493 emitMoveTo(cx0, cy0 - lineWidth2);
494 this.cmx = this.smx = 0.0f;
495 this.cmy = this.smy = -lineWidth2;
496 this.cdx = this.sdx = 1.0f;
497 this.cdy = this.sdy = 0.0f;
498 finish();
499 return;
500 }
501
502 if (cx0 != sx0 || cy0 != sy0) {
503 lineTo(sx0, sy0);
504 }
505
506 drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy);
507
508 emitLineTo(sx0 + smx, sy0 + smy);
509
510 emitMoveTo(sx0 - smx, sy0 - smy);
511 emitReverse();
512
513 this.prev = CLOSE;
514 emitClose();
515 }
516
517 private void emitReverse() {
518 reverse.popAll(out);
519 }
520
521 @Override
522 public void pathDone() {
523 if (prev == DRAWING_OP_TO) {
524 finish();
525 }
526
527 out.pathDone();
528
529 // this shouldn't matter since this object won't be used
530 // after the call to this method.
531 this.prev = CLOSE;
532
533 // Dispose this instance:
534 dispose();
535 }
536
537 private void finish() {
538 if (capStyle == CAP_ROUND) {
539 drawRoundCap(cx0, cy0, cmx, cmy);
540 } else if (capStyle == CAP_SQUARE) {
541 emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy);
542 emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy);
543 }
544
545 emitReverse();
546
547 if (capStyle == CAP_ROUND) {
548 drawRoundCap(sx0, sy0, -smx, -smy);
549 } else if (capStyle == CAP_SQUARE) {
550 emitLineTo(sx0 + smy - smx, sy0 - smx - smy);
551 emitLineTo(sx0 + smy + smx, sy0 - smx + smy);
552 }
553
554 emitClose();
555 }
556
557 private void emitMoveTo(final float x0, final float y0) {
558 out.moveTo(x0, y0);
559 }
560
561 private void emitLineTo(final float x1, final float y1) {
562 out.lineTo(x1, y1);
563 }
564
565 private void emitLineToRev(final float x1, final float y1) {
566 reverse.pushLine(x1, y1);
567 }
568
569 private void emitLineTo(final float x1, final float y1,
570 final boolean rev)
571 {
572 if (rev) {
573 emitLineToRev(x1, y1);
605 private void emitCurveTo(final float x0, final float y0,
606 final float x1, final float y1,
607 final float x2, final float y2,
608 final float x3, final float y3, final boolean rev)
609 {
610 if (rev) {
611 reverse.pushCubic(x0, y0, x1, y1, x2, y2);
612 } else {
613 out.curveTo(x1, y1, x2, y2, x3, y3);
614 }
615 }
616
617 private void emitClose() {
618 out.closePath();
619 }
620
621 private void drawJoin(float pdx, float pdy,
622 float x0, float y0,
623 float dx, float dy,
624 float omx, float omy,
625 float mx, float my)
626 {
627 if (prev != DRAWING_OP_TO) {
628 emitMoveTo(x0 + mx, y0 + my);
629 this.sdx = dx;
630 this.sdy = dy;
631 this.smx = mx;
632 this.smy = my;
633 } else {
634 boolean cw = isCW(pdx, pdy, dx, dy);
635 if (joinStyle == JOIN_MITER) {
636 drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
637 } else if (joinStyle == JOIN_ROUND) {
638 drawRoundJoin(x0, y0,
639 omx, omy,
640 mx, my, cw,
641 ROUND_JOIN_THRESHOLD);
642 }
643 emitLineTo(x0, y0, !cw);
644 }
645 prev = DRAWING_OP_TO;
646 }
647
648 private static boolean within(final float x1, final float y1,
649 final float x2, final float y2,
650 final float ERR)
651 {
652 assert ERR > 0 : "";
653 // compare taxicab distance. ERR will always be small, so using
654 // true distance won't give much benefit
655 return (Helpers.within(x1, x2, ERR) && // we want to avoid calling Math.abs
656 Helpers.within(y1, y2, ERR)); // this is just as good.
657 }
658
659 private void getLineOffsets(float x1, float y1,
660 float x2, float y2,
661 float[] left, float[] right) {
926 int ret = 0;
927 // we subdivide at values of t such that the remaining rotated
928 // curves are monotonic in x and y.
929 ret += c.dxRoots(ts, ret);
930 ret += c.dyRoots(ts, ret);
931 // subdivide at inflection points.
932 if (type == 8) {
933 // quadratic curves can't have inflection points
934 ret += c.infPoints(ts, ret);
935 }
936
937 // now we must subdivide at points where one of the offset curves will have
938 // a cusp. This happens at ts where the radius of curvature is equal to w.
939 ret += c.rootsOfROCMinusW(ts, ret, w, 0.0001f);
940
941 ret = Helpers.filterOutNotInAB(ts, 0, ret, 0.0001f, 0.9999f);
942 Helpers.isort(ts, 0, ret);
943 return ret;
944 }
945
946 @Override public void curveTo(float x1, float y1,
947 float x2, float y2,
948 float x3, float y3)
949 {
950 final float[] mid = middle;
951
952 mid[0] = cx0; mid[1] = cy0;
953 mid[2] = x1; mid[3] = y1;
954 mid[4] = x2; mid[5] = y2;
955 mid[6] = x3; mid[7] = y3;
956
957 // need these so we can update the state at the end of this method
958 final float xf = mid[6], yf = mid[7];
959 float dxs = mid[2] - mid[0];
960 float dys = mid[3] - mid[1];
961 float dxf = mid[6] - mid[4];
962 float dyf = mid[7] - mid[5];
963
964 boolean p1eqp2 = (dxs == 0.0f && dys == 0.0f);
965 boolean p3eqp4 = (dxf == 0.0f && dyf == 0.0f);
966 if (p1eqp2) {
967 dxs = mid[4] - mid[0];
968 dys = mid[5] - mid[1];
969 if (dxs == 0.0f && dys == 0.0f) {
970 dxs = mid[6] - mid[0];
971 dys = mid[7] - mid[1];
972 }
973 }
974 if (p3eqp4) {
975 dxf = mid[6] - mid[2];
976 dyf = mid[7] - mid[3];
977 if (dxf == 0.0f && dyf == 0.0f) {
978 dxf = mid[6] - mid[0];
979 dyf = mid[7] - mid[1];
980 }
981 }
982 if (dxs == 0.0f && dys == 0.0f) {
983 // this happens if the "curve" is just a point
984 lineTo(mid[0], mid[1]);
985 return;
986 }
987
988 // if these vectors are too small, normalize them, to avoid future
989 // precision problems.
990 if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) {
991 float len = (float) Math.sqrt(dxs*dxs + dys*dys);
992 dxs /= len;
993 dys /= len;
994 }
995 if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) {
996 float len = (float) Math.sqrt(dxf*dxf + dyf*dyf);
997 dxf /= len;
998 dyf /= len;
999 }
1000
1001 computeOffset(dxs, dys, lineWidth2, offset0);
1002 drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]);
1003
1004 final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2);
1005
1006 float prevT = 0.0f;
1007 for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
1008 final float t = subdivTs[i];
1009 Helpers.subdivideCubicAt((t - prevT) / (1.0f - prevT),
1010 mid, off, mid, off, mid, off + 6);
1011 prevT = t;
1012 }
1013
1014 final float[] l = lp;
1015 final float[] r = rp;
1016
1017 int kind = 0;
1018 for (int i = 0, off = 0; i <= nSplits; i++, off += 6) {
1019 kind = computeOffsetCubic(mid, off, l, r);
1020
1021 emitLineTo(l[0], l[1]);
1022
1023 switch(kind) {
1024 case 8:
1025 emitCurveTo(l[2], l[3], l[4], l[5], l[6], l[7]);
1026 emitCurveToRev(r[0], r[1], r[2], r[3], r[4], r[5]);
1027 break;
1028 case 4:
1029 emitLineTo(l[2], l[3]);
1030 emitLineToRev(r[0], r[1]);
1031 break;
1032 default:
1033 }
1034 emitLineToRev(r[kind - 2], r[kind - 1]);
1035 }
1036
1037 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
1038 this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f;
1039 this.cdx = dxf;
1040 this.cdy = dyf;
1041 this.cx0 = xf;
1042 this.cy0 = yf;
1043 this.prev = DRAWING_OP_TO;
1044 }
1045
1046 @Override public void quadTo(float x1, float y1, float x2, float y2) {
1047 final float[] mid = middle;
1048
1049 mid[0] = cx0; mid[1] = cy0;
1050 mid[2] = x1; mid[3] = y1;
1051 mid[4] = x2; mid[5] = y2;
1052
1053 // need these so we can update the state at the end of this method
1054 final float xf = mid[4], yf = mid[5];
1055 float dxs = mid[2] - mid[0];
1056 float dys = mid[3] - mid[1];
1057 float dxf = mid[4] - mid[2];
1058 float dyf = mid[5] - mid[3];
1059 if ((dxs == 0.0f && dys == 0.0f) || (dxf == 0.0f && dyf == 0.0f)) {
1060 dxs = dxf = mid[4] - mid[0];
1061 dys = dyf = mid[5] - mid[1];
1062 }
1063 if (dxs == 0.0f && dys == 0.0f) {
1064 // this happens if the "curve" is just a point
1065 lineTo(mid[0], mid[1]);
1066 return;
1067 }
1068 // if these vectors are too small, normalize them, to avoid future
1069 // precision problems.
1070 if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) {
1071 float len = (float) Math.sqrt(dxs*dxs + dys*dys);
1072 dxs /= len;
1073 dys /= len;
1074 }
1075 if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) {
1076 float len = (float) Math.sqrt(dxf*dxf + dyf*dyf);
1077 dxf /= len;
1078 dyf /= len;
1079 }
1080
1081 computeOffset(dxs, dys, lineWidth2, offset0);
1082 drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]);
1083
1084 int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2);
1085
1086 float prevt = 0.0f;
1087 for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
1088 final float t = subdivTs[i];
1089 Helpers.subdivideQuadAt((t - prevt) / (1.0f - prevt),
1090 mid, off, mid, off, mid, off + 4);
1091 prevt = t;
1092 }
1093
1094 final float[] l = lp;
1095 final float[] r = rp;
1096
1097 int kind = 0;
1098 for (int i = 0, off = 0; i <= nSplits; i++, off += 4) {
1099 kind = computeOffsetQuad(mid, off, l, r);
1100
1101 emitLineTo(l[0], l[1]);
1102
1103 switch(kind) {
1104 case 6:
1105 emitQuadTo(l[2], l[3], l[4], l[5]);
1106 emitQuadToRev(r[0], r[1], r[2], r[3]);
1107 break;
1108 case 4:
1109 emitLineTo(l[2], l[3]);
1110 emitLineToRev(r[0], r[1]);
1111 break;
1112 default:
1113 }
1114 emitLineToRev(r[kind - 2], r[kind - 1]);
1115 }
1116
1117 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
1118 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 }
1125
1126 @Override public long getNativeConsumer() {
1127 throw new InternalError("Stroker doesn't use a native consumer");
1128 }
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 }
|
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package sun.java2d.marlin;
27
28 import java.util.Arrays;
29
30 import sun.awt.geom.PathConsumer2D;
31 import sun.java2d.marlin.Helpers.PolyStack;
32
33 // TODO: some of the arithmetic here is too verbose and prone to hard to
34 // debug typos. We should consider making a small Point/Vector class that
35 // has methods like plus(Point), minus(Point), dot(Point), cross(Point)and such
36 final class Stroker implements PathConsumer2D, MarlinConst {
37
38 private static final int MOVE_TO = 0;
39 private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad
40 private static final int CLOSE = 2;
41
42 // pisces used to use fixed point arithmetic with 16 decimal digits. I
43 // didn't want to change the values of the constant below when I converted
44 // it to floating point, so that's why the divisions by 2^16 are there.
45 private static final float ROUND_JOIN_THRESHOLD = 1000.0f/65536.0f;
46
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);
52
53 private static final int MAX_N_CURVES = 11;
54
55 private PathConsumer2D out;
56
57 private int capStyle;
58 private int joinStyle;
59
60 private float lineWidth2;
61 private float invHalfLineWidth2Sq;
62
63 private final float[] offset0 = new float[2];
64 private final float[] offset1 = new float[2];
65 private final float[] offset2 = new float[2];
66 private final float[] miter = new float[2];
67 private float miterLimitSq;
68
69 private int prev;
70
71 // The starting point of the path, and the slope there.
78 // original path (thought they may have different directions), so these
79 // could be computed from sdx,sdy and cdx,cdy (and vice versa), but that
80 // would be error prone and hard to read, so we keep these anyway.
81 private float smx, smy, cmx, cmy;
82
83 private final PolyStack reverse;
84
85 // This is where the curve to be processed is put. We give it
86 // enough room to store all curves.
87 private final float[] middle = new float[MAX_N_CURVES * 6 + 2];
88 private final float[] lp = new float[8];
89 private final float[] rp = new float[8];
90 private final float[] subdivTs = new float[MAX_N_CURVES - 1];
91
92 // per-thread renderer context
93 final RendererContext rdrCtx;
94
95 // dirty curve
96 final Curve curve;
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
112 /**
113 * Constructs a <code>Stroker</code>.
114 * @param rdrCtx per-thread renderer context
115 */
116 Stroker(final RendererContext rdrCtx) {
117 this.rdrCtx = rdrCtx;
118
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
128 this.curve = rdrCtx.curve;
129 }
130
131 /**
132 * Inits the <code>Stroker</code>.
133 *
134 * @param pc2d an output <code>PathConsumer2D</code>.
135 * @param lineWidth the desired line width in pixels
136 * @param capStyle the desired end cap style, one of
137 * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or
138 * <code>CAP_SQUARE</code>.
139 * @param joinStyle the desired line join style, one of
140 * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or
141 * <code>JOIN_BEVEL</code>.
142 * @param miterLimit the desired miter limit
143 * @param scale scaling factor applied to clip boundaries
144 * @return this instance
145 */
146 Stroker init(final PathConsumer2D pc2d,
147 final float lineWidth,
148 final int capStyle,
149 final int joinStyle,
150 final float miterLimit,
151 final float scale)
152 {
153 this.out = pc2d;
154
155 this.lineWidth2 = lineWidth / 2.0f;
156 this.invHalfLineWidth2Sq = 1.0f / (2.0f * lineWidth2 * lineWidth2);
157 this.capStyle = capStyle;
158 this.joinStyle = joinStyle;
159
160 final float limit = miterLimit * lineWidth2;
161 this.miterLimitSq = limit * limit;
162
163 this.prev = CLOSE;
164
165 rdrCtx.stroking = 1;
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 }
199 return this; // fluent API
200 }
201
202 /**
203 * Disposes this stroker:
204 * clean up before reusing this instance
205 */
206 void dispose() {
207 reverse.dispose();
208
209 opened = false;
210 capStart = false;
211
212 if (DO_CLEAN_DIRTY) {
213 // Force zero-fill dirty arrays:
214 Arrays.fill(offset0, 0.0f);
215 Arrays.fill(offset1, 0.0f);
216 Arrays.fill(offset2, 0.0f);
217 Arrays.fill(miter, 0.0f);
218 Arrays.fill(middle, 0.0f);
219 Arrays.fill(lp, 0.0f);
220 Arrays.fill(rp, 0.0f);
221 Arrays.fill(subdivTs, 0.0f);
222 }
223 }
224
225 private static void computeOffset(final float lx, final float ly,
226 final float w, final float[] m)
227 {
228 float len = lx*lx + ly*ly;
229 if (len == 0.0f) {
230 m[0] = 0.0f;
231 m[1] = 0.0f;
462
463 computeMiter((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy,
464 (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my,
465 miter, 0);
466
467 final float miterX = miter[0];
468 final float miterY = miter[1];
469 float lenSq = (miterX-x0)*(miterX-x0) + (miterY-y0)*(miterY-y0);
470
471 // If the lines are parallel, lenSq will be either NaN or +inf
472 // (actually, I'm not sure if the latter is possible. The important
473 // thing is that -inf is not possible, because lenSq is a square).
474 // For both of those values, the comparison below will fail and
475 // no miter will be drawn, which is correct.
476 if (lenSq < miterLimitSq) {
477 emitLineTo(miterX, miterY, rev);
478 }
479 }
480
481 @Override
482 public void moveTo(final float x0, final float y0) {
483 moveTo(x0, y0, cOutCode);
484 // update starting point:
485 this.sx0 = x0;
486 this.sy0 = y0;
487 this.sdx = 1.0f;
488 this.sdy = 0.0f;
489 this.opened = false;
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 }
515 }
516
517 @Override
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
538 float dx = x1 - cx0;
539 float dy = y1 - cy0;
540 if (dx == 0.0f && dy == 0.0f) {
541 dx = 1.0f;
542 }
543 computeOffset(dx, dy, lineWidth2, offset0);
544 final float mx = offset0[0];
545 final float my = offset0[1];
546
547 drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my, outcode0);
548
549 emitLineTo(cx0 + mx, cy0 + my);
550 emitLineTo( x1 + mx, y1 + my);
551
552 emitLineToRev(cx0 - mx, cy0 - my);
553 emitLineToRev( x1 - mx, y1 - my);
554
555 this.prev = DRAWING_OP_TO;
556 this.cx0 = x1;
557 this.cy0 = y1;
558 this.cdx = dx;
559 this.cdy = dy;
560 this.cmx = mx;
561 this.cmy = my;
562 }
563
564 @Override
565 public void closePath() {
566 // distinguish empty path at all vs opened path ?
567 if (prev != DRAWING_OP_TO && !opened) {
568 if (prev == CLOSE) {
569 return;
570 }
571 emitMoveTo(cx0, cy0 - lineWidth2);
572
573 this.sdx = 1.0f;
574 this.sdy = 0.0f;
575 this.cdx = 1.0f;
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);
584 return;
585 }
586
587 // basic acceptance criteria
588 if ((sOutCode & cOutCode) == 0) {
589 if (cx0 != sx0 || cy0 != sy0) {
590 lineTo(sx0, sy0, true);
591 }
592
593 drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy, sOutCode);
594
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)
604 emitReverse();
605
606 this.prev = CLOSE;
607
608 if (opened) {
609 // do not emit close
610 opened = false;
611 } else {
612 emitClose();
613 }
614 }
615
616 private void emitReverse() {
617 reverse.popAll(out);
618 }
619
620 @Override
621 public void pathDone() {
622 if (prev == DRAWING_OP_TO) {
623 finish(cOutCode);
624 }
625
626 out.pathDone();
627
628 // this shouldn't matter since this object won't be used
629 // after the call to this method.
630 this.prev = CLOSE;
631
632 // Dispose this instance:
633 dispose();
634 }
635
636 private void finish(final int outcode) {
637 // Problem: impossible to guess if the path will be closed in advance
638 // i.e. if caps must be drawn or not ?
639 // Solution: use the ClosedPathDetector before Stroker to determine
640 // if the path is a closed path or not
641 if (!rdrCtx.closedPath) {
642 if (outcode == 0) {
643 // current point = end's cap:
644 if (capStyle == CAP_ROUND) {
645 drawRoundCap(cx0, cy0, cmx, cmy);
646 } else if (capStyle == CAP_SQUARE) {
647 emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy);
648 emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy);
649 }
650 }
651 emitReverse();
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 }
669 emitClose();
670 }
671
672 private void emitMoveTo(final float x0, final float y0) {
673 out.moveTo(x0, y0);
674 }
675
676 private void emitLineTo(final float x1, final float y1) {
677 out.lineTo(x1, y1);
678 }
679
680 private void emitLineToRev(final float x1, final float y1) {
681 reverse.pushLine(x1, y1);
682 }
683
684 private void emitLineTo(final float x1, final float y1,
685 final boolean rev)
686 {
687 if (rev) {
688 emitLineToRev(x1, y1);
720 private void emitCurveTo(final float x0, final float y0,
721 final float x1, final float y1,
722 final float x2, final float y2,
723 final float x3, final float y3, final boolean rev)
724 {
725 if (rev) {
726 reverse.pushCubic(x0, y0, x1, y1, x2, y2);
727 } else {
728 out.curveTo(x1, y1, x2, y2, x3, y3);
729 }
730 }
731
732 private void emitClose() {
733 out.closePath();
734 }
735
736 private void drawJoin(float pdx, float pdy,
737 float x0, float y0,
738 float dx, float dy,
739 float omx, float omy,
740 float mx, float my,
741 final int outcode)
742 {
743 if (prev != DRAWING_OP_TO) {
744 emitMoveTo(x0 + mx, y0 + my);
745 if (!opened) {
746 this.sdx = dx;
747 this.sdy = dy;
748 this.smx = mx;
749 this.smy = my;
750 }
751 } else {
752 final boolean cw = isCW(pdx, pdy, dx, dy);
753 if (outcode == 0) {
754 if (joinStyle == JOIN_MITER) {
755 drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
756 } else if (joinStyle == JOIN_ROUND) {
757 drawRoundJoin(x0, y0,
758 omx, omy,
759 mx, my, cw,
760 ROUND_JOIN_THRESHOLD);
761 }
762 }
763 emitLineTo(x0, y0, !cw);
764 }
765 prev = DRAWING_OP_TO;
766 }
767
768 private static boolean within(final float x1, final float y1,
769 final float x2, final float y2,
770 final float ERR)
771 {
772 assert ERR > 0 : "";
773 // compare taxicab distance. ERR will always be small, so using
774 // true distance won't give much benefit
775 return (Helpers.within(x1, x2, ERR) && // we want to avoid calling Math.abs
776 Helpers.within(y1, y2, ERR)); // this is just as good.
777 }
778
779 private void getLineOffsets(float x1, float y1,
780 float x2, float y2,
781 float[] left, float[] right) {
1046 int ret = 0;
1047 // we subdivide at values of t such that the remaining rotated
1048 // curves are monotonic in x and y.
1049 ret += c.dxRoots(ts, ret);
1050 ret += c.dyRoots(ts, ret);
1051 // subdivide at inflection points.
1052 if (type == 8) {
1053 // quadratic curves can't have inflection points
1054 ret += c.infPoints(ts, ret);
1055 }
1056
1057 // now we must subdivide at points where one of the offset curves will have
1058 // a cusp. This happens at ts where the radius of curvature is equal to w.
1059 ret += c.rootsOfROCMinusW(ts, ret, w, 0.0001f);
1060
1061 ret = Helpers.filterOutNotInAB(ts, 0, ret, 0.0001f, 0.9999f);
1062 Helpers.isort(ts, 0, ret);
1063 return ret;
1064 }
1065
1066 @Override
1067 public void curveTo(final float x1, final float y1,
1068 final float x2, final float y2,
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
1089 final float[] mid = middle;
1090
1091 mid[0] = cx0; mid[1] = cy0;
1092 mid[2] = x1; mid[3] = y1;
1093 mid[4] = x2; mid[5] = y2;
1094 mid[6] = x3; mid[7] = y3;
1095
1096 // need these so we can update the state at the end of this method
1097 final float xf = x3, yf = y3;
1098 float dxs = mid[2] - mid[0];
1099 float dys = mid[3] - mid[1];
1100 float dxf = mid[6] - mid[4];
1101 float dyf = mid[7] - mid[5];
1102
1103 boolean p1eqp2 = (dxs == 0.0f && dys == 0.0f);
1104 boolean p3eqp4 = (dxf == 0.0f && dyf == 0.0f);
1105 if (p1eqp2) {
1106 dxs = mid[4] - mid[0];
1107 dys = mid[5] - mid[1];
1108 if (dxs == 0.0f && dys == 0.0f) {
1109 dxs = mid[6] - mid[0];
1110 dys = mid[7] - mid[1];
1111 }
1112 }
1113 if (p3eqp4) {
1114 dxf = mid[6] - mid[2];
1115 dyf = mid[7] - mid[3];
1116 if (dxf == 0.0f && dyf == 0.0f) {
1117 dxf = mid[6] - mid[0];
1118 dyf = mid[7] - mid[1];
1119 }
1120 }
1121 if (dxs == 0.0f && dys == 0.0f) {
1122 // this happens if the "curve" is just a point
1123 // fix outcode0 for lineTo() call:
1124 if (clipRect != null) {
1125 this.cOutCode = outcode0;
1126 }
1127 lineTo(mid[0], mid[1]);
1128 return;
1129 }
1130
1131 // if these vectors are too small, normalize them, to avoid future
1132 // precision problems.
1133 if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) {
1134 float len = (float) Math.sqrt(dxs*dxs + dys*dys);
1135 dxs /= len;
1136 dys /= len;
1137 }
1138 if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) {
1139 float len = (float) Math.sqrt(dxf*dxf + dyf*dyf);
1140 dxf /= len;
1141 dyf /= len;
1142 }
1143
1144 computeOffset(dxs, dys, lineWidth2, offset0);
1145 drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
1146
1147 final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2);
1148
1149 float prevT = 0.0f;
1150 for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
1151 final float t = subdivTs[i];
1152 Helpers.subdivideCubicAt((t - prevT) / (1.0f - prevT),
1153 mid, off, mid, off, mid, off + 6);
1154 prevT = t;
1155 }
1156
1157 final float[] l = lp;
1158 final float[] r = rp;
1159
1160 int kind = 0;
1161 for (int i = 0, off = 0; i <= nSplits; i++, off += 6) {
1162 kind = computeOffsetCubic(mid, off, l, r);
1163
1164 emitLineTo(l[0], l[1]);
1165
1166 switch(kind) {
1167 case 8:
1168 emitCurveTo(l[2], l[3], l[4], l[5], l[6], l[7]);
1169 emitCurveToRev(r[0], r[1], r[2], r[3], r[4], r[5]);
1170 break;
1171 case 4:
1172 emitLineTo(l[2], l[3]);
1173 emitLineToRev(r[0], r[1]);
1174 break;
1175 default:
1176 }
1177 emitLineToRev(r[kind - 2], r[kind - 1]);
1178 }
1179
1180 this.prev = DRAWING_OP_TO;
1181 this.cx0 = xf;
1182 this.cy0 = yf;
1183 this.cdx = dxf;
1184 this.cdy = dyf;
1185 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
1186 this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f;
1187 }
1188
1189 @Override
1190 public void quadTo(final float x1, final float y1,
1191 final float x2, final float y2)
1192 {
1193 final int outcode0 = this.cOutCode;
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
1210 final float[] mid = middle;
1211
1212 mid[0] = cx0; mid[1] = cy0;
1213 mid[2] = x1; mid[3] = y1;
1214 mid[4] = x2; mid[5] = y2;
1215
1216 // need these so we can update the state at the end of this method
1217 final float xf = x2, yf = y2;
1218 float dxs = mid[2] - mid[0];
1219 float dys = mid[3] - mid[1];
1220 float dxf = mid[4] - mid[2];
1221 float dyf = mid[5] - mid[3];
1222 if ((dxs == 0.0f && dys == 0.0f) || (dxf == 0.0f && dyf == 0.0f)) {
1223 dxs = dxf = mid[4] - mid[0];
1224 dys = dyf = mid[5] - mid[1];
1225 }
1226 if (dxs == 0.0f && dys == 0.0f) {
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 }
1232 lineTo(mid[0], mid[1]);
1233 return;
1234 }
1235 // if these vectors are too small, normalize them, to avoid future
1236 // precision problems.
1237 if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) {
1238 float len = (float) Math.sqrt(dxs*dxs + dys*dys);
1239 dxs /= len;
1240 dys /= len;
1241 }
1242 if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) {
1243 float len = (float) Math.sqrt(dxf*dxf + dyf*dyf);
1244 dxf /= len;
1245 dyf /= len;
1246 }
1247
1248 computeOffset(dxs, dys, lineWidth2, offset0);
1249 drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
1250
1251 int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2);
1252
1253 float prevt = 0.0f;
1254 for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
1255 final float t = subdivTs[i];
1256 Helpers.subdivideQuadAt((t - prevt) / (1.0f - prevt),
1257 mid, off, mid, off, mid, off + 4);
1258 prevt = t;
1259 }
1260
1261 final float[] l = lp;
1262 final float[] r = rp;
1263
1264 int kind = 0;
1265 for (int i = 0, off = 0; i <= nSplits; i++, off += 4) {
1266 kind = computeOffsetQuad(mid, off, l, r);
1267
1268 emitLineTo(l[0], l[1]);
1269
1270 switch(kind) {
1271 case 6:
1272 emitQuadTo(l[2], l[3], l[4], l[5]);
1273 emitQuadToRev(r[0], r[1], r[2], r[3]);
1274 break;
1275 case 4:
1276 emitLineTo(l[2], l[3]);
1277 emitLineToRev(r[0], r[1]);
1278 break;
1279 default:
1280 }
1281 emitLineToRev(r[kind - 2], r[kind - 1]);
1282 }
1283
1284 this.prev = DRAWING_OP_TO;
1285 this.cx0 = xf;
1286 this.cy0 = yf;
1287 this.cdx = dxf;
1288 this.cdy = dyf;
1289 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
1290 this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f;
1291 }
1292
1293 @Override public long getNativeConsumer() {
1294 throw new InternalError("Stroker doesn't use a native consumer");
1295 }
1296 }
|