< prev index next >

src/java.desktop/share/classes/sun/java2d/marlin/Stroker.java

Print this page




  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.java2d.marlin;
  27 
  28 import java.util.Arrays;
  29 
  30 import sun.awt.geom.PathConsumer2D;

  31 
  32 // TODO: some of the arithmetic here is too verbose and prone to hard to
  33 // debug typos. We should consider making a small Point/Vector class that
  34 // has methods like plus(Point), minus(Point), dot(Point), cross(Point)and such
  35 final class Stroker implements PathConsumer2D, MarlinConst {
  36 
  37     private static final int MOVE_TO = 0;
  38     private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad
  39     private static final int CLOSE = 2;
  40 
  41     /**
  42      * Constant value for join style.
  43      */
  44     public static final int JOIN_MITER = 0;
  45 
  46     /**
  47      * Constant value for join style.
  48      */
  49     public static final int JOIN_ROUND = 1;
  50 


 103     // original path (thought they may have different directions), so these
 104     // could be computed from sdx,sdy and cdx,cdy (and vice versa), but that
 105     // would be error prone and hard to read, so we keep these anyway.
 106     private float smx, smy, cmx, cmy;
 107 
 108     private final PolyStack reverse;
 109 
 110     // This is where the curve to be processed is put. We give it
 111     // enough room to store all curves.
 112     private final float[] middle = new float[MAX_N_CURVES * 6 + 2];
 113     private final float[] lp = new float[8];
 114     private final float[] rp = new float[8];
 115     private final float[] subdivTs = new float[MAX_N_CURVES - 1];
 116 
 117     // per-thread renderer context
 118     final RendererContext rdrCtx;
 119 
 120     // dirty curve
 121     final Curve curve;
 122 














 123     /**
 124      * Constructs a <code>Stroker</code>.
 125      * @param rdrCtx per-thread renderer context
 126      */
 127     Stroker(final RendererContext rdrCtx) {
 128         this.rdrCtx = rdrCtx;
 129 
 130         this.reverse = new PolyStack(rdrCtx);








 131         this.curve = rdrCtx.curve;
 132     }
 133 
 134     /**
 135      * Inits the <code>Stroker</code>.
 136      *
 137      * @param pc2d an output <code>PathConsumer2D</code>.
 138      * @param lineWidth the desired line width in pixels
 139      * @param capStyle the desired end cap style, one of
 140      * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or
 141      * <code>CAP_SQUARE</code>.
 142      * @param joinStyle the desired line join style, one of
 143      * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or
 144      * <code>JOIN_BEVEL</code>.
 145      * @param miterLimit the desired miter limit

 146      * @return this instance
 147      */
 148     Stroker init(PathConsumer2D pc2d,
 149               float lineWidth,
 150               int capStyle,
 151               int joinStyle,
 152               float miterLimit)

 153     {
 154         this.out = pc2d;
 155 
 156         this.lineWidth2 = lineWidth / 2.0f;
 157         this.invHalfLineWidth2Sq = 1.0f / (2.0f * lineWidth2 * lineWidth2);
 158         this.capStyle = capStyle;
 159         this.joinStyle = joinStyle;
 160 
 161         float limit = miterLimit * lineWidth2;
 162         this.miterLimitSq = limit * limit;
 163 
 164         this.prev = CLOSE;
 165 
 166         rdrCtx.stroking = 1;
 167 



































 168         return this; // fluent API
 169     }
 170 
 171     /**
 172      * Disposes this stroker:
 173      * clean up before reusing this instance
 174      */
 175     void dispose() {
 176         reverse.dispose();
 177 



 178         if (DO_CLEAN_DIRTY) {
 179             // Force zero-fill dirty arrays:
 180             Arrays.fill(offset0, 0.0f);
 181             Arrays.fill(offset1, 0.0f);
 182             Arrays.fill(offset2, 0.0f);
 183             Arrays.fill(miter, 0.0f);
 184             Arrays.fill(middle, 0.0f);
 185             Arrays.fill(lp, 0.0f);
 186             Arrays.fill(rp, 0.0f);
 187             Arrays.fill(subdivTs, 0.0f);
 188         }
 189     }
 190 
 191     private static void computeOffset(final float lx, final float ly,
 192                                       final float w, final float[] m)
 193     {
 194         float len = lx*lx + ly*ly;
 195         if (len == 0.0f) {
 196             m[0] = 0.0f;
 197             m[1] = 0.0f;


 429         computeMiter((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy,
 430                      (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my,
 431                      miter, 0);
 432 
 433         final float miterX = miter[0];
 434         final float miterY = miter[1];
 435         float lenSq = (miterX-x0)*(miterX-x0) + (miterY-y0)*(miterY-y0);
 436 
 437         // If the lines are parallel, lenSq will be either NaN or +inf
 438         // (actually, I'm not sure if the latter is possible. The important
 439         // thing is that -inf is not possible, because lenSq is a square).
 440         // For both of those values, the comparison below will fail and
 441         // no miter will be drawn, which is correct.
 442         if (lenSq < miterLimitSq) {
 443             emitLineTo(miterX, miterY, rev);
 444         }
 445     }
 446 
 447     @Override
 448     public void moveTo(float x0, float y0) {
 449         if (prev == DRAWING_OP_TO) {
 450             finish();





























 451         }
 452         this.sx0 = this.cx0 = x0;
 453         this.sy0 = this.cy0 = y0;
 454         this.cdx = this.sdx = 1.0f;
 455         this.cdy = this.sdy = 0.0f;
 456         this.prev = MOVE_TO;
 457     }
 458 
 459     @Override
 460     public void lineTo(float x1, float y1) {

















 461         float dx = x1 - cx0;
 462         float dy = y1 - cy0;
 463         if (dx == 0.0f && dy == 0.0f) {
 464             dx = 1.0f;
 465         }
 466         computeOffset(dx, dy, lineWidth2, offset0);
 467         final float mx = offset0[0];
 468         final float my = offset0[1];
 469 
 470         drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my);
 471 
 472         emitLineTo(cx0 + mx, cy0 + my);
 473         emitLineTo( x1 + mx,  y1 + my);
 474 
 475         emitLineToRev(cx0 - mx, cy0 - my);
 476         emitLineToRev( x1 - mx,  y1 - my);
 477 
 478         this.cmx = mx;
 479         this.cmy = my;
 480         this.cdx = dx;
 481         this.cdy = dy;
 482         this.cx0 = x1;
 483         this.cy0 = y1;
 484         this.prev = DRAWING_OP_TO;



 485     }
 486 
 487     @Override
 488     public void closePath() {
 489         if (prev != DRAWING_OP_TO) {

 490             if (prev == CLOSE) {
 491                 return;
 492             }
 493             emitMoveTo(cx0, cy0 - lineWidth2);
 494             this.cmx = this.smx = 0.0f;
 495             this.cmy = this.smy = -lineWidth2;
 496             this.cdx = this.sdx = 1.0f;
 497             this.cdy = this.sdy = 0.0f;
 498             finish();







 499             return;
 500         }
 501 
 502         if (cx0 != sx0 || cy0 != sy0) {
 503             lineTo(sx0, sy0);
 504         }

 505 
 506         drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy);
 507 
 508         emitLineTo(sx0 + smx, sy0 + smy);
 509 
 510         emitMoveTo(sx0 - smx, sy0 - smy);






 511         emitReverse();
 512 
 513         this.prev = CLOSE;
 514         emitClose();






 515     }
 516 
 517     private void emitReverse() {
 518         reverse.popAll(out);
 519     }
 520 
 521     @Override
 522     public void pathDone() {
 523         if (prev == DRAWING_OP_TO) {
 524             finish();
 525         }
 526 
 527         out.pathDone();
 528 
 529         // this shouldn't matter since this object won't be used
 530         // after the call to this method.
 531         this.prev = CLOSE;
 532 
 533         // Dispose this instance:
 534         dispose();
 535     }
 536 
 537     private void finish() {
 538         if (capStyle == CAP_ROUND) {
 539             drawRoundCap(cx0, cy0, cmx, cmy);
 540         } else if (capStyle == CAP_SQUARE) {
 541             emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy);
 542             emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy);
 543         }









 544 
 545         emitReverse();

 546 
 547         if (capStyle == CAP_ROUND) {
 548             drawRoundCap(sx0, sy0, -smx, -smy);
 549         } else if (capStyle == CAP_SQUARE) {
 550             emitLineTo(sx0 + smy - smx, sy0 - smx - smy);
 551             emitLineTo(sx0 + smy + smx, sy0 - smx + smy);







 552         }
 553 
 554         emitClose();
 555     }
 556 
 557     private void emitMoveTo(final float x0, final float y0) {
 558         out.moveTo(x0, y0);
 559     }
 560 
 561     private void emitLineTo(final float x1, final float y1) {
 562         out.lineTo(x1, y1);
 563     }
 564 
 565     private void emitLineToRev(final float x1, final float y1) {
 566         reverse.pushLine(x1, y1);
 567     }
 568 
 569     private void emitLineTo(final float x1, final float y1,
 570                             final boolean rev)
 571     {
 572         if (rev) {
 573             emitLineToRev(x1, y1);


 605     private void emitCurveTo(final float x0, final float y0,
 606                              final float x1, final float y1,
 607                              final float x2, final float y2,
 608                              final float x3, final float y3, final boolean rev)
 609     {
 610         if (rev) {
 611             reverse.pushCubic(x0, y0, x1, y1, x2, y2);
 612         } else {
 613             out.curveTo(x1, y1, x2, y2, x3, y3);
 614         }
 615     }
 616 
 617     private void emitClose() {
 618         out.closePath();
 619     }
 620 
 621     private void drawJoin(float pdx, float pdy,
 622                           float x0, float y0,
 623                           float dx, float dy,
 624                           float omx, float omy,
 625                           float mx, float my)

 626     {
 627         if (prev != DRAWING_OP_TO) {
 628             emitMoveTo(x0 + mx, y0 + my);
 629             this.sdx = dx;
 630             this.sdy = dy;
 631             this.smx = mx;
 632             this.smy = my;


 633         } else {
 634             boolean cw = isCW(pdx, pdy, dx, dy);
 635             if (joinStyle == JOIN_MITER) {
 636                 drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
 637             } else if (joinStyle == JOIN_ROUND) {
 638                 drawRoundJoin(x0, y0,
 639                               omx, omy,
 640                               mx, my, cw,
 641                               ROUND_JOIN_THRESHOLD);


 642             }
 643             emitLineTo(x0, y0, !cw);
 644         }
 645         prev = DRAWING_OP_TO;
 646     }
 647 
 648     private static boolean within(final float x1, final float y1,
 649                                   final float x2, final float y2,
 650                                   final float ERR)
 651     {
 652         assert ERR > 0 : "";
 653         // compare taxicab distance. ERR will always be small, so using
 654         // true distance won't give much benefit
 655         return (Helpers.within(x1, x2, ERR) &&  // we want to avoid calling Math.abs
 656                 Helpers.within(y1, y2, ERR)); // this is just as good.
 657     }
 658 
 659     private void getLineOffsets(float x1, float y1,
 660                                 float x2, float y2,
 661                                 float[] left, float[] right) {


 926         int ret = 0;
 927         // we subdivide at values of t such that the remaining rotated
 928         // curves are monotonic in x and y.
 929         ret += c.dxRoots(ts, ret);
 930         ret += c.dyRoots(ts, ret);
 931         // subdivide at inflection points.
 932         if (type == 8) {
 933             // quadratic curves can't have inflection points
 934             ret += c.infPoints(ts, ret);
 935         }
 936 
 937         // now we must subdivide at points where one of the offset curves will have
 938         // a cusp. This happens at ts where the radius of curvature is equal to w.
 939         ret += c.rootsOfROCMinusW(ts, ret, w, 0.0001f);
 940 
 941         ret = Helpers.filterOutNotInAB(ts, 0, ret, 0.0001f, 0.9999f);
 942         Helpers.isort(ts, 0, ret);
 943         return ret;
 944     }
 945 
 946     @Override public void curveTo(float x1, float y1,
 947                                   float x2, float y2,
 948                                   float x3, float y3)
 949     {



















 950         final float[] mid = middle;
 951 
 952         mid[0] = cx0; mid[1] = cy0;
 953         mid[2] = x1;  mid[3] = y1;
 954         mid[4] = x2;  mid[5] = y2;
 955         mid[6] = x3;  mid[7] = y3;
 956 
 957         // need these so we can update the state at the end of this method
 958         final float xf = mid[6], yf = mid[7];
 959         float dxs = mid[2] - mid[0];
 960         float dys = mid[3] - mid[1];
 961         float dxf = mid[6] - mid[4];
 962         float dyf = mid[7] - mid[5];
 963 
 964         boolean p1eqp2 = (dxs == 0.0f && dys == 0.0f);
 965         boolean p3eqp4 = (dxf == 0.0f && dyf == 0.0f);
 966         if (p1eqp2) {
 967             dxs = mid[4] - mid[0];
 968             dys = mid[5] - mid[1];
 969             if (dxs == 0.0f && dys == 0.0f) {
 970                 dxs = mid[6] - mid[0];
 971                 dys = mid[7] - mid[1];
 972             }
 973         }
 974         if (p3eqp4) {
 975             dxf = mid[6] - mid[2];
 976             dyf = mid[7] - mid[3];
 977             if (dxf == 0.0f && dyf == 0.0f) {
 978                 dxf = mid[6] - mid[0];
 979                 dyf = mid[7] - mid[1];
 980             }
 981         }
 982         if (dxs == 0.0f && dys == 0.0f) {
 983             // this happens if the "curve" is just a point




 984             lineTo(mid[0], mid[1]);
 985             return;
 986         }
 987 
 988         // if these vectors are too small, normalize them, to avoid future
 989         // precision problems.
 990         if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) {
 991             float len = (float) Math.sqrt(dxs*dxs + dys*dys);
 992             dxs /= len;
 993             dys /= len;
 994         }
 995         if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) {
 996             float len = (float) Math.sqrt(dxf*dxf + dyf*dyf);
 997             dxf /= len;
 998             dyf /= len;
 999         }
1000 
1001         computeOffset(dxs, dys, lineWidth2, offset0);
1002         drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]);
1003 
1004         final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2);
1005 
1006         float prevT = 0.0f;
1007         for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
1008             final float t = subdivTs[i];
1009             Helpers.subdivideCubicAt((t - prevT) / (1.0f - prevT),
1010                                      mid, off, mid, off, mid, off + 6);
1011             prevT = t;
1012         }
1013 
1014         final float[] l = lp;
1015         final float[] r = rp;
1016 
1017         int kind = 0;
1018         for (int i = 0, off = 0; i <= nSplits; i++, off += 6) {
1019             kind = computeOffsetCubic(mid, off, l, r);
1020 
1021             emitLineTo(l[0], l[1]);
1022 
1023             switch(kind) {
1024             case 8:
1025                 emitCurveTo(l[2], l[3], l[4], l[5], l[6], l[7]);
1026                 emitCurveToRev(r[0], r[1], r[2], r[3], r[4], r[5]);
1027                 break;
1028             case 4:
1029                 emitLineTo(l[2], l[3]);
1030                 emitLineToRev(r[0], r[1]);
1031                 break;
1032             default:
1033             }
1034             emitLineToRev(r[kind - 2], r[kind - 1]);
1035         }
1036 
1037         this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
1038         this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f;
1039         this.cdx = dxf;
1040         this.cdy = dyf;
1041         this.cx0 = xf;
1042         this.cy0 = yf;
1043         this.prev = DRAWING_OP_TO;



