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