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