1044     }
1045 
1046     @Override public void quadTo(float x1, float y1, float x2, float y2) {




















1047         final float[] mid = middle;
1048 
1049         mid[0] = cx0; mid[1] = cy0;
1050         mid[2] = x1;  mid[3] = y1;
1051         mid[4] = x2;  mid[5] = y2;
1052 
1053         // need these so we can update the state at the end of this method
1054         final float xf = mid[4], yf = mid[5];
1055         float dxs = mid[2] - mid[0];
1056         float dys = mid[3] - mid[1];
1057         float dxf = mid[4] - mid[2];
1058         float dyf = mid[5] - mid[3];
1059         if ((dxs == 0.0f && dys == 0.0f) || (dxf == 0.0f && dyf == 0.0f)) {
1060             dxs = dxf = mid[4] - mid[0];
1061             dys = dyf = mid[5] - mid[1];
1062         }
1063         if (dxs == 0.0f && dys == 0.0f) {
1064             // this happens if the "curve" is just a point




1065             lineTo(mid[0], mid[1]);
1066             return;
1067         }
1068         // if these vectors are too small, normalize them, to avoid future
1069         // precision problems.
1070         if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) {
1071             float len = (float) Math.sqrt(dxs*dxs + dys*dys);
1072             dxs /= len;
1073             dys /= len;
1074         }
1075         if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) {
1076             float len = (float) Math.sqrt(dxf*dxf + dyf*dyf);
1077             dxf /= len;
1078             dyf /= len;
1079         }
1080 
1081         computeOffset(dxs, dys, lineWidth2, offset0);
1082         drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]);
1083 
1084         int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2);
1085 
1086         float prevt = 0.0f;
1087         for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
1088             final float t = subdivTs[i];
1089             Helpers.subdivideQuadAt((t - prevt) / (1.0f - prevt),
1090                                     mid, off, mid, off, mid, off + 4);
1091             prevt = t;
1092         }
1093 
1094         final float[] l = lp;
1095         final float[] r = rp;
1096 
1097         int kind = 0;
1098         for (int i = 0, off = 0; i <= nSplits; i++, off += 4) {
1099             kind = computeOffsetQuad(mid, off, l, r);
1100 
1101             emitLineTo(l[0], l[1]);
1102 
1103             switch(kind) {
1104             case 6:
1105                 emitQuadTo(l[2], l[3], l[4], l[5]);
1106                 emitQuadToRev(r[0], r[1], r[2], r[3]);
1107                 break;
1108             case 4:
1109                 emitLineTo(l[2], l[3]);
1110                 emitLineToRev(r[0], r[1]);
1111                 break;
1112             default:
1113             }
1114             emitLineToRev(r[kind - 2], r[kind - 1]);
1115         }
1116 
1117         this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
1118         this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f;
1119         this.cdx = dxf;
1120         this.cdy = dyf;
1121         this.cx0 = xf;
1122         this.cy0 = yf;
1123         this.prev = DRAWING_OP_TO;



1124     }
1125 
1126     @Override public long getNativeConsumer() {
1127         throw new InternalError("Stroker doesn't use a native consumer");
1128     }
1129 
1130     // a stack of polynomial curves where each curve shares endpoints with
1131     // adjacent ones.
1132     static final class PolyStack {
1133         private static final byte TYPE_LINETO  = (byte) 0;
1134         private static final byte TYPE_QUADTO  = (byte) 1;
1135         private static final byte TYPE_CUBICTO = (byte) 2;
1136 
1137         // curves capacity = edges count (8192) = edges x 2 (coords)
1138         private static final int INITIAL_CURVES_COUNT = INITIAL_EDGES_COUNT << 1;
1139 
1140         // types capacity = edges count (4096)
1141         private static final int INITIAL_TYPES_COUNT = INITIAL_EDGES_COUNT;
1142 
1143         float[] curves;
1144         int end;
1145         byte[] curveTypes;
1146         int numCurves;
1147 
1148         // per-thread renderer context
1149         final RendererContext rdrCtx;
1150 
1151         // curves ref (dirty)
1152         final FloatArrayCache.Reference curves_ref;
1153         // curveTypes ref (dirty)
1154         final ByteArrayCache.Reference curveTypes_ref;
1155 
1156         // used marks (stats only)
1157         int curveTypesUseMark;
1158         int curvesUseMark;
1159 
1160         /**
1161          * Constructor
1162          * @param rdrCtx per-thread renderer context
1163          */
1164         PolyStack(final RendererContext rdrCtx) {
1165             this.rdrCtx = rdrCtx;
1166 
1167             curves_ref = rdrCtx.newDirtyFloatArrayRef(INITIAL_CURVES_COUNT); // 32K
1168             curves     = curves_ref.initial;
1169 
1170             curveTypes_ref = rdrCtx.newDirtyByteArrayRef(INITIAL_TYPES_COUNT); // 4K
1171             curveTypes     = curveTypes_ref.initial;
1172             numCurves = 0;
1173             end = 0;
1174 
1175             if (DO_STATS) {
1176                 curveTypesUseMark = 0;
1177                 curvesUseMark = 0;
1178             }
1179         }
1180 
1181         /**
1182          * Disposes this PolyStack:
1183          * clean up before reusing this instance
1184          */
1185         void dispose() {
1186             end = 0;
1187             numCurves = 0;
1188 
1189             if (DO_STATS) {
1190                 rdrCtx.stats.stat_rdr_poly_stack_types.add(curveTypesUseMark);
1191                 rdrCtx.stats.stat_rdr_poly_stack_curves.add(curvesUseMark);
1192                 rdrCtx.stats.hist_rdr_poly_stack_curves.add(curvesUseMark);
1193 
1194                 // reset marks
1195                 curveTypesUseMark = 0;
1196                 curvesUseMark = 0;
1197             }
1198 
1199             // Return arrays:
1200             // curves and curveTypes are kept dirty
1201             curves     = curves_ref.putArray(curves);
1202             curveTypes = curveTypes_ref.putArray(curveTypes);
1203         }
1204 
1205         private void ensureSpace(final int n) {
1206             // use substraction to avoid integer overflow:
1207             if (curves.length - end < n) {
1208                 if (DO_STATS) {
1209                     rdrCtx.stats.stat_array_stroker_polystack_curves
1210                         .add(end + n);
1211                 }
1212                 curves = curves_ref.widenArray(curves, end, end + n);
1213             }
1214             if (curveTypes.length <= numCurves) {
1215                 if (DO_STATS) {
1216                     rdrCtx.stats.stat_array_stroker_polystack_curveTypes
1217                         .add(numCurves + 1);
1218                 }
1219                 curveTypes = curveTypes_ref.widenArray(curveTypes,
1220                                                        numCurves,
1221                                                        numCurves + 1);
1222             }
1223         }
1224 
1225         void pushCubic(float x0, float y0,
1226                        float x1, float y1,
1227                        float x2, float y2)
1228         {
1229             ensureSpace(6);
1230             curveTypes[numCurves++] = TYPE_CUBICTO;
1231             // we reverse the coordinate order to make popping easier
1232             final float[] _curves = curves;
1233             int e = end;
1234             _curves[e++] = x2;    _curves[e++] = y2;
1235             _curves[e++] = x1;    _curves[e++] = y1;
1236             _curves[e++] = x0;    _curves[e++] = y0;
1237             end = e;
1238         }
1239 
1240         void pushQuad(float x0, float y0,
1241                       float x1, float y1)
1242         {
1243             ensureSpace(4);
1244             curveTypes[numCurves++] = TYPE_QUADTO;
1245             final float[] _curves = curves;
1246             int e = end;
1247             _curves[e++] = x1;    _curves[e++] = y1;
1248             _curves[e++] = x0;    _curves[e++] = y0;
1249             end = e;
1250         }
1251 
1252         void pushLine(float x, float y) {
1253             ensureSpace(2);
1254             curveTypes[numCurves++] = TYPE_LINETO;
1255             curves[end++] = x;    curves[end++] = y;
1256         }
1257 
1258         void popAll(PathConsumer2D io) {
1259             if (DO_STATS) {
1260                 // update used marks:
1261                 if (numCurves > curveTypesUseMark) {
1262                     curveTypesUseMark = numCurves;
1263                 }
1264                 if (end > curvesUseMark) {
1265                     curvesUseMark = end;
1266                 }
1267             }
1268             final byte[]  _curveTypes = curveTypes;
1269             final float[] _curves = curves;
1270             int nc = numCurves;
1271             int e  = end;
1272 
1273             while (nc != 0) {
1274                 switch(_curveTypes[--nc]) {
1275                 case TYPE_LINETO:
1276                     e -= 2;
1277                     io.lineTo(_curves[e], _curves[e+1]);
1278                     continue;
1279                 case TYPE_QUADTO:
1280                     e -= 4;
1281                     io.quadTo(_curves[e+0], _curves[e+1],
1282                               _curves[e+2], _curves[e+3]);
1283                     continue;
1284                 case TYPE_CUBICTO:
1285                     e -= 6;
1286                     io.curveTo(_curves[e+0], _curves[e+1],
1287                                _curves[e+2], _curves[e+3],
1288                                _curves[e+4], _curves[e+5]);
1289                     continue;
1290                 default:
1291                 }
1292             }
1293             numCurves = 0;
1294             end = 0;
1295         }
1296 
1297         @Override
1298         public String toString() {
1299             String ret = "";
1300             int nc = numCurves;
1301             int last = end;
1302             int len;
1303             while (nc != 0) {
1304                 switch(curveTypes[--nc]) {
1305                 case TYPE_LINETO:
1306                     len = 2;
1307                     ret += "line: ";
1308                     break;
1309                 case TYPE_QUADTO:
1310                     len = 4;
1311                     ret += "quad: ";
1312                     break;
1313                 case TYPE_CUBICTO:
1314                     len = 6;
1315                     ret += "cubic: ";
1316                     break;
1317                 default:
1318                     len = 0;
1319                 }
1320                 last -= len;
1321                 ret += Arrays.toString(Arrays.copyOfRange(curves, last, last+len))
1322                                        + "\n";
1323             }
1324             return ret;
1325         }
1326     }
1327 }


  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.java2d.marlin;
  27 
  28 import java.util.Arrays;
  29 
  30 import sun.awt.geom.PathConsumer2D;
  31 import sun.java2d.marlin.Helpers.PolyStack;
  32 
  33 // TODO: some of the arithmetic here is too verbose and prone to hard to
  34 // debug typos. We should consider making a small Point/Vector class that
  35 // has methods like plus(Point), minus(Point), dot(Point), cross(Point)and such
  36 final class Stroker implements PathConsumer2D, MarlinConst {
  37 
  38     private static final int MOVE_TO = 0;
  39     private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad
  40     private static final int CLOSE = 2;
  41 
  42     /**
  43      * Constant value for join style.
  44      */
  45     public static final int JOIN_MITER = 0;
  46 
  47     /**
  48      * Constant value for join style.
  49      */
  50     public static final int JOIN_ROUND = 1;
  51 


 104     // original path (thought they may have different directions), so these
 105     // could be computed from sdx,sdy and cdx,cdy (and vice versa), but that
 106     // would be error prone and hard to read, so we keep these anyway.
 107     private float smx, smy, cmx, cmy;
 108 
 109     private final PolyStack reverse;
 110 
 111     // This is where the curve to be processed is put. We give it
 112     // enough room to store all curves.
 113     private final float[] middle = new float[MAX_N_CURVES * 6 + 2];
 114     private final float[] lp = new float[8];
 115     private final float[] rp = new float[8];
 116     private final float[] subdivTs = new float[MAX_N_CURVES - 1];
 117 
 118     // per-thread renderer context
 119     final RendererContext rdrCtx;
 120 
 121     // dirty curve
 122     final Curve curve;
 123 
 124     // Bounds of the drawing region, at pixel precision.
 125     private float[] clipRect;
 126 
 127     // the outcode of the current point
 128     private int cOutCode = 0;
 129 
 130     // the outcode of the starting point
 131     private int sOutCode = 0;
 132 
 133     // flag indicating if the path is opened (clipped)
 134     private boolean opened = false;
 135     // flag indicating if the starting point's cap is done
 136     private boolean capStart = false;
 137 
 138     /**
 139      * Constructs a <code>Stroker</code>.
 140      * @param rdrCtx per-thread renderer context
 141      */
 142     Stroker(final RendererContext rdrCtx) {
 143         this.rdrCtx = rdrCtx;
 144 
 145         this.reverse = (rdrCtx.stats != null) ?
 146             new PolyStack(rdrCtx,
 147                     rdrCtx.stats.stat_str_polystack_types,
 148                     rdrCtx.stats.stat_str_polystack_curves,
 149                     rdrCtx.stats.hist_str_polystack_curves,
 150                     rdrCtx.stats.stat_array_str_polystack_curves,
 151                     rdrCtx.stats.stat_array_str_polystack_types)
 152             : new PolyStack(rdrCtx);
 153 
 154         this.curve = rdrCtx.curve;
 155     }
 156 
 157     /**
 158      * Inits the <code>Stroker</code>.
 159      *
 160      * @param pc2d an output <code>PathConsumer2D</code>.
 161      * @param lineWidth the desired line width in pixels
 162      * @param capStyle the desired end cap style, one of
 163      * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or
 164      * <code>CAP_SQUARE</code>.
 165      * @param joinStyle the desired line join style, one of
 166      * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or
 167      * <code>JOIN_BEVEL</code>.
 168      * @param miterLimit the desired miter limit
 169      * @param scale scaling factor applied to clip boundaries
 170      * @return this instance
 171      */
 172     Stroker init(final PathConsumer2D pc2d,
 173               final float lineWidth,
 174               final int capStyle,
 175               final int joinStyle,
 176               final float miterLimit,
 177               final float scale)
 178     {
 179         this.out = pc2d;
 180 
 181         this.lineWidth2 = lineWidth / 2.0f;
 182         this.invHalfLineWidth2Sq = 1.0f / (2.0f * lineWidth2 * lineWidth2);
 183         this.capStyle = capStyle;
 184         this.joinStyle = joinStyle;
 185 
 186         final float limit = miterLimit * lineWidth2;
 187         this.miterLimitSq = limit * limit;
 188 
 189         this.prev = CLOSE;
 190 
 191         rdrCtx.stroking = 1;
 192 
 193         if (rdrCtx.doClip) {
 194             // Adjust the clipping rectangle with the stroker margin (miter limit, width)
 195 
 196             // round joins / caps:
 197             final float widthLimit =
 198                 ((joinStyle == JOIN_ROUND) || (capStyle == CAP_ROUND)) ? C * lineWidth // why 0.55 ?
 199                 : lineWidth2;
 200 
 201             float boundsMargin;
 202             if (joinStyle == JOIN_MITER) {
 203                 boundsMargin = Math.max(widthLimit, limit);
 204             } else {
 205                 boundsMargin = widthLimit;
 206             }
 207             float rdrOffX = 0.0f, rdrOffY = 0.0f;
 208 
 209             if (scale != 1.0f) {
 210                 boundsMargin *= scale;
 211                 rdrOffX = scale * Renderer.RDR_OFFSET_X;
 212                 rdrOffY = scale * Renderer.RDR_OFFSET_Y;
 213             }
 214 
 215             // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY
 216             // adjust clip rectangle (ymin, ymax, xmin, xmax):
 217             final float[] _clipRect = rdrCtx.clipRect;
 218             _clipRect[0] -= boundsMargin - rdrOffY;
 219             _clipRect[1] += boundsMargin + rdrOffY;
 220             _clipRect[2] -= boundsMargin - rdrOffX;
 221             _clipRect[3] += boundsMargin + rdrOffX;
 222 //            System.out.println("clip: "+java.util.Arrays.toString(_clipRect));
 223 
 224             this.clipRect = _clipRect;
 225         } else {
 226             this.clipRect = null;
 227         }
 228         return this; // fluent API
 229     }
 230 
 231     /**
 232      * Disposes this stroker:
 233      * clean up before reusing this instance
 234      */
 235     void dispose() {
 236         reverse.dispose();
 237 
 238         opened   = false;
 239         capStart = false;
 240 
 241         if (DO_CLEAN_DIRTY) {
 242             // Force zero-fill dirty arrays:
 243             Arrays.fill(offset0, 0.0f);
 244             Arrays.fill(offset1, 0.0f);
 245             Arrays.fill(offset2, 0.0f);
 246             Arrays.fill(miter, 0.0f);
 247             Arrays.fill(middle, 0.0f);
 248             Arrays.fill(lp, 0.0f);
 249             Arrays.fill(rp, 0.0f);
 250             Arrays.fill(subdivTs, 0.0f);
 251         }
 252     }
 253 
 254     private static void computeOffset(final float lx, final float ly,
 255                                       final float w, final float[] m)
 256     {
 257         float len = lx*lx + ly*ly;
 258         if (len == 0.0f) {
 259             m[0] = 0.0f;
 260             m[1] = 0.0f;


 492         computeMiter((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy,
 493                      (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my,
 494                      miter, 0);
 495 
 496         final float miterX = miter[0];
 497         final float miterY = miter[1];
 498         float lenSq = (miterX-x0)*(miterX-x0) + (miterY-y0)*(miterY-y0);
 499 
 500         // If the lines are parallel, lenSq will be either NaN or +inf
 501         // (actually, I'm not sure if the latter is possible. The important
 502         // thing is that -inf is not possible, because lenSq is a square).
 503         // For both of those values, the comparison below will fail and
 504         // no miter will be drawn, which is correct.
 505         if (lenSq < miterLimitSq) {
 506             emitLineTo(miterX, miterY, rev);
 507         }
 508     }
 509 
 510     @Override
 511     public void moveTo(float x0, float y0) {
 512         moveTo(x0, y0, cOutCode);
 513         // update starting point:
 514         this.sx0 = x0;
 515         this.sy0 = y0;
 516         this.sdx = 1.0f;
 517         this.sdy = 0.0f;
 518         this.opened   = false;
 519         this.capStart = false;
 520 
 521         if (clipRect != null) {
 522             final int outcode = Helpers.outcode(x0, y0, clipRect);
 523             this.cOutCode = outcode;
 524             this.sOutCode = outcode;
 525         }
 526     }
 527 
 528     private void moveTo(float x0, float y0,
 529                         final int outcode)
 530     {
 531         if (prev == MOVE_TO) {
 532             this.cx0 = x0;
 533             this.cy0 = y0;
 534         } else {
 535             if (prev == DRAWING_OP_TO) {
 536                 finish(outcode);
 537             }
 538             this.prev = MOVE_TO;
 539             this.cx0 = x0;
 540             this.cy0 = y0;
 541             this.cdx = 1.0f;
 542             this.cdy = 0.0f;
 543         }





 544     }
 545 
 546     @Override
 547     public void lineTo(float x1, float y1) {
 548         lineTo(x1, y1, false);
 549     }
 550 
 551     private void lineTo(float x1, float y1, boolean force) {
 552         final int outcode0 = this.cOutCode;
 553         if (!force && clipRect != null) {
 554             final int outcode1 = Helpers.outcode(x1, y1, clipRect);
 555             this.cOutCode = outcode1;
 556 
 557             // basic rejection criteria
 558             if ((outcode0 & outcode1) != 0) {
 559                 moveTo(x1, y1, outcode0);
 560                 opened = true;
 561                 return;
 562             }
 563         }
 564 
 565         float dx = x1 - cx0;
 566         float dy = y1 - cy0;
 567         if (dx == 0.0f && dy == 0.0f) {
 568             dx = 1.0f;
 569         }
 570         computeOffset(dx, dy, lineWidth2, offset0);
 571         final float mx = offset0[0];
 572         final float my = offset0[1];
 573 
 574         drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my, outcode0);
 575 
 576         emitLineTo(cx0 + mx, cy0 + my);
 577         emitLineTo( x1 + mx,  y1 + my);
 578 
 579         emitLineToRev(cx0 - mx, cy0 - my);
 580         emitLineToRev( x1 - mx,  y1 - my);
 581 
 582         this.prev = DRAWING_OP_TO;



 583         this.cx0 = x1;
 584         this.cy0 = y1;
 585         this.cdx = dx;
 586         this.cdy = dy;
 587         this.cmx = mx;
 588         this.cmy = my;
 589     }
 590 
 591     @Override
 592     public void closePath() {
 593         // distinguish empty path at all vs opened path ?
 594         if (prev != DRAWING_OP_TO && !opened) {
 595             if (prev == CLOSE) {
 596                 return;
 597             }
 598             emitMoveTo(cx0, cy0 - lineWidth2);
 599 
 600             this.sdx = 1.0f;
 601             this.sdy = 0.0f;
 602             this.cdx = 1.0f;
 603             this.cdy = 0.0f;
 604 
 605             this.smx = 0.0f;
 606             this.smy = -lineWidth2;
 607             this.cmx = 0.0f;
 608             this.cmy = -lineWidth2;
 609 
 610             finish(cOutCode);
 611             return;
 612         }
 613 
 614         if (sOutCode == 0) {
 615             if (cx0 != sx0 || cy0 != sy0) {
 616                 lineTo(sx0, sy0, true);
 617             }
 618 
 619             drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy, cOutCode);
 620 
 621             emitLineTo(sx0 + smx, sy0 + smy);
 622 
 623             if (opened) {
 624                 emitLineTo(sx0 - smx, sy0 - smy);
 625             } else {
 626                 emitMoveTo(sx0 - smx, sy0 - smy);
 627             }
 628         }
 629         // Ignore caps like finish(false)
 630         emitReverse();
 631 
 632         this.prev = CLOSE;
 633 
 634         if (opened) {
 635             // do not emit close
 636             opened = false;
 637         } else {
 638             emitClose();
 639         }
 640     }
 641 
 642     private void emitReverse() {
 643         reverse.popAll(out);
 644     }
 645 
 646     @Override
 647     public void pathDone() {
 648         if (prev == DRAWING_OP_TO) {
 649             finish(cOutCode);
 650         }
 651 
 652         out.pathDone();
 653 
 654         // this shouldn't matter since this object won't be used
 655         // after the call to this method.
 656         this.prev = CLOSE;
 657 
 658         // Dispose this instance:
 659         dispose();
 660     }
 661 
 662     private void finish(final int outcode) {
 663         // Problem: impossible to guess if the path will be closed in advance
 664         //          i.e. if caps must be drawn or not ?
 665         // Solution: use the ClosedPathDetector before Stroker to determine
 666         // if the path is a closed path or not
 667         if (!rdrCtx.closedPath) {
 668             if (outcode == 0) {
 669                 // current point = end's cap:
 670                 if (capStyle == CAP_ROUND) {
 671                     drawRoundCap(cx0, cy0, cmx, cmy);
 672                 } else if (capStyle == CAP_SQUARE) {
 673                     emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy);
 674                     emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy);
 675                 }
 676             }
 677             emitReverse();
 678 
 679             if (!capStart) {
 680                 capStart = true;
 681 
 682                 if (sOutCode == 0) {
 683                     // starting point = initial cap:
 684                     if (capStyle == CAP_ROUND) {
 685                         drawRoundCap(sx0, sy0, -smx, -smy);
 686                     } else if (capStyle == CAP_SQUARE) {
 687                         emitLineTo(sx0 + smy - smx, sy0 - smx - smy);
 688                         emitLineTo(sx0 + smy + smx, sy0 - smx + smy);
 689                     }
 690                 }
 691             }
 692         } else {
 693             emitReverse();
 694         }

 695         emitClose();
 696     }
 697 
 698     private void emitMoveTo(final float x0, final float y0) {
 699         out.moveTo(x0, y0);
 700     }
 701 
 702     private void emitLineTo(final float x1, final float y1) {
 703         out.lineTo(x1, y1);
 704     }
 705 
 706     private void emitLineToRev(final float x1, final float y1) {
 707         reverse.pushLine(x1, y1);
 708     }
 709 
 710     private void emitLineTo(final float x1, final float y1,
 711                             final boolean rev)
 712     {
 713         if (rev) {
 714             emitLineToRev(x1, y1);


 746     private void emitCurveTo(final float x0, final float y0,
 747                              final float x1, final float y1,
 748                              final float x2, final float y2,
 749                              final float x3, final float y3, final boolean rev)
 750     {
 751         if (rev) {
 752             reverse.pushCubic(x0, y0, x1, y1, x2, y2);
 753         } else {
 754             out.curveTo(x1, y1, x2, y2, x3, y3);
 755         }
 756     }
 757 
 758     private void emitClose() {
 759         out.closePath();
 760     }
 761 
 762     private void drawJoin(float pdx, float pdy,
 763                           float x0, float y0,
 764                           float dx, float dy,
 765                           float omx, float omy,
 766                           float mx, float my,
 767                           final int outcode)
 768     {
 769         if (prev != DRAWING_OP_TO) {
 770             emitMoveTo(x0 + mx, y0 + my);
 771             if (!opened) {
 772                 this.sdx = dx;
 773                 this.sdy = dy;
 774                 this.smx = mx;
 775                 this.smy = my;
 776             }
 777         } else {
 778             final boolean cw = isCW(pdx, pdy, dx, dy);
 779             if (outcode == 0) {
 780                 if (joinStyle == JOIN_MITER) {
 781                     drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
 782                 } else if (joinStyle == JOIN_ROUND) {
 783                     drawRoundJoin(x0, y0,
 784                                   omx, omy,
 785                                   mx, my, cw,
 786                                   ROUND_JOIN_THRESHOLD);
 787                 }
 788             }
 789             emitLineTo(x0, y0, !cw);
 790         }
 791         prev = DRAWING_OP_TO;
 792     }
 793 
 794     private static boolean within(final float x1, final float y1,
 795                                   final float x2, final float y2,
 796                                   final float ERR)
 797     {
 798         assert ERR > 0 : "";
 799         // compare taxicab distance. ERR will always be small, so using
 800         // true distance won't give much benefit
 801         return (Helpers.within(x1, x2, ERR) &&  // we want to avoid calling Math.abs
 802                 Helpers.within(y1, y2, ERR)); // this is just as good.
 803     }
 804 
 805     private void getLineOffsets(float x1, float y1,
 806                                 float x2, float y2,
 807                                 float[] left, float[] right) {


1072         int ret = 0;
1073         // we subdivide at values of t such that the remaining rotated
1074         // curves are monotonic in x and y.
1075         ret += c.dxRoots(ts, ret);
1076         ret += c.dyRoots(ts, ret);
1077         // subdivide at inflection points.
1078         if (type == 8) {
1079             // quadratic curves can't have inflection points
1080             ret += c.infPoints(ts, ret);
1081         }
1082 
1083         // now we must subdivide at points where one of the offset curves will have
1084         // a cusp. This happens at ts where the radius of curvature is equal to w.
1085         ret += c.rootsOfROCMinusW(ts, ret, w, 0.0001f);
1086 
1087         ret = Helpers.filterOutNotInAB(ts, 0, ret, 0.0001f, 0.9999f);
1088         Helpers.isort(ts, 0, ret);
1089         return ret;
1090     }
1091 
1092     @Override
1093     public void curveTo(float x1, float y1,
1094                         float x2, float y2,
1095                         float x3, float y3)
1096     {
1097         final int outcode0 = this.cOutCode;
1098         if (clipRect != null) {
1099             final int outcode3 = Helpers.outcode(x3, y3, clipRect);
1100             this.cOutCode = outcode3;
1101 
1102             if (outcode3 != 0) {
1103                 final int outcode1 = Helpers.outcode(x1, y1, clipRect);
1104                 final int outcode2 = Helpers.outcode(x2, y2, clipRect);
1105 
1106                 // basic rejection criteria
1107                 if ((outcode0 & outcode1 & outcode2 & outcode3) != 0) {
1108                     moveTo(x3, y3, outcode0);
1109                     opened = true;
1110                     return;
1111                 }
1112             }
1113         }
1114 
1115         final float[] mid = middle;
1116 
1117         mid[0] = cx0; mid[1] = cy0;
1118         mid[2] = x1;  mid[3] = y1;
1119         mid[4] = x2;  mid[5] = y2;
1120         mid[6] = x3;  mid[7] = y3;
1121 
1122         // need these so we can update the state at the end of this method
1123         final float xf = x3, yf = y3;
1124         float dxs = mid[2] - mid[0];
1125         float dys = mid[3] - mid[1];
1126         float dxf = mid[6] - mid[4];
1127         float dyf = mid[7] - mid[5];
1128 
1129         boolean p1eqp2 = (dxs == 0.0f && dys == 0.0f);
1130         boolean p3eqp4 = (dxf == 0.0f && dyf == 0.0f);
1131         if (p1eqp2) {
1132             dxs = mid[4] - mid[0];
1133             dys = mid[5] - mid[1];
1134             if (dxs == 0.0f && dys == 0.0f) {
1135                 dxs = mid[6] - mid[0];
1136                 dys = mid[7] - mid[1];
1137             }
1138         }
1139         if (p3eqp4) {
1140             dxf = mid[6] - mid[2];
1141             dyf = mid[7] - mid[3];
1142             if (dxf == 0.0f && dyf == 0.0f) {
1143                 dxf = mid[6] - mid[0];
1144                 dyf = mid[7] - mid[1];
1145             }
1146         }
1147         if (dxs == 0.0f && dys == 0.0f) {
1148             // this happens if the "curve" is just a point
1149             // fix outcode0 for lineTo() call:
1150             if (clipRect != null) {
1151                 this.cOutCode = outcode0;
1152             }
1153             lineTo(mid[0], mid[1]);
1154             return;
1155         }
1156 
1157         // if these vectors are too small, normalize them, to avoid future
1158         // precision problems.
1159         if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) {
1160             float len = (float) Math.sqrt(dxs*dxs + dys*dys);
1161             dxs /= len;
1162             dys /= len;
1163         }
1164         if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) {
1165             float len = (float) Math.sqrt(dxf*dxf + dyf*dyf);
1166             dxf /= len;
1167             dyf /= len;
1168         }
1169 
1170         computeOffset(dxs, dys, lineWidth2, offset0);
1171         drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
1172 
1173         final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2);
1174 
1175         float prevT = 0.0f;
1176         for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
1177             final float t = subdivTs[i];
1178             Helpers.subdivideCubicAt((t - prevT) / (1.0f - prevT),
1179                                      mid, off, mid, off, mid, off + 6);
1180             prevT = t;
1181         }
1182 
1183         final float[] l = lp;
1184         final float[] r = rp;
1185 
1186         int kind = 0;
1187         for (int i = 0, off = 0; i <= nSplits; i++, off += 6) {
1188             kind = computeOffsetCubic(mid, off, l, r);
1189 
1190             emitLineTo(l[0], l[1]);
1191 
1192             switch(kind) {
1193             case 8:
1194                 emitCurveTo(l[2], l[3], l[4], l[5], l[6], l[7]);
1195                 emitCurveToRev(r[0], r[1], r[2], r[3], r[4], r[5]);
1196                 break;
1197             case 4:
1198                 emitLineTo(l[2], l[3]);
1199                 emitLineToRev(r[0], r[1]);
1200                 break;
1201             default:
1202             }
1203             emitLineToRev(r[kind - 2], r[kind - 1]);
1204         }
1205 
1206         this.prev = DRAWING_OP_TO;



1207         this.cx0 = xf;
1208         this.cy0 = yf;
1209         this.cdx = dxf;
1210         this.cdy = dyf;
1211         this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
1212         this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f;
1213     }
1214 
1215     @Override
1216     public void quadTo(float x1, float y1,
1217                        float x2, float y2)
1218     {
1219         final int outcode0 = this.cOutCode;
1220         if (clipRect != null) {
1221             final int outcode2 = Helpers.outcode(x2, y2, clipRect);
1222             this.cOutCode = outcode2;
1223 
1224             if (outcode2 != 0) {
1225                 final int outcode1 = Helpers.outcode(x1, y1, clipRect);
1226 
1227                 // basic rejection criteria
1228                 if ((outcode0 & outcode1 & outcode2) != 0) {
1229                     moveTo(x2, y2, outcode0);
1230                     opened = true;
1231                     return;
1232                 }
1233             }
1234         }
1235 
1236         final float[] mid = middle;
1237 
1238         mid[0] = cx0; mid[1] = cy0;
1239         mid[2] = x1;  mid[3] = y1;
1240         mid[4] = x2;  mid[5] = y2;
1241 
1242         // need these so we can update the state at the end of this method
1243         final float xf = x2, yf = y2;
1244         float dxs = mid[2] - mid[0];
1245         float dys = mid[3] - mid[1];
1246         float dxf = mid[4] - mid[2];
1247         float dyf = mid[5] - mid[3];
1248         if ((dxs == 0.0f && dys == 0.0f) || (dxf == 0.0f && dyf == 0.0f)) {
1249             dxs = dxf = mid[4] - mid[0];
1250             dys = dyf = mid[5] - mid[1];
1251         }
1252         if (dxs == 0.0f && dys == 0.0f) {
1253             // this happens if the "curve" is just a point
1254             // fix outcode0 for lineTo() call:
1255             if (clipRect != null) {
1256                 this.cOutCode = outcode0;
1257             }
1258             lineTo(mid[0], mid[1]);
1259             return;
1260         }
1261         // if these vectors are too small, normalize them, to avoid future
1262         // precision problems.
1263         if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) {
1264             float len = (float) Math.sqrt(dxs*dxs + dys*dys);
1265             dxs /= len;
1266             dys /= len;
1267         }
1268         if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) {
1269             float len = (float) Math.sqrt(dxf*dxf + dyf*dyf);
1270             dxf /= len;
1271             dyf /= len;
1272         }
1273 
1274         computeOffset(dxs, dys, lineWidth2, offset0);
1275         drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
1276 
1277         int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2);
1278 
1279         float prevt = 0.0f;
1280         for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
1281             final float t = subdivTs[i];
1282             Helpers.subdivideQuadAt((t - prevt) / (1.0f - prevt),
1283                                     mid, off, mid, off, mid, off + 4);
1284             prevt = t;
1285         }
1286 
1287         final float[] l = lp;
1288         final float[] r = rp;
1289 
1290         int kind = 0;
1291         for (int i = 0, off = 0; i <= nSplits; i++, off += 4) {
1292             kind = computeOffsetQuad(mid, off, l, r);
1293 
1294             emitLineTo(l[0], l[1]);
1295 
1296             switch(kind) {
1297             case 6:
1298                 emitQuadTo(l[2], l[3], l[4], l[5]);
1299                 emitQuadToRev(r[0], r[1], r[2], r[3]);
1300                 break;
1301             case 4:
1302                 emitLineTo(l[2], l[3]);
1303                 emitLineToRev(r[0], r[1]);
1304                 break;
1305             default:
1306             }
1307             emitLineToRev(r[kind - 2], r[kind - 1]);
1308         }
1309 
1310         this.prev = DRAWING_OP_TO;



1311         this.cx0 = xf;
1312         this.cy0 = yf;
1313         this.cdx = dxf;
1314         this.cdy = dyf;
1315         this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
1316         this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f;
1317     }
1318 
1319     @Override public long getNativeConsumer() {
1320         throw new InternalError("Stroker doesn't use a native consumer");






































































































































































































1321     }
1322 }
< prev index next >