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