< 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 
  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 }
< prev index next >