< 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 


  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     /**
  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 


  57     /**
  58      * Constant value for end cap style.
  59      */
  60     public static final int CAP_BUTT = 0;
  61 
  62     /**
  63      * Constant value for end cap style.
  64      */
  65     public static final int CAP_ROUND = 1;
  66 
  67     /**
  68      * Constant value for end cap style.
  69      */
  70     public static final int CAP_SQUARE = 2;
  71 
  72     // pisces used to use fixed point arithmetic with 16 decimal digits. I
  73     // didn't want to change the values of the constant below when I converted
  74     // it to floating point, so that's why the divisions by 2^16 are there.
  75     private static final float ROUND_JOIN_THRESHOLD = 1000.0f/65536.0f;
  76 
  77     // kappa = (4/3) * (SQRT(2) - 1)
  78     private static final float C = (float)(4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d);
  79 
  80     // SQRT(2)
  81     private static final float SQRT_2 = (float)Math.sqrt(2.0d);
  82 
  83     private static final int MAX_N_CURVES = 11;
  84 
  85     private PathConsumer2D out;
  86 
  87     private int capStyle;
  88     private int joinStyle;
  89 
  90     private float lineWidth2;
  91     private float invHalfLineWidth2Sq;
  92 
  93     private final float[] offset0 = new float[2];
  94     private final float[] offset1 = new float[2];
  95     private final float[] offset2 = new float[2];
  96     private final float[] miter = new float[2];
  97     private float miterLimitSq;
  98 
  99     private int prev;
 100 
 101     // The starting point of the path, and the slope there.


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


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





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



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

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


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


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



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



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






































































































































































































1320     }
1321 }
< prev index next >