9 * by Oracle in the LICENSE file that accompanied this code.
10 *
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 // TODO: some of the arithmetic here is too verbose and prone to hard to
31 // debug typos. We should consider making a small Point/Vector class that
32 // has methods like plus(Point), minus(Point), dot(Point), cross(Point)and such
33 final class DStroker implements DPathConsumer2D, MarlinConst {
34
35 private static final int MOVE_TO = 0;
36 private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad
37 private static final int CLOSE = 2;
38
39 /**
40 * Constant value for join style.
41 */
42 public static final int JOIN_MITER = 0;
43
44 /**
45 * Constant value for join style.
46 */
47 public static final int JOIN_ROUND = 1;
48
54 /**
55 * Constant value for end cap style.
56 */
57 public static final int CAP_BUTT = 0;
58
59 /**
60 * Constant value for end cap style.
61 */
62 public static final int CAP_ROUND = 1;
63
64 /**
65 * Constant value for end cap style.
66 */
67 public static final int CAP_SQUARE = 2;
68
69 // pisces used to use fixed point arithmetic with 16 decimal digits. I
70 // didn't want to change the values of the constant below when I converted
71 // it to floating point, so that's why the divisions by 2^16 are there.
72 private static final double ROUND_JOIN_THRESHOLD = 1000.0d/65536.0d;
73
74 private static final double C = 0.5522847498307933d;
75
76 private static final int MAX_N_CURVES = 11;
77
78 private DPathConsumer2D out;
79
80 private int capStyle;
81 private int joinStyle;
82
83 private double lineWidth2;
84 private double invHalfLineWidth2Sq;
85
86 private final double[] offset0 = new double[2];
87 private final double[] offset1 = new double[2];
88 private final double[] offset2 = new double[2];
89 private final double[] miter = new double[2];
90 private double miterLimitSq;
91
92 private int prev;
93
94 // The starting point of the path, and the slope there.
101 // original path (thought they may have different directions), so these
102 // could be computed from sdx,sdy and cdx,cdy (and vice versa), but that
103 // would be error prone and hard to read, so we keep these anyway.
104 private double smx, smy, cmx, cmy;
105
106 private final PolyStack reverse;
107
108 // This is where the curve to be processed is put. We give it
109 // enough room to store all curves.
110 private final double[] middle = new double[MAX_N_CURVES * 6 + 2];
111 private final double[] lp = new double[8];
112 private final double[] rp = new double[8];
113 private final double[] subdivTs = new double[MAX_N_CURVES - 1];
114
115 // per-thread renderer context
116 final DRendererContext rdrCtx;
117
118 // dirty curve
119 final DCurve curve;
120
121 /**
122 * Constructs a <code>DStroker</code>.
123 * @param rdrCtx per-thread renderer context
124 */
125 DStroker(final DRendererContext rdrCtx) {
126 this.rdrCtx = rdrCtx;
127
128 this.reverse = new PolyStack(rdrCtx);
129 this.curve = rdrCtx.curve;
130 }
131
132 /**
133 * Inits the <code>DStroker</code>.
134 *
135 * @param pc2d an output <code>DPathConsumer2D</code>.
136 * @param lineWidth the desired line width in pixels
137 * @param capStyle the desired end cap style, one of
138 * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or
139 * <code>CAP_SQUARE</code>.
140 * @param joinStyle the desired line join style, one of
141 * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or
142 * <code>JOIN_BEVEL</code>.
143 * @param miterLimit the desired miter limit
144 * @return this instance
145 */
146 DStroker init(DPathConsumer2D pc2d,
147 double lineWidth,
148 int capStyle,
149 int joinStyle,
150 double miterLimit)
151 {
152 this.out = pc2d;
153
154 this.lineWidth2 = lineWidth / 2.0d;
155 this.invHalfLineWidth2Sq = 1.0d / (2.0d * lineWidth2 * lineWidth2);
156 this.capStyle = capStyle;
157 this.joinStyle = joinStyle;
158
159 double limit = miterLimit * lineWidth2;
160 this.miterLimitSq = limit * limit;
161
162 this.prev = CLOSE;
163
164 rdrCtx.stroking = 1;
165
166 return this; // fluent API
167 }
168
169 /**
170 * Disposes this stroker:
171 * clean up before reusing this instance
172 */
173 void dispose() {
174 reverse.dispose();
175
176 if (DO_CLEAN_DIRTY) {
177 // Force zero-fill dirty arrays:
178 Arrays.fill(offset0, 0.0d);
179 Arrays.fill(offset1, 0.0d);
180 Arrays.fill(offset2, 0.0d);
181 Arrays.fill(miter, 0.0d);
182 Arrays.fill(middle, 0.0d);
183 Arrays.fill(lp, 0.0d);
184 Arrays.fill(rp, 0.0d);
185 Arrays.fill(subdivTs, 0.0d);
186 }
187 }
188
189 private static void computeOffset(final double lx, final double ly,
190 final double w, final double[] m)
191 {
192 double len = lx*lx + ly*ly;
193 if (len == 0.0d) {
194 m[0] = 0.0d;
195 m[1] = 0.0d;
426
427 computeMiter((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy,
428 (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my,
429 miter, 0);
430
431 final double miterX = miter[0];
432 final double miterY = miter[1];
433 double lenSq = (miterX-x0)*(miterX-x0) + (miterY-y0)*(miterY-y0);
434
435 // If the lines are parallel, lenSq will be either NaN or +inf
436 // (actually, I'm not sure if the latter is possible. The important
437 // thing is that -inf is not possible, because lenSq is a square).
438 // For both of those values, the comparison below will fail and
439 // no miter will be drawn, which is correct.
440 if (lenSq < miterLimitSq) {
441 emitLineTo(miterX, miterY, rev);
442 }
443 }
444
445 @Override
446 public void moveTo(double x0, double y0) {
447 if (prev == DRAWING_OP_TO) {
448 finish();
449 }
450 this.sx0 = this.cx0 = x0;
451 this.sy0 = this.cy0 = y0;
452 this.cdx = this.sdx = 1.0d;
453 this.cdy = this.sdy = 0.0d;
454 this.prev = MOVE_TO;
455 }
456
457 @Override
458 public void lineTo(double x1, double y1) {
459 double dx = x1 - cx0;
460 double dy = y1 - cy0;
461 if (dx == 0.0d && dy == 0.0d) {
462 dx = 1.0d;
463 }
464 computeOffset(dx, dy, lineWidth2, offset0);
465 final double mx = offset0[0];
466 final double my = offset0[1];
467
468 drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my);
469
470 emitLineTo(cx0 + mx, cy0 + my);
471 emitLineTo( x1 + mx, y1 + my);
472
473 emitLineToRev(cx0 - mx, cy0 - my);
474 emitLineToRev( x1 - mx, y1 - my);
475
476 this.cmx = mx;
477 this.cmy = my;
478 this.cdx = dx;
479 this.cdy = dy;
480 this.cx0 = x1;
481 this.cy0 = y1;
482 this.prev = DRAWING_OP_TO;
483 }
484
485 @Override
486 public void closePath() {
487 if (prev != DRAWING_OP_TO) {
488 if (prev == CLOSE) {
489 return;
490 }
491 emitMoveTo(cx0, cy0 - lineWidth2);
492 this.cmx = this.smx = 0.0d;
493 this.cmy = this.smy = -lineWidth2;
494 this.cdx = this.sdx = 1.0d;
495 this.cdy = this.sdy = 0.0d;
496 finish();
497 return;
498 }
499
500 if (cx0 != sx0 || cy0 != sy0) {
501 lineTo(sx0, sy0);
502 }
503
504 drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy);
505
506 emitLineTo(sx0 + smx, sy0 + smy);
507
508 emitMoveTo(sx0 - smx, sy0 - smy);
509 emitReverse();
510
511 this.prev = CLOSE;
512 emitClose();
513 }
514
515 private void emitReverse() {
516 reverse.popAll(out);
517 }
518
519 @Override
520 public void pathDone() {
521 if (prev == DRAWING_OP_TO) {
522 finish();
523 }
524
525 out.pathDone();
526
527 // this shouldn't matter since this object won't be used
528 // after the call to this method.
529 this.prev = CLOSE;
530
531 // Dispose this instance:
532 dispose();
533 }
534
535 private void finish() {
536 if (capStyle == CAP_ROUND) {
537 drawRoundCap(cx0, cy0, cmx, cmy);
538 } else if (capStyle == CAP_SQUARE) {
539 emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy);
540 emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy);
541 }
542
543 emitReverse();
544
545 if (capStyle == CAP_ROUND) {
546 drawRoundCap(sx0, sy0, -smx, -smy);
547 } else if (capStyle == CAP_SQUARE) {
548 emitLineTo(sx0 + smy - smx, sy0 - smx - smy);
549 emitLineTo(sx0 + smy + smx, sy0 - smx + smy);
550 }
551
552 emitClose();
553 }
554
555 private void emitMoveTo(final double x0, final double y0) {
556 out.moveTo(x0, y0);
557 }
558
559 private void emitLineTo(final double x1, final double y1) {
560 out.lineTo(x1, y1);
561 }
562
563 private void emitLineToRev(final double x1, final double y1) {
564 reverse.pushLine(x1, y1);
565 }
566
567 private void emitLineTo(final double x1, final double y1,
568 final boolean rev)
569 {
570 if (rev) {
571 emitLineToRev(x1, y1);
603 private void emitCurveTo(final double x0, final double y0,
604 final double x1, final double y1,
605 final double x2, final double y2,
606 final double x3, final double y3, final boolean rev)
607 {
608 if (rev) {
609 reverse.pushCubic(x0, y0, x1, y1, x2, y2);
610 } else {
611 out.curveTo(x1, y1, x2, y2, x3, y3);
612 }
613 }
614
615 private void emitClose() {
616 out.closePath();
617 }
618
619 private void drawJoin(double pdx, double pdy,
620 double x0, double y0,
621 double dx, double dy,
622 double omx, double omy,
623 double mx, double my)
624 {
625 if (prev != DRAWING_OP_TO) {
626 emitMoveTo(x0 + mx, y0 + my);
627 this.sdx = dx;
628 this.sdy = dy;
629 this.smx = mx;
630 this.smy = my;
631 } else {
632 boolean cw = isCW(pdx, pdy, dx, dy);
633 if (joinStyle == JOIN_MITER) {
634 drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
635 } else if (joinStyle == JOIN_ROUND) {
636 drawRoundJoin(x0, y0,
637 omx, omy,
638 mx, my, cw,
639 ROUND_JOIN_THRESHOLD);
640 }
641 emitLineTo(x0, y0, !cw);
642 }
643 prev = DRAWING_OP_TO;
644 }
645
646 private static boolean within(final double x1, final double y1,
647 final double x2, final double y2,
648 final double ERR)
649 {
650 assert ERR > 0 : "";
651 // compare taxicab distance. ERR will always be small, so using
652 // true distance won't give much benefit
653 return (DHelpers.within(x1, x2, ERR) && // we want to avoid calling Math.abs
654 DHelpers.within(y1, y2, ERR)); // this is just as good.
655 }
656
657 private void getLineOffsets(double x1, double y1,
658 double x2, double y2,
659 double[] left, double[] right) {
924 int ret = 0;
925 // we subdivide at values of t such that the remaining rotated
926 // curves are monotonic in x and y.
927 ret += c.dxRoots(ts, ret);
928 ret += c.dyRoots(ts, ret);
929 // subdivide at inflection points.
930 if (type == 8) {
931 // quadratic curves can't have inflection points
932 ret += c.infPoints(ts, ret);
933 }
934
935 // now we must subdivide at points where one of the offset curves will have
936 // a cusp. This happens at ts where the radius of curvature is equal to w.
937 ret += c.rootsOfROCMinusW(ts, ret, w, 0.0001d);
938
939 ret = DHelpers.filterOutNotInAB(ts, 0, ret, 0.0001d, 0.9999d);
940 DHelpers.isort(ts, 0, ret);
941 return ret;
942 }
943
944 @Override public void curveTo(double x1, double y1,
945 double x2, double y2,
946 double x3, double y3)
947 {
948 final double[] mid = middle;
949
950 mid[0] = cx0; mid[1] = cy0;
951 mid[2] = x1; mid[3] = y1;
952 mid[4] = x2; mid[5] = y2;
953 mid[6] = x3; mid[7] = y3;
954
955 // need these so we can update the state at the end of this method
956 final double xf = mid[6], yf = mid[7];
957 double dxs = mid[2] - mid[0];
958 double dys = mid[3] - mid[1];
959 double dxf = mid[6] - mid[4];
960 double dyf = mid[7] - mid[5];
961
962 boolean p1eqp2 = (dxs == 0.0d && dys == 0.0d);
963 boolean p3eqp4 = (dxf == 0.0d && dyf == 0.0d);
964 if (p1eqp2) {
965 dxs = mid[4] - mid[0];
966 dys = mid[5] - mid[1];
967 if (dxs == 0.0d && dys == 0.0d) {
968 dxs = mid[6] - mid[0];
969 dys = mid[7] - mid[1];
970 }
971 }
972 if (p3eqp4) {
973 dxf = mid[6] - mid[2];
974 dyf = mid[7] - mid[3];
975 if (dxf == 0.0d && dyf == 0.0d) {
976 dxf = mid[6] - mid[0];
977 dyf = mid[7] - mid[1];
978 }
979 }
980 if (dxs == 0.0d && dys == 0.0d) {
981 // this happens if the "curve" is just a point
982 lineTo(mid[0], mid[1]);
983 return;
984 }
985
986 // if these vectors are too small, normalize them, to avoid future
987 // precision problems.
988 if (Math.abs(dxs) < 0.1d && Math.abs(dys) < 0.1d) {
989 double len = Math.sqrt(dxs*dxs + dys*dys);
990 dxs /= len;
991 dys /= len;
992 }
993 if (Math.abs(dxf) < 0.1d && Math.abs(dyf) < 0.1d) {
994 double len = Math.sqrt(dxf*dxf + dyf*dyf);
995 dxf /= len;
996 dyf /= len;
997 }
998
999 computeOffset(dxs, dys, lineWidth2, offset0);
1000 drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]);
1001
1002 final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2);
1003
1004 double prevT = 0.0d;
1005 for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
1006 final double t = subdivTs[i];
1007 DHelpers.subdivideCubicAt((t - prevT) / (1.0d - prevT),
1008 mid, off, mid, off, mid, off + 6);
1009 prevT = t;
1010 }
1011
1012 final double[] l = lp;
1013 final double[] r = rp;
1014
1015 int kind = 0;
1016 for (int i = 0, off = 0; i <= nSplits; i++, off += 6) {
1017 kind = computeOffsetCubic(mid, off, l, r);
1018
1019 emitLineTo(l[0], l[1]);
1020
1021 switch(kind) {
1022 case 8:
1023 emitCurveTo(l[2], l[3], l[4], l[5], l[6], l[7]);
1024 emitCurveToRev(r[0], r[1], r[2], r[3], r[4], r[5]);
1025 break;
1026 case 4:
1027 emitLineTo(l[2], l[3]);
1028 emitLineToRev(r[0], r[1]);
1029 break;
1030 default:
1031 }
1032 emitLineToRev(r[kind - 2], r[kind - 1]);
1033 }
1034
1035 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
1036 this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
1037 this.cdx = dxf;
1038 this.cdy = dyf;
1039 this.cx0 = xf;
1040 this.cy0 = yf;
1041 this.prev = DRAWING_OP_TO;
1042 }
1043
1044 @Override public void quadTo(double x1, double y1, double x2, double y2) {
1045 final double[] mid = middle;
1046
1047 mid[0] = cx0; mid[1] = cy0;
1048 mid[2] = x1; mid[3] = y1;
1049 mid[4] = x2; mid[5] = y2;
1050
1051 // need these so we can update the state at the end of this method
1052 final double xf = mid[4], yf = mid[5];
1053 double dxs = mid[2] - mid[0];
1054 double dys = mid[3] - mid[1];
1055 double dxf = mid[4] - mid[2];
1056 double dyf = mid[5] - mid[3];
1057 if ((dxs == 0.0d && dys == 0.0d) || (dxf == 0.0d && dyf == 0.0d)) {
1058 dxs = dxf = mid[4] - mid[0];
1059 dys = dyf = mid[5] - mid[1];
1060 }
1061 if (dxs == 0.0d && dys == 0.0d) {
1062 // this happens if the "curve" is just a point
1063 lineTo(mid[0], mid[1]);
1064 return;
1065 }
1066 // if these vectors are too small, normalize them, to avoid future
1067 // precision problems.
1068 if (Math.abs(dxs) < 0.1d && Math.abs(dys) < 0.1d) {
1069 double len = Math.sqrt(dxs*dxs + dys*dys);
1070 dxs /= len;
1071 dys /= len;
1072 }
1073 if (Math.abs(dxf) < 0.1d && Math.abs(dyf) < 0.1d) {
1074 double len = Math.sqrt(dxf*dxf + dyf*dyf);
1075 dxf /= len;
1076 dyf /= len;
1077 }
1078
1079 computeOffset(dxs, dys, lineWidth2, offset0);
1080 drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]);
1081
1082 int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2);
1083
1084 double prevt = 0.0d;
1085 for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
1086 final double t = subdivTs[i];
1087 DHelpers.subdivideQuadAt((t - prevt) / (1.0d - prevt),
1088 mid, off, mid, off, mid, off + 4);
1089 prevt = t;
1090 }
1091
1092 final double[] l = lp;
1093 final double[] r = rp;
1094
1095 int kind = 0;
1096 for (int i = 0, off = 0; i <= nSplits; i++, off += 4) {
1097 kind = computeOffsetQuad(mid, off, l, r);
1098
1099 emitLineTo(l[0], l[1]);
1100
1101 switch(kind) {
1102 case 6:
1103 emitQuadTo(l[2], l[3], l[4], l[5]);
1104 emitQuadToRev(r[0], r[1], r[2], r[3]);
1105 break;
1106 case 4:
1107 emitLineTo(l[2], l[3]);
1108 emitLineToRev(r[0], r[1]);
1109 break;
1110 default:
1111 }
1112 emitLineToRev(r[kind - 2], r[kind - 1]);
1113 }
1114
1115 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
1116 this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
1117 this.cdx = dxf;
1118 this.cdy = dyf;
1119 this.cx0 = xf;
1120 this.cy0 = yf;
1121 this.prev = DRAWING_OP_TO;
1122 }
1123
1124 @Override public long getNativeConsumer() {
1125 throw new InternalError("Stroker doesn't use a native consumer");
1126 }
1127
1128 // a stack of polynomial curves where each curve shares endpoints with
1129 // adjacent ones.
1130 static final class PolyStack {
1131 private static final byte TYPE_LINETO = (byte) 0;
1132 private static final byte TYPE_QUADTO = (byte) 1;
1133 private static final byte TYPE_CUBICTO = (byte) 2;
1134
1135 // curves capacity = edges count (8192) = edges x 2 (coords)
1136 private static final int INITIAL_CURVES_COUNT = INITIAL_EDGES_COUNT << 1;
1137
1138 // types capacity = edges count (4096)
1139 private static final int INITIAL_TYPES_COUNT = INITIAL_EDGES_COUNT;
1140
1141 double[] curves;
1142 int end;
1143 byte[] curveTypes;
1144 int numCurves;
1145
1146 // per-thread renderer context
1147 final DRendererContext rdrCtx;
1148
1149 // curves ref (dirty)
1150 final DoubleArrayCache.Reference curves_ref;
1151 // curveTypes ref (dirty)
1152 final ByteArrayCache.Reference curveTypes_ref;
1153
1154 // used marks (stats only)
1155 int curveTypesUseMark;
1156 int curvesUseMark;
1157
1158 /**
1159 * Constructor
1160 * @param rdrCtx per-thread renderer context
1161 */
1162 PolyStack(final DRendererContext rdrCtx) {
1163 this.rdrCtx = rdrCtx;
1164
1165 curves_ref = rdrCtx.newDirtyDoubleArrayRef(INITIAL_CURVES_COUNT); // 32K
1166 curves = curves_ref.initial;
1167
1168 curveTypes_ref = rdrCtx.newDirtyByteArrayRef(INITIAL_TYPES_COUNT); // 4K
1169 curveTypes = curveTypes_ref.initial;
1170 numCurves = 0;
1171 end = 0;
1172
1173 if (DO_STATS) {
1174 curveTypesUseMark = 0;
1175 curvesUseMark = 0;
1176 }
1177 }
1178
1179 /**
1180 * Disposes this PolyStack:
1181 * clean up before reusing this instance
1182 */
1183 void dispose() {
1184 end = 0;
1185 numCurves = 0;
1186
1187 if (DO_STATS) {
1188 rdrCtx.stats.stat_rdr_poly_stack_types.add(curveTypesUseMark);
1189 rdrCtx.stats.stat_rdr_poly_stack_curves.add(curvesUseMark);
1190 rdrCtx.stats.hist_rdr_poly_stack_curves.add(curvesUseMark);
1191
1192 // reset marks
1193 curveTypesUseMark = 0;
1194 curvesUseMark = 0;
1195 }
1196
1197 // Return arrays:
1198 // curves and curveTypes are kept dirty
1199 curves = curves_ref.putArray(curves);
1200 curveTypes = curveTypes_ref.putArray(curveTypes);
1201 }
1202
1203 private void ensureSpace(final int n) {
1204 // use substraction to avoid integer overflow:
1205 if (curves.length - end < n) {
1206 if (DO_STATS) {
1207 rdrCtx.stats.stat_array_stroker_polystack_curves
1208 .add(end + n);
1209 }
1210 curves = curves_ref.widenArray(curves, end, end + n);
1211 }
1212 if (curveTypes.length <= numCurves) {
1213 if (DO_STATS) {
1214 rdrCtx.stats.stat_array_stroker_polystack_curveTypes
1215 .add(numCurves + 1);
1216 }
1217 curveTypes = curveTypes_ref.widenArray(curveTypes,
1218 numCurves,
1219 numCurves + 1);
1220 }
1221 }
1222
1223 void pushCubic(double x0, double y0,
1224 double x1, double y1,
1225 double x2, double y2)
1226 {
1227 ensureSpace(6);
1228 curveTypes[numCurves++] = TYPE_CUBICTO;
1229 // we reverse the coordinate order to make popping easier
1230 final double[] _curves = curves;
1231 int e = end;
1232 _curves[e++] = x2; _curves[e++] = y2;
1233 _curves[e++] = x1; _curves[e++] = y1;
1234 _curves[e++] = x0; _curves[e++] = y0;
1235 end = e;
1236 }
1237
1238 void pushQuad(double x0, double y0,
1239 double x1, double y1)
1240 {
1241 ensureSpace(4);
1242 curveTypes[numCurves++] = TYPE_QUADTO;
1243 final double[] _curves = curves;
1244 int e = end;
1245 _curves[e++] = x1; _curves[e++] = y1;
1246 _curves[e++] = x0; _curves[e++] = y0;
1247 end = e;
1248 }
1249
1250 void pushLine(double x, double y) {
1251 ensureSpace(2);
1252 curveTypes[numCurves++] = TYPE_LINETO;
1253 curves[end++] = x; curves[end++] = y;
1254 }
1255
1256 void popAll(DPathConsumer2D io) {
1257 if (DO_STATS) {
1258 // update used marks:
1259 if (numCurves > curveTypesUseMark) {
1260 curveTypesUseMark = numCurves;
1261 }
1262 if (end > curvesUseMark) {
1263 curvesUseMark = end;
1264 }
1265 }
1266 final byte[] _curveTypes = curveTypes;
1267 final double[] _curves = curves;
1268 int nc = numCurves;
1269 int e = end;
1270
1271 while (nc != 0) {
1272 switch(_curveTypes[--nc]) {
1273 case TYPE_LINETO:
1274 e -= 2;
1275 io.lineTo(_curves[e], _curves[e+1]);
1276 continue;
1277 case TYPE_QUADTO:
1278 e -= 4;
1279 io.quadTo(_curves[e+0], _curves[e+1],
1280 _curves[e+2], _curves[e+3]);
1281 continue;
1282 case TYPE_CUBICTO:
1283 e -= 6;
1284 io.curveTo(_curves[e+0], _curves[e+1],
1285 _curves[e+2], _curves[e+3],
1286 _curves[e+4], _curves[e+5]);
1287 continue;
1288 default:
1289 }
1290 }
1291 numCurves = 0;
1292 end = 0;
1293 }
1294
1295 @Override
1296 public String toString() {
1297 String ret = "";
1298 int nc = numCurves;
1299 int last = end;
1300 int len;
1301 while (nc != 0) {
1302 switch(curveTypes[--nc]) {
1303 case TYPE_LINETO:
1304 len = 2;
1305 ret += "line: ";
1306 break;
1307 case TYPE_QUADTO:
1308 len = 4;
1309 ret += "quad: ";
1310 break;
1311 case TYPE_CUBICTO:
1312 len = 6;
1313 ret += "cubic: ";
1314 break;
1315 default:
1316 len = 0;
1317 }
1318 last -= len;
1319 ret += Arrays.toString(Arrays.copyOfRange(curves, last, last+len))
1320 + "\n";
1321 }
1322 return ret;
1323 }
1324 }
1325 }
|
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
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 import sun.java2d.marlin.DHelpers.PolyStack;
30
31 // TODO: some of the arithmetic here is too verbose and prone to hard to
32 // debug typos. We should consider making a small Point/Vector class that
33 // has methods like plus(Point), minus(Point), dot(Point), cross(Point)and such
34 final class DStroker implements DPathConsumer2D, MarlinConst {
35
36 private static final int MOVE_TO = 0;
37 private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad
38 private static final int CLOSE = 2;
39
40 /**
41 * Constant value for join style.
42 */
43 public static final int JOIN_MITER = 0;
44
45 /**
46 * Constant value for join style.
47 */
48 public static final int JOIN_ROUND = 1;
49
55 /**
56 * Constant value for end cap style.
57 */
58 public static final int CAP_BUTT = 0;
59
60 /**
61 * Constant value for end cap style.
62 */
63 public static final int CAP_ROUND = 1;
64
65 /**
66 * Constant value for end cap style.
67 */
68 public static final int CAP_SQUARE = 2;
69
70 // pisces used to use fixed point arithmetic with 16 decimal digits. I
71 // didn't want to change the values of the constant below when I converted
72 // it to floating point, so that's why the divisions by 2^16 are there.
73 private static final double ROUND_JOIN_THRESHOLD = 1000.0d/65536.0d;
74
75 // kappa = (4/3) * (SQRT(2) - 1)
76 private static final double C = (4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d);
77
78 // SQRT(2)
79 private static final double SQRT_2 = Math.sqrt(2.0d);
80
81 private static final int MAX_N_CURVES = 11;
82
83 private DPathConsumer2D out;
84
85 private int capStyle;
86 private int joinStyle;
87
88 private double lineWidth2;
89 private double invHalfLineWidth2Sq;
90
91 private final double[] offset0 = new double[2];
92 private final double[] offset1 = new double[2];
93 private final double[] offset2 = new double[2];
94 private final double[] miter = new double[2];
95 private double miterLimitSq;
96
97 private int prev;
98
99 // The starting point of the path, and the slope there.
106 // original path (thought they may have different directions), so these
107 // could be computed from sdx,sdy and cdx,cdy (and vice versa), but that
108 // would be error prone and hard to read, so we keep these anyway.
109 private double smx, smy, cmx, cmy;
110
111 private final PolyStack reverse;
112
113 // This is where the curve to be processed is put. We give it
114 // enough room to store all curves.
115 private final double[] middle = new double[MAX_N_CURVES * 6 + 2];
116 private final double[] lp = new double[8];
117 private final double[] rp = new double[8];
118 private final double[] subdivTs = new double[MAX_N_CURVES - 1];
119
120 // per-thread renderer context
121 final DRendererContext rdrCtx;
122
123 // dirty curve
124 final DCurve curve;
125
126 // Bounds of the drawing region, at pixel precision.
127 private double[] clipRect;
128
129 // the outcode of the current point
130 private int cOutCode = 0;
131
132 // the outcode of the starting point
133 private int sOutCode = 0;
134
135 // flag indicating if the path is opened (clipped)
136 private boolean opened = false;
137 // flag indicating if the starting point's cap is done
138 private boolean capStart = false;
139
140 /**
141 * Constructs a <code>DStroker</code>.
142 * @param rdrCtx per-thread renderer context
143 */
144 DStroker(final DRendererContext rdrCtx) {
145 this.rdrCtx = rdrCtx;
146
147 this.reverse = (rdrCtx.stats != null) ?
148 new PolyStack(rdrCtx,
149 rdrCtx.stats.stat_str_polystack_types,
150 rdrCtx.stats.stat_str_polystack_curves,
151 rdrCtx.stats.hist_str_polystack_curves,
152 rdrCtx.stats.stat_array_str_polystack_curves,
153 rdrCtx.stats.stat_array_str_polystack_types)
154 : new PolyStack(rdrCtx);
155
156 this.curve = rdrCtx.curve;
157 }
158
159 /**
160 * Inits the <code>DStroker</code>.
161 *
162 * @param pc2d an output <code>DPathConsumer2D</code>.
163 * @param lineWidth the desired line width in pixels
164 * @param capStyle the desired end cap style, one of
165 * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or
166 * <code>CAP_SQUARE</code>.
167 * @param joinStyle the desired line join style, one of
168 * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or
169 * <code>JOIN_BEVEL</code>.
170 * @param miterLimit the desired miter limit
171 * @param scale scaling factor applied to clip boundaries
172 * @return this instance
173 */
174 DStroker init(final DPathConsumer2D pc2d,
175 final double lineWidth,
176 final int capStyle,
177 final int joinStyle,
178 final double miterLimit,
179 final double scale)
180 {
181 this.out = pc2d;
182
183 this.lineWidth2 = lineWidth / 2.0d;
184 this.invHalfLineWidth2Sq = 1.0d / (2.0d * lineWidth2 * lineWidth2);
185 this.capStyle = capStyle;
186 this.joinStyle = joinStyle;
187
188 final double limit = miterLimit * lineWidth2;
189 this.miterLimitSq = limit * limit;
190
191 this.prev = CLOSE;
192
193 rdrCtx.stroking = 1;
194
195 if (rdrCtx.doClip) {
196 // Adjust the clipping rectangle with the stroker margin (miter limit, width)
197 double rdrOffX = 0.0d, rdrOffY = 0.0d;
198 double margin = lineWidth2;
199
200 if (capStyle == CAP_SQUARE) {
201 margin *= SQRT_2;
202 }
203 if ((joinStyle == JOIN_MITER) && (margin < limit)) {
204 margin = limit;
205 }
206 if (scale != 1.0d) {
207 margin *= scale;
208 rdrOffX = scale * DRenderer.RDR_OFFSET_X;
209 rdrOffY = scale * DRenderer.RDR_OFFSET_Y;
210 }
211
212 // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY
213 // adjust clip rectangle (ymin, ymax, xmin, xmax):
214 final double[] _clipRect = rdrCtx.clipRect;
215 _clipRect[0] -= margin - rdrOffY;
216 _clipRect[1] += margin + rdrOffY;
217 _clipRect[2] -= margin - rdrOffX;
218 _clipRect[3] += margin + rdrOffX;
219 this.clipRect = _clipRect;
220 } else {
221 this.clipRect = null;
222 }
223 return this; // fluent API
224 }
225
226 /**
227 * Disposes this stroker:
228 * clean up before reusing this instance
229 */
230 void dispose() {
231 reverse.dispose();
232
233 opened = false;
234 capStart = false;
235
236 if (DO_CLEAN_DIRTY) {
237 // Force zero-fill dirty arrays:
238 Arrays.fill(offset0, 0.0d);
239 Arrays.fill(offset1, 0.0d);
240 Arrays.fill(offset2, 0.0d);
241 Arrays.fill(miter, 0.0d);
242 Arrays.fill(middle, 0.0d);
243 Arrays.fill(lp, 0.0d);
244 Arrays.fill(rp, 0.0d);
245 Arrays.fill(subdivTs, 0.0d);
246 }
247 }
248
249 private static void computeOffset(final double lx, final double ly,
250 final double w, final double[] m)
251 {
252 double len = lx*lx + ly*ly;
253 if (len == 0.0d) {
254 m[0] = 0.0d;
255 m[1] = 0.0d;
486
487 computeMiter((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy,
488 (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my,
489 miter, 0);
490
491 final double miterX = miter[0];
492 final double miterY = miter[1];
493 double lenSq = (miterX-x0)*(miterX-x0) + (miterY-y0)*(miterY-y0);
494
495 // If the lines are parallel, lenSq will be either NaN or +inf
496 // (actually, I'm not sure if the latter is possible. The important
497 // thing is that -inf is not possible, because lenSq is a square).
498 // For both of those values, the comparison below will fail and
499 // no miter will be drawn, which is correct.
500 if (lenSq < miterLimitSq) {
501 emitLineTo(miterX, miterY, rev);
502 }
503 }
504
505 @Override
506 public void moveTo(final double x0, final double y0) {
507 moveTo(x0, y0, cOutCode);
508 // update starting point:
509 this.sx0 = x0;
510 this.sy0 = y0;
511 this.sdx = 1.0d;
512 this.sdy = 0.0d;
513 this.opened = false;
514 this.capStart = false;
515
516 if (clipRect != null) {
517 final int outcode = DHelpers.outcode(x0, y0, clipRect);
518 this.cOutCode = outcode;
519 this.sOutCode = outcode;
520 }
521 }
522
523 private void moveTo(final double x0, final double y0,
524 final int outcode)
525 {
526 if (prev == MOVE_TO) {
527 this.cx0 = x0;
528 this.cy0 = y0;
529 } else {
530 if (prev == DRAWING_OP_TO) {
531 finish(outcode);
532 }
533 this.prev = MOVE_TO;
534 this.cx0 = x0;
535 this.cy0 = y0;
536 this.cdx = 1.0d;
537 this.cdy = 0.0d;
538 }
539 }
540
541 @Override
542 public void lineTo(final double x1, final double y1) {
543 lineTo(x1, y1, false);
544 }
545
546 private void lineTo(final double x1, final double y1,
547 final boolean force)
548 {
549 final int outcode0 = this.cOutCode;
550 if (!force && clipRect != null) {
551 final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
552 this.cOutCode = outcode1;
553
554 // basic rejection criteria
555 if ((outcode0 & outcode1) != 0) {
556 moveTo(x1, y1, outcode0);
557 opened = true;
558 return;
559 }
560 }
561
562 double dx = x1 - cx0;
563 double dy = y1 - cy0;
564 if (dx == 0.0d && dy == 0.0d) {
565 dx = 1.0d;
566 }
567 computeOffset(dx, dy, lineWidth2, offset0);
568 final double mx = offset0[0];
569 final double my = offset0[1];
570
571 drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my, outcode0);
572
573 emitLineTo(cx0 + mx, cy0 + my);
574 emitLineTo( x1 + mx, y1 + my);
575
576 emitLineToRev(cx0 - mx, cy0 - my);
577 emitLineToRev( x1 - mx, y1 - my);
578
579 this.prev = DRAWING_OP_TO;
580 this.cx0 = x1;
581 this.cy0 = y1;
582 this.cdx = dx;
583 this.cdy = dy;
584 this.cmx = mx;
585 this.cmy = my;
586 }
587
588 @Override
589 public void closePath() {
590 // distinguish empty path at all vs opened path ?
591 if (prev != DRAWING_OP_TO && !opened) {
592 if (prev == CLOSE) {
593 return;
594 }
595 emitMoveTo(cx0, cy0 - lineWidth2);
596
597 this.sdx = 1.0d;
598 this.sdy = 0.0d;
599 this.cdx = 1.0d;
600 this.cdy = 0.0d;
601
602 this.smx = 0.0d;
603 this.smy = -lineWidth2;
604 this.cmx = 0.0d;
605 this.cmy = -lineWidth2;
606
607 finish(cOutCode);
608 return;
609 }
610
611 if (sOutCode == 0) {
612 if (cx0 != sx0 || cy0 != sy0) {
613 lineTo(sx0, sy0, true);
614 }
615
616 drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy, cOutCode);
617
618 emitLineTo(sx0 + smx, sy0 + smy);
619
620 if (opened) {
621 emitLineTo(sx0 - smx, sy0 - smy);
622 } else {
623 emitMoveTo(sx0 - smx, sy0 - smy);
624 }
625 }
626 // Ignore caps like finish(false)
627 emitReverse();
628
629 this.prev = CLOSE;
630
631 if (opened) {
632 // do not emit close
633 opened = false;
634 } else {
635 emitClose();
636 }
637 }
638
639 private void emitReverse() {
640 reverse.popAll(out);
641 }
642
643 @Override
644 public void pathDone() {
645 if (prev == DRAWING_OP_TO) {
646 finish(cOutCode);
647 }
648
649 out.pathDone();
650
651 // this shouldn't matter since this object won't be used
652 // after the call to this method.
653 this.prev = CLOSE;
654
655 // Dispose this instance:
656 dispose();
657 }
658
659 private void finish(final int outcode) {
660 // Problem: impossible to guess if the path will be closed in advance
661 // i.e. if caps must be drawn or not ?
662 // Solution: use the ClosedPathDetector before Stroker to determine
663 // if the path is a closed path or not
664 if (!rdrCtx.closedPath) {
665 if (outcode == 0) {
666 // current point = end's cap:
667 if (capStyle == CAP_ROUND) {
668 drawRoundCap(cx0, cy0, cmx, cmy);
669 } else if (capStyle == CAP_SQUARE) {
670 emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy);
671 emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy);
672 }
673 }
674 emitReverse();
675
676 if (!capStart) {
677 capStart = true;
678
679 if (sOutCode == 0) {
680 // starting point = initial cap:
681 if (capStyle == CAP_ROUND) {
682 drawRoundCap(sx0, sy0, -smx, -smy);
683 } else if (capStyle == CAP_SQUARE) {
684 emitLineTo(sx0 + smy - smx, sy0 - smx - smy);
685 emitLineTo(sx0 + smy + smx, sy0 - smx + smy);
686 }
687 }
688 }
689 } else {
690 emitReverse();
691 }
692 emitClose();
693 }
694
695 private void emitMoveTo(final double x0, final double y0) {
696 out.moveTo(x0, y0);
697 }
698
699 private void emitLineTo(final double x1, final double y1) {
700 out.lineTo(x1, y1);
701 }
702
703 private void emitLineToRev(final double x1, final double y1) {
704 reverse.pushLine(x1, y1);
705 }
706
707 private void emitLineTo(final double x1, final double y1,
708 final boolean rev)
709 {
710 if (rev) {
711 emitLineToRev(x1, y1);
743 private void emitCurveTo(final double x0, final double y0,
744 final double x1, final double y1,
745 final double x2, final double y2,
746 final double x3, final double y3, final boolean rev)
747 {
748 if (rev) {
749 reverse.pushCubic(x0, y0, x1, y1, x2, y2);
750 } else {
751 out.curveTo(x1, y1, x2, y2, x3, y3);
752 }
753 }
754
755 private void emitClose() {
756 out.closePath();
757 }
758
759 private void drawJoin(double pdx, double pdy,
760 double x0, double y0,
761 double dx, double dy,
762 double omx, double omy,
763 double mx, double my,
764 final int outcode)
765 {
766 if (prev != DRAWING_OP_TO) {
767 emitMoveTo(x0 + mx, y0 + my);
768 if (!opened) {
769 this.sdx = dx;
770 this.sdy = dy;
771 this.smx = mx;
772 this.smy = my;
773 }
774 } else {
775 final boolean cw = isCW(pdx, pdy, dx, dy);
776 if (outcode == 0) {
777 if (joinStyle == JOIN_MITER) {
778 drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
779 } else if (joinStyle == JOIN_ROUND) {
780 drawRoundJoin(x0, y0,
781 omx, omy,
782 mx, my, cw,
783 ROUND_JOIN_THRESHOLD);
784 }
785 }
786 emitLineTo(x0, y0, !cw);
787 }
788 prev = DRAWING_OP_TO;
789 }
790
791 private static boolean within(final double x1, final double y1,
792 final double x2, final double y2,
793 final double ERR)
794 {
795 assert ERR > 0 : "";
796 // compare taxicab distance. ERR will always be small, so using
797 // true distance won't give much benefit
798 return (DHelpers.within(x1, x2, ERR) && // we want to avoid calling Math.abs
799 DHelpers.within(y1, y2, ERR)); // this is just as good.
800 }
801
802 private void getLineOffsets(double x1, double y1,
803 double x2, double y2,
804 double[] left, double[] right) {
1069 int ret = 0;
1070 // we subdivide at values of t such that the remaining rotated
1071 // curves are monotonic in x and y.
1072 ret += c.dxRoots(ts, ret);
1073 ret += c.dyRoots(ts, ret);
1074 // subdivide at inflection points.
1075 if (type == 8) {
1076 // quadratic curves can't have inflection points
1077 ret += c.infPoints(ts, ret);
1078 }
1079
1080 // now we must subdivide at points where one of the offset curves will have
1081 // a cusp. This happens at ts where the radius of curvature is equal to w.
1082 ret += c.rootsOfROCMinusW(ts, ret, w, 0.0001d);
1083
1084 ret = DHelpers.filterOutNotInAB(ts, 0, ret, 0.0001d, 0.9999d);
1085 DHelpers.isort(ts, 0, ret);
1086 return ret;
1087 }
1088
1089 @Override
1090 public void curveTo(final double x1, final double y1,
1091 final double x2, final double y2,
1092 final double x3, final double y3)
1093 {
1094 final int outcode0 = this.cOutCode;
1095 if (clipRect != null) {
1096 final int outcode3 = DHelpers.outcode(x3, y3, clipRect);
1097 this.cOutCode = outcode3;
1098
1099 if (outcode3 != 0) {
1100 final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
1101 final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
1102
1103 // basic rejection criteria
1104 if ((outcode0 & outcode1 & outcode2 & outcode3) != 0) {
1105 moveTo(x3, y3, outcode0);
1106 opened = true;
1107 return;
1108 }
1109 }
1110 }
1111
1112 final double[] mid = middle;
1113
1114 mid[0] = cx0; mid[1] = cy0;
1115 mid[2] = x1; mid[3] = y1;
1116 mid[4] = x2; mid[5] = y2;
1117 mid[6] = x3; mid[7] = y3;
1118
1119 // need these so we can update the state at the end of this method
1120 final double xf = x3, yf = y3;
1121 double dxs = mid[2] - mid[0];
1122 double dys = mid[3] - mid[1];
1123 double dxf = mid[6] - mid[4];
1124 double dyf = mid[7] - mid[5];
1125
1126 boolean p1eqp2 = (dxs == 0.0d && dys == 0.0d);
1127 boolean p3eqp4 = (dxf == 0.0d && dyf == 0.0d);
1128 if (p1eqp2) {
1129 dxs = mid[4] - mid[0];
1130 dys = mid[5] - mid[1];
1131 if (dxs == 0.0d && dys == 0.0d) {
1132 dxs = mid[6] - mid[0];
1133 dys = mid[7] - mid[1];
1134 }
1135 }
1136 if (p3eqp4) {
1137 dxf = mid[6] - mid[2];
1138 dyf = mid[7] - mid[3];
1139 if (dxf == 0.0d && dyf == 0.0d) {
1140 dxf = mid[6] - mid[0];
1141 dyf = mid[7] - mid[1];
1142 }
1143 }
1144 if (dxs == 0.0d && dys == 0.0d) {
1145 // this happens if the "curve" is just a point
1146 // fix outcode0 for lineTo() call:
1147 if (clipRect != null) {
1148 this.cOutCode = outcode0;
1149 }
1150 lineTo(mid[0], mid[1]);
1151 return;
1152 }
1153
1154 // if these vectors are too small, normalize them, to avoid future
1155 // precision problems.
1156 if (Math.abs(dxs) < 0.1d && Math.abs(dys) < 0.1d) {
1157 double len = Math.sqrt(dxs*dxs + dys*dys);
1158 dxs /= len;
1159 dys /= len;
1160 }
1161 if (Math.abs(dxf) < 0.1d && Math.abs(dyf) < 0.1d) {
1162 double len = Math.sqrt(dxf*dxf + dyf*dyf);
1163 dxf /= len;
1164 dyf /= len;
1165 }
1166
1167 computeOffset(dxs, dys, lineWidth2, offset0);
1168 drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
1169
1170 final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2);
1171
1172 double prevT = 0.0d;
1173 for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
1174 final double t = subdivTs[i];
1175 DHelpers.subdivideCubicAt((t - prevT) / (1.0d - prevT),
1176 mid, off, mid, off, mid, off + 6);
1177 prevT = t;
1178 }
1179
1180 final double[] l = lp;
1181 final double[] r = rp;
1182
1183 int kind = 0;
1184 for (int i = 0, off = 0; i <= nSplits; i++, off += 6) {
1185 kind = computeOffsetCubic(mid, off, l, r);
1186
1187 emitLineTo(l[0], l[1]);
1188
1189 switch(kind) {
1190 case 8:
1191 emitCurveTo(l[2], l[3], l[4], l[5], l[6], l[7]);
1192 emitCurveToRev(r[0], r[1], r[2], r[3], r[4], r[5]);
1193 break;
1194 case 4:
1195 emitLineTo(l[2], l[3]);
1196 emitLineToRev(r[0], r[1]);
1197 break;
1198 default:
1199 }
1200 emitLineToRev(r[kind - 2], r[kind - 1]);
1201 }
1202
1203 this.prev = DRAWING_OP_TO;
1204 this.cx0 = xf;
1205 this.cy0 = yf;
1206 this.cdx = dxf;
1207 this.cdy = dyf;
1208 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
1209 this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
1210 }
1211
1212 @Override
1213 public void quadTo(final double x1, final double y1,
1214 final double x2, final double y2)
1215 {
1216 final int outcode0 = this.cOutCode;
1217 if (clipRect != null) {
1218 final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
1219 this.cOutCode = outcode2;
1220
1221 if (outcode2 != 0) {
1222 final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
1223
1224 // basic rejection criteria
1225 if ((outcode0 & outcode1 & outcode2) != 0) {
1226 moveTo(x2, y2, outcode0);
1227 opened = true;
1228 return;
1229 }
1230 }
1231 }
1232
1233 final double[] mid = middle;
1234
1235 mid[0] = cx0; mid[1] = cy0;
1236 mid[2] = x1; mid[3] = y1;
1237 mid[4] = x2; mid[5] = y2;
1238
1239 // need these so we can update the state at the end of this method
1240 final double xf = x2, yf = y2;
1241 double dxs = mid[2] - mid[0];
1242 double dys = mid[3] - mid[1];
1243 double dxf = mid[4] - mid[2];
1244 double dyf = mid[5] - mid[3];
1245 if ((dxs == 0.0d && dys == 0.0d) || (dxf == 0.0d && dyf == 0.0d)) {
1246 dxs = dxf = mid[4] - mid[0];
1247 dys = dyf = mid[5] - mid[1];
1248 }
1249 if (dxs == 0.0d && dys == 0.0d) {
1250 // this happens if the "curve" is just a point
1251 // fix outcode0 for lineTo() call:
1252 if (clipRect != null) {
1253 this.cOutCode = outcode0;
1254 }
1255 lineTo(mid[0], mid[1]);
1256 return;
1257 }
1258 // if these vectors are too small, normalize them, to avoid future
1259 // precision problems.
1260 if (Math.abs(dxs) < 0.1d && Math.abs(dys) < 0.1d) {
1261 double len = Math.sqrt(dxs*dxs + dys*dys);
1262 dxs /= len;
1263 dys /= len;
1264 }
1265 if (Math.abs(dxf) < 0.1d && Math.abs(dyf) < 0.1d) {
1266 double len = Math.sqrt(dxf*dxf + dyf*dyf);
1267 dxf /= len;
1268 dyf /= len;
1269 }
1270
1271 computeOffset(dxs, dys, lineWidth2, offset0);
1272 drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
1273
1274 int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2);
1275
1276 double prevt = 0.0d;
1277 for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
1278 final double t = subdivTs[i];
1279 DHelpers.subdivideQuadAt((t - prevt) / (1.0d - prevt),
1280 mid, off, mid, off, mid, off + 4);
1281 prevt = t;
1282 }
1283
1284 final double[] l = lp;
1285 final double[] r = rp;
1286
1287 int kind = 0;
1288 for (int i = 0, off = 0; i <= nSplits; i++, off += 4) {
1289 kind = computeOffsetQuad(mid, off, l, r);
1290
1291 emitLineTo(l[0], l[1]);
1292
1293 switch(kind) {
1294 case 6:
1295 emitQuadTo(l[2], l[3], l[4], l[5]);
1296 emitQuadToRev(r[0], r[1], r[2], r[3]);
1297 break;
1298 case 4:
1299 emitLineTo(l[2], l[3]);
1300 emitLineToRev(r[0], r[1]);
1301 break;
1302 default:
1303 }
1304 emitLineToRev(r[kind - 2], r[kind - 1]);
1305 }
1306
1307 this.prev = DRAWING_OP_TO;
1308 this.cx0 = xf;
1309 this.cy0 = yf;
1310 this.cdx = dxf;
1311 this.cdy = dyf;
1312 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
1313 this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
1314 }
1315
1316 @Override public long getNativeConsumer() {
1317 throw new InternalError("Stroker doesn't use a native consumer");
1318 }
1319 }
|