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
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;
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 /**
43 * Constant value for join style.
44 */
45 public static final int JOIN_MITER = 0;
46
47 /**
48 * Constant value for join style.
49 */
50 public static final int JOIN_ROUND = 1;
51
104 // original path (thought they may have different directions), so these
105 // could be computed from sdx,sdy and cdx,cdy (and vice versa), but that
106 // would be error prone and hard to read, so we keep these anyway.
107 private float smx, smy, cmx, cmy;
108
109 private final PolyStack reverse;
110
111 // This is where the curve to be processed is put. We give it
112 // enough room to store all curves.
113 private final float[] middle = new float[MAX_N_CURVES * 6 + 2];
114 private final float[] lp = new float[8];
115 private final float[] rp = new float[8];
116 private final float[] subdivTs = new float[MAX_N_CURVES - 1];
117
118 // per-thread renderer context
119 final RendererContext rdrCtx;
120
121 // dirty curve
122 final Curve curve;
123
124 // Bounds of the drawing region, at pixel precision.
125 private float[] clipRect;
126
127 // the outcode of the current point
128 private int cOutCode = 0;
129
130 // the outcode of the starting point
131 private int sOutCode = 0;
132
133 // flag indicating if the path is opened (clipped)
134 private boolean opened = false;
135 // flag indicating if the starting point's cap is done
136 private boolean capStart = false;
137
138 /**
139 * Constructs a <code>Stroker</code>.
140 * @param rdrCtx per-thread renderer context
141 */
142 Stroker(final RendererContext rdrCtx) {
143 this.rdrCtx = rdrCtx;
144
145 this.reverse = (rdrCtx.stats != null) ?
146 new PolyStack(rdrCtx,
147 rdrCtx.stats.stat_str_polystack_types,
148 rdrCtx.stats.stat_str_polystack_curves,
149 rdrCtx.stats.hist_str_polystack_curves,
150 rdrCtx.stats.stat_array_str_polystack_curves,
151 rdrCtx.stats.stat_array_str_polystack_types)
152 : new PolyStack(rdrCtx);
153
154 this.curve = rdrCtx.curve;
155 }
156
157 /**
158 * Inits the <code>Stroker</code>.
159 *
160 * @param pc2d an output <code>PathConsumer2D</code>.
161 * @param lineWidth the desired line width in pixels
162 * @param capStyle the desired end cap style, one of
163 * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or
164 * <code>CAP_SQUARE</code>.
165 * @param joinStyle the desired line join style, one of
166 * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or
167 * <code>JOIN_BEVEL</code>.
168 * @param miterLimit the desired miter limit
169 * @param scale scaling factor applied to clip boundaries
170 * @return this instance
171 */
172 Stroker init(final PathConsumer2D pc2d,
173 final float lineWidth,
174 final int capStyle,
175 final int joinStyle,
176 final float miterLimit,
177 final float scale)
178 {
179 this.out = pc2d;
180
181 this.lineWidth2 = lineWidth / 2.0f;
182 this.invHalfLineWidth2Sq = 1.0f / (2.0f * lineWidth2 * lineWidth2);
183 this.capStyle = capStyle;
184 this.joinStyle = joinStyle;
185
186 final float limit = miterLimit * lineWidth2;
187 this.miterLimitSq = limit * limit;
188
189 this.prev = CLOSE;
190
191 rdrCtx.stroking = 1;
192
193 if (rdrCtx.doClip) {
194 // Adjust the clipping rectangle with the stroker margin (miter limit, width)
195
196 // round joins / caps:
197 final float widthLimit =
198 ((joinStyle == JOIN_ROUND) || (capStyle == CAP_ROUND)) ? C * lineWidth // why 0.55 ?
199 : lineWidth2;
200
201 float boundsMargin;
202 if (joinStyle == JOIN_MITER) {
203 boundsMargin = Math.max(widthLimit, limit);
204 } else {
205 boundsMargin = widthLimit;
206 }
207 float rdrOffX = 0.0f, rdrOffY = 0.0f;
208
209 if (scale != 1.0f) {
210 boundsMargin *= scale;
211 rdrOffX = scale * Renderer.RDR_OFFSET_X;
212 rdrOffY = scale * Renderer.RDR_OFFSET_Y;
213 }
214
215 // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY
216 // adjust clip rectangle (ymin, ymax, xmin, xmax):
217 final float[] _clipRect = rdrCtx.clipRect;
218 _clipRect[0] -= boundsMargin - rdrOffY;
219 _clipRect[1] += boundsMargin + rdrOffY;
220 _clipRect[2] -= boundsMargin - rdrOffX;
221 _clipRect[3] += boundsMargin + rdrOffX;
222 // System.out.println("clip: "+java.util.Arrays.toString(_clipRect));
223
224 this.clipRect = _clipRect;
225 } else {
226 this.clipRect = null;
227 }
228 return this; // fluent API
229 }
230
231 /**
232 * Disposes this stroker:
233 * clean up before reusing this instance
234 */
235 void dispose() {
236 reverse.dispose();
237
238 opened = false;
239 capStart = false;
240
241 if (DO_CLEAN_DIRTY) {
242 // Force zero-fill dirty arrays:
243 Arrays.fill(offset0, 0.0f);
244 Arrays.fill(offset1, 0.0f);
245 Arrays.fill(offset2, 0.0f);
246 Arrays.fill(miter, 0.0f);
247 Arrays.fill(middle, 0.0f);
248 Arrays.fill(lp, 0.0f);
249 Arrays.fill(rp, 0.0f);
250 Arrays.fill(subdivTs, 0.0f);
251 }
252 }
253
254 private static void computeOffset(final float lx, final float ly,
255 final float w, final float[] m)
256 {
257 float len = lx*lx + ly*ly;
258 if (len == 0.0f) {
259 m[0] = 0.0f;
260 m[1] = 0.0f;
492 computeMiter((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy,
493 (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my,
494 miter, 0);
495
496 final float miterX = miter[0];
497 final float miterY = miter[1];
498 float lenSq = (miterX-x0)*(miterX-x0) + (miterY-y0)*(miterY-y0);
499
500 // If the lines are parallel, lenSq will be either NaN or +inf
501 // (actually, I'm not sure if the latter is possible. The important
502 // thing is that -inf is not possible, because lenSq is a square).
503 // For both of those values, the comparison below will fail and
504 // no miter will be drawn, which is correct.
505 if (lenSq < miterLimitSq) {
506 emitLineTo(miterX, miterY, rev);
507 }
508 }
509
510 @Override
511 public void moveTo(float x0, float y0) {
512 moveTo(x0, y0, cOutCode);
513 // update starting point:
514 this.sx0 = x0;
515 this.sy0 = y0;
516 this.sdx = 1.0f;
517 this.sdy = 0.0f;
518 this.opened = false;
519 this.capStart = false;
520
521 if (clipRect != null) {
522 final int outcode = Helpers.outcode(x0, y0, clipRect);
523 this.cOutCode = outcode;
524 this.sOutCode = outcode;
525 }
526 }
527
528 private void moveTo(float x0, float y0,
529 final int outcode)
530 {
531 if (prev == MOVE_TO) {
532 this.cx0 = x0;
533 this.cy0 = y0;
534 } else {
535 if (prev == DRAWING_OP_TO) {
536 finish(outcode);
537 }
538 this.prev = MOVE_TO;
539 this.cx0 = x0;
540 this.cy0 = y0;
541 this.cdx = 1.0f;
542 this.cdy = 0.0f;
543 }
544 }
545
546 @Override
547 public void lineTo(float x1, float y1) {
548 lineTo(x1, y1, false);
549 }
550
551 private void lineTo(float x1, float y1, boolean force) {
552 final int outcode0 = this.cOutCode;
553 if (!force && clipRect != null) {
554 final int outcode1 = Helpers.outcode(x1, y1, clipRect);
555 this.cOutCode = outcode1;
556
557 // basic rejection criteria
558 if ((outcode0 & outcode1) != 0) {
559 moveTo(x1, y1, outcode0);
560 opened = true;
561 return;
562 }
563 }
564
565 float dx = x1 - cx0;
566 float dy = y1 - cy0;
567 if (dx == 0.0f && dy == 0.0f) {
568 dx = 1.0f;
569 }
570 computeOffset(dx, dy, lineWidth2, offset0);
571 final float mx = offset0[0];
572 final float my = offset0[1];
573
574 drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my, outcode0);
575
576 emitLineTo(cx0 + mx, cy0 + my);
577 emitLineTo( x1 + mx, y1 + my);
578
579 emitLineToRev(cx0 - mx, cy0 - my);
580 emitLineToRev( x1 - mx, y1 - my);
581
582 this.prev = DRAWING_OP_TO;
583 this.cx0 = x1;
584 this.cy0 = y1;
585 this.cdx = dx;
586 this.cdy = dy;
587 this.cmx = mx;
588 this.cmy = my;
589 }
590
591 @Override
592 public void closePath() {
593 // distinguish empty path at all vs opened path ?
594 if (prev != DRAWING_OP_TO && !opened) {
595 if (prev == CLOSE) {
596 return;
597 }
598 emitMoveTo(cx0, cy0 - lineWidth2);
599
600 this.sdx = 1.0f;
601 this.sdy = 0.0f;
602 this.cdx = 1.0f;
603 this.cdy = 0.0f;
604
605 this.smx = 0.0f;
606 this.smy = -lineWidth2;
607 this.cmx = 0.0f;
608 this.cmy = -lineWidth2;
609
610 finish(cOutCode);
611 return;
612 }
613
614 if (sOutCode == 0) {
615 if (cx0 != sx0 || cy0 != sy0) {
616 lineTo(sx0, sy0, true);
617 }
618
619 drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy, cOutCode);
620
621 emitLineTo(sx0 + smx, sy0 + smy);
622
623 if (opened) {
624 emitLineTo(sx0 - smx, sy0 - smy);
625 } else {
626 emitMoveTo(sx0 - smx, sy0 - smy);
627 }
628 }
629 // Ignore caps like finish(false)
630 emitReverse();
631
632 this.prev = CLOSE;
633
634 if (opened) {
635 // do not emit close
636 opened = false;
637 } else {
638 emitClose();
639 }
640 }
641
642 private void emitReverse() {
643 reverse.popAll(out);
644 }
645
646 @Override
647 public void pathDone() {
648 if (prev == DRAWING_OP_TO) {
649 finish(cOutCode);
650 }
651
652 out.pathDone();
653
654 // this shouldn't matter since this object won't be used
655 // after the call to this method.
656 this.prev = CLOSE;
657
658 // Dispose this instance:
659 dispose();
660 }
661
662 private void finish(final int outcode) {
663 // Problem: impossible to guess if the path will be closed in advance
664 // i.e. if caps must be drawn or not ?
665 // Solution: use the ClosedPathDetector before Stroker to determine
666 // if the path is a closed path or not
667 if (!rdrCtx.closedPath) {
668 if (outcode == 0) {
669 // current point = end's cap:
670 if (capStyle == CAP_ROUND) {
671 drawRoundCap(cx0, cy0, cmx, cmy);
672 } else if (capStyle == CAP_SQUARE) {
673 emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy);
674 emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy);
675 }
676 }
677 emitReverse();
678
679 if (!capStart) {
680 capStart = true;
681
682 if (sOutCode == 0) {
683 // starting point = initial cap:
684 if (capStyle == CAP_ROUND) {
685 drawRoundCap(sx0, sy0, -smx, -smy);
686 } else if (capStyle == CAP_SQUARE) {
687 emitLineTo(sx0 + smy - smx, sy0 - smx - smy);
688 emitLineTo(sx0 + smy + smx, sy0 - smx + smy);
689 }
690 }
691 }
692 } else {
693 emitReverse();
694 }
695 emitClose();
696 }
697
698 private void emitMoveTo(final float x0, final float y0) {
699 out.moveTo(x0, y0);
700 }
701
702 private void emitLineTo(final float x1, final float y1) {
703 out.lineTo(x1, y1);
704 }
705
706 private void emitLineToRev(final float x1, final float y1) {
707 reverse.pushLine(x1, y1);
708 }
709
710 private void emitLineTo(final float x1, final float y1,
711 final boolean rev)
712 {
713 if (rev) {
714 emitLineToRev(x1, y1);
746 private void emitCurveTo(final float x0, final float y0,
747 final float x1, final float y1,
748 final float x2, final float y2,
749 final float x3, final float y3, final boolean rev)
750 {
751 if (rev) {
752 reverse.pushCubic(x0, y0, x1, y1, x2, y2);
753 } else {
754 out.curveTo(x1, y1, x2, y2, x3, y3);
755 }
756 }
757
758 private void emitClose() {
759 out.closePath();
760 }
761
762 private void drawJoin(float pdx, float pdy,
763 float x0, float y0,
764 float dx, float dy,
765 float omx, float omy,
766 float mx, float my,
767 final int outcode)
768 {
769 if (prev != DRAWING_OP_TO) {
770 emitMoveTo(x0 + mx, y0 + my);
771 if (!opened) {
772 this.sdx = dx;
773 this.sdy = dy;
774 this.smx = mx;
775 this.smy = my;
776 }
777 } else {
778 final boolean cw = isCW(pdx, pdy, dx, dy);
779 if (outcode == 0) {
780 if (joinStyle == JOIN_MITER) {
781 drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
782 } else if (joinStyle == JOIN_ROUND) {
783 drawRoundJoin(x0, y0,
784 omx, omy,
785 mx, my, cw,
786 ROUND_JOIN_THRESHOLD);
787 }
788 }
789 emitLineTo(x0, y0, !cw);
790 }
791 prev = DRAWING_OP_TO;
792 }
793
794 private static boolean within(final float x1, final float y1,
795 final float x2, final float y2,
796 final float ERR)
797 {
798 assert ERR > 0 : "";
799 // compare taxicab distance. ERR will always be small, so using
800 // true distance won't give much benefit
801 return (Helpers.within(x1, x2, ERR) && // we want to avoid calling Math.abs
802 Helpers.within(y1, y2, ERR)); // this is just as good.
803 }
804
805 private void getLineOffsets(float x1, float y1,
806 float x2, float y2,
807 float[] left, float[] right) {
1072 int ret = 0;
1073 // we subdivide at values of t such that the remaining rotated
1074 // curves are monotonic in x and y.
1075 ret += c.dxRoots(ts, ret);
1076 ret += c.dyRoots(ts, ret);
1077 // subdivide at inflection points.
1078 if (type == 8) {
1079 // quadratic curves can't have inflection points
1080 ret += c.infPoints(ts, ret);
1081 }
1082
1083 // now we must subdivide at points where one of the offset curves will have
1084 // a cusp. This happens at ts where the radius of curvature is equal to w.
1085 ret += c.rootsOfROCMinusW(ts, ret, w, 0.0001f);
1086
1087 ret = Helpers.filterOutNotInAB(ts, 0, ret, 0.0001f, 0.9999f);
1088 Helpers.isort(ts, 0, ret);
1089 return ret;
1090 }
1091
1092 @Override
1093 public void curveTo(float x1, float y1,
1094 float x2, float y2,
1095 float x3, float y3)
1096 {
1097 final int outcode0 = this.cOutCode;
1098 if (clipRect != null) {
1099 final int outcode3 = Helpers.outcode(x3, y3, clipRect);
1100 this.cOutCode = outcode3;
1101
1102 if (outcode3 != 0) {
1103 final int outcode1 = Helpers.outcode(x1, y1, clipRect);
1104 final int outcode2 = Helpers.outcode(x2, y2, clipRect);
1105
1106 // basic rejection criteria
1107 if ((outcode0 & outcode1 & outcode2 & outcode3) != 0) {
1108 moveTo(x3, y3, outcode0);
1109 opened = true;
1110 return;
1111 }
1112 }
1113 }
1114
1115 final float[] mid = middle;
1116
1117 mid[0] = cx0; mid[1] = cy0;
1118 mid[2] = x1; mid[3] = y1;
1119 mid[4] = x2; mid[5] = y2;
1120 mid[6] = x3; mid[7] = y3;
1121
1122 // need these so we can update the state at the end of this method
1123 final float xf = x3, yf = y3;
1124 float dxs = mid[2] - mid[0];
1125 float dys = mid[3] - mid[1];
1126 float dxf = mid[6] - mid[4];
1127 float dyf = mid[7] - mid[5];
1128
1129 boolean p1eqp2 = (dxs == 0.0f && dys == 0.0f);
1130 boolean p3eqp4 = (dxf == 0.0f && dyf == 0.0f);
1131 if (p1eqp2) {
1132 dxs = mid[4] - mid[0];
1133 dys = mid[5] - mid[1];
1134 if (dxs == 0.0f && dys == 0.0f) {
1135 dxs = mid[6] - mid[0];
1136 dys = mid[7] - mid[1];
1137 }
1138 }
1139 if (p3eqp4) {
1140 dxf = mid[6] - mid[2];
1141 dyf = mid[7] - mid[3];
1142 if (dxf == 0.0f && dyf == 0.0f) {
1143 dxf = mid[6] - mid[0];
1144 dyf = mid[7] - mid[1];
1145 }
1146 }
1147 if (dxs == 0.0f && dys == 0.0f) {
1148 // this happens if the "curve" is just a point
1149 // fix outcode0 for lineTo() call:
1150 if (clipRect != null) {
1151 this.cOutCode = outcode0;
1152 }
1153 lineTo(mid[0], mid[1]);
1154 return;
1155 }
1156
1157 // if these vectors are too small, normalize them, to avoid future
1158 // precision problems.
1159 if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) {
1160 float len = (float) Math.sqrt(dxs*dxs + dys*dys);
1161 dxs /= len;
1162 dys /= len;
1163 }
1164 if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) {
1165 float len = (float) Math.sqrt(dxf*dxf + dyf*dyf);
1166 dxf /= len;
1167 dyf /= len;
1168 }
1169
1170 computeOffset(dxs, dys, lineWidth2, offset0);
1171 drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
1172
1173 final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2);
1174
1175 float prevT = 0.0f;
1176 for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
1177 final float t = subdivTs[i];
1178 Helpers.subdivideCubicAt((t - prevT) / (1.0f - prevT),
1179 mid, off, mid, off, mid, off + 6);
1180 prevT = t;
1181 }
1182
1183 final float[] l = lp;
1184 final float[] r = rp;
1185
1186 int kind = 0;
1187 for (int i = 0, off = 0; i <= nSplits; i++, off += 6) {
1188 kind = computeOffsetCubic(mid, off, l, r);
1189
1190 emitLineTo(l[0], l[1]);
1191
1192 switch(kind) {
1193 case 8:
1194 emitCurveTo(l[2], l[3], l[4], l[5], l[6], l[7]);
1195 emitCurveToRev(r[0], r[1], r[2], r[3], r[4], r[5]);
1196 break;
1197 case 4:
1198 emitLineTo(l[2], l[3]);
1199 emitLineToRev(r[0], r[1]);
1200 break;
1201 default:
1202 }
1203 emitLineToRev(r[kind - 2], r[kind - 1]);
1204 }
1205
1206 this.prev = DRAWING_OP_TO;
1207 this.cx0 = xf;
1208 this.cy0 = yf;
1209 this.cdx = dxf;
1210 this.cdy = dyf;
1211 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
1212 this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f;
1213 }
1214
1215 @Override
1216 public void quadTo(float x1, float y1,
1217 float x2, float y2)
1218 {
1219 final int outcode0 = this.cOutCode;
1220 if (clipRect != null) {
1221 final int outcode2 = Helpers.outcode(x2, y2, clipRect);
1222 this.cOutCode = outcode2;
1223
1224 if (outcode2 != 0) {
1225 final int outcode1 = Helpers.outcode(x1, y1, clipRect);
1226
1227 // basic rejection criteria
1228 if ((outcode0 & outcode1 & outcode2) != 0) {
1229 moveTo(x2, y2, outcode0);
1230 opened = true;
1231 return;
1232 }
1233 }
1234 }
1235
1236 final float[] mid = middle;
1237
1238 mid[0] = cx0; mid[1] = cy0;
1239 mid[2] = x1; mid[3] = y1;
1240 mid[4] = x2; mid[5] = y2;
1241
1242 // need these so we can update the state at the end of this method
1243 final float xf = x2, yf = y2;
1244 float dxs = mid[2] - mid[0];
1245 float dys = mid[3] - mid[1];
1246 float dxf = mid[4] - mid[2];
1247 float dyf = mid[5] - mid[3];
1248 if ((dxs == 0.0f && dys == 0.0f) || (dxf == 0.0f && dyf == 0.0f)) {
1249 dxs = dxf = mid[4] - mid[0];
1250 dys = dyf = mid[5] - mid[1];
1251 }
1252 if (dxs == 0.0f && dys == 0.0f) {
1253 // this happens if the "curve" is just a point
1254 // fix outcode0 for lineTo() call:
1255 if (clipRect != null) {
1256 this.cOutCode = outcode0;
1257 }
1258 lineTo(mid[0], mid[1]);
1259 return;
1260 }
1261 // if these vectors are too small, normalize them, to avoid future
1262 // precision problems.
1263 if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) {
1264 float len = (float) Math.sqrt(dxs*dxs + dys*dys);
1265 dxs /= len;
1266 dys /= len;
1267 }
1268 if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) {
1269 float len = (float) Math.sqrt(dxf*dxf + dyf*dyf);
1270 dxf /= len;
1271 dyf /= len;
1272 }
1273
1274 computeOffset(dxs, dys, lineWidth2, offset0);
1275 drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
1276
1277 int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2);
1278
1279 float prevt = 0.0f;
1280 for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
1281 final float t = subdivTs[i];
1282 Helpers.subdivideQuadAt((t - prevt) / (1.0f - prevt),
1283 mid, off, mid, off, mid, off + 4);
1284 prevt = t;
1285 }
1286
1287 final float[] l = lp;
1288 final float[] r = rp;
1289
1290 int kind = 0;
1291 for (int i = 0, off = 0; i <= nSplits; i++, off += 4) {
1292 kind = computeOffsetQuad(mid, off, l, r);
1293
1294 emitLineTo(l[0], l[1]);
1295
1296 switch(kind) {
1297 case 6:
1298 emitQuadTo(l[2], l[3], l[4], l[5]);
1299 emitQuadToRev(r[0], r[1], r[2], r[3]);
1300 break;
1301 case 4:
1302 emitLineTo(l[2], l[3]);
1303 emitLineToRev(r[0], r[1]);
1304 break;
1305 default:
1306 }
1307 emitLineToRev(r[kind - 2], r[kind - 1]);
1308 }
1309
1310 this.prev = DRAWING_OP_TO;
1311 this.cx0 = xf;
1312 this.cy0 = yf;
1313 this.cdx = dxf;
1314 this.cdy = dyf;
1315 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
1316 this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f;
1317 }
1318
1319 @Override public long getNativeConsumer() {
1320 throw new InternalError("Stroker doesn't use a native consumer");
1321 }
1322 }
|