< prev index next >

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

Print this page




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

  29 
  30 // TODO: some of the arithmetic here is too verbose and prone to hard to
  31 // debug typos. We should consider making a small Point/Vector class that
  32 // has methods like plus(Point), minus(Point), dot(Point), cross(Point)and such
  33 final class DStroker implements DPathConsumer2D, MarlinConst {
  34 
  35     private static final int MOVE_TO = 0;
  36     private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad
  37     private static final int CLOSE = 2;
  38 
  39     /**
  40      * Constant value for join style.
  41      */
  42     public static final int JOIN_MITER = 0;
  43 
  44     /**
  45      * Constant value for join style.
  46      */
  47     public static final int JOIN_ROUND = 1;
  48 
  49     /**
  50      * Constant value for join style.
  51      */
  52     public static final int JOIN_BEVEL = 2;
  53 
  54     /**
  55      * Constant value for end cap style.
  56      */
  57     public static final int CAP_BUTT = 0;
  58 
  59     /**
  60      * Constant value for end cap style.
  61      */
  62     public static final int CAP_ROUND = 1;
  63 
  64     /**
  65      * Constant value for end cap style.
  66      */
  67     public static final int CAP_SQUARE = 2;
  68 
  69     // pisces used to use fixed point arithmetic with 16 decimal digits. I
  70     // didn't want to change the values of the constant below when I converted
  71     // it to floating point, so that's why the divisions by 2^16 are there.
  72     private static final double ROUND_JOIN_THRESHOLD = 1000.0d/65536.0d;
  73 
  74     private static final double C = 0.5522847498307933d;




  75 
  76     private static final int MAX_N_CURVES = 11;
  77 
  78     private DPathConsumer2D out;
  79 
  80     private int capStyle;
  81     private int joinStyle;
  82 
  83     private double lineWidth2;
  84     private double invHalfLineWidth2Sq;
  85 
  86     private final double[] offset0 = new double[2];
  87     private final double[] offset1 = new double[2];
  88     private final double[] offset2 = new double[2];
  89     private final double[] miter = new double[2];
  90     private double miterLimitSq;
  91 
  92     private int prev;
  93 
  94     // The starting point of the path, and the slope there.


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














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








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

 144      * @return this instance
 145      */
 146     DStroker init(DPathConsumer2D pc2d,
 147               double lineWidth,
 148               int capStyle,
 149               int joinStyle,
 150               double miterLimit)

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
































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



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


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





























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



















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



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

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







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


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






 509         emitReverse();
 510 
 511         this.prev = CLOSE;
 512         emitClose();






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









 542 
 543         emitReverse();

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







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


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

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


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


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


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



















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




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



1042     }
1043 
1044     @Override public void quadTo(double x1, double y1, double x2, double y2) {




















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




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



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


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






























  40     // pisces used to use fixed point arithmetic with 16 decimal digits. I
  41     // didn't want to change the values of the constant below when I converted
  42     // it to floating point, so that's why the divisions by 2^16 are there.
  43     private static final double ROUND_JOIN_THRESHOLD = 1000.0d/65536.0d;
  44 
  45     // kappa = (4/3) * (SQRT(2) - 1)
  46     private static final double C = (4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d);
  47 
  48     // SQRT(2)
  49     private static final double SQRT_2 = Math.sqrt(2.0d);
  50 
  51     private static final int MAX_N_CURVES = 11;
  52 
  53     private DPathConsumer2D out;
  54 
  55     private int capStyle;
  56     private int joinStyle;
  57 
  58     private double lineWidth2;
  59     private double invHalfLineWidth2Sq;
  60 
  61     private final double[] offset0 = new double[2];
  62     private final double[] offset1 = new double[2];
  63     private final double[] offset2 = new double[2];
  64     private final double[] miter = new double[2];
  65     private double miterLimitSq;
  66 
  67     private int prev;
  68 
  69     // The starting point of the path, and the slope there.


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


 460 
 461         computeMiter((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy,
 462                      (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my,
 463                      miter, 0);
 464 
 465         final double miterX = miter[0];
 466         final double miterY = miter[1];
 467         double lenSq = (miterX-x0)*(miterX-x0) + (miterY-y0)*(miterY-y0);
 468 
 469         // If the lines are parallel, lenSq will be either NaN or +inf
 470         // (actually, I'm not sure if the latter is possible. The important
 471         // thing is that -inf is not possible, because lenSq is a square).
 472         // For both of those values, the comparison below will fail and
 473         // no miter will be drawn, which is correct.
 474         if (lenSq < miterLimitSq) {
 475             emitLineTo(miterX, miterY, rev);
 476         }
 477     }
 478 
 479     @Override
 480     public void moveTo(final double x0, final double y0) {
 481         moveTo(x0, y0, cOutCode);
 482         // update starting point:
 483         this.sx0 = x0;
 484         this.sy0 = y0;
 485         this.sdx = 1.0d;
 486         this.sdy = 0.0d;
 487         this.opened   = false;
 488         this.capStart = false;
 489 
 490         if (clipRect != null) {
 491             final int outcode = DHelpers.outcode(x0, y0, clipRect);
 492             this.cOutCode = outcode;
 493             this.sOutCode = outcode;
 494         }
 495     }
 496 
 497     private void moveTo(final double x0, final double y0,
 498                         final int outcode)
 499     {
 500         if (prev == MOVE_TO) {
 501             this.cx0 = x0;
 502             this.cy0 = y0;
 503         } else {
 504             if (prev == DRAWING_OP_TO) {
 505                 finish(outcode);
 506             }
 507             this.prev = MOVE_TO;
 508             this.cx0 = x0;
 509             this.cy0 = y0;
 510             this.cdx = 1.0d;
 511             this.cdy = 0.0d;
 512         }





 513     }
 514 
 515     @Override
 516     public void lineTo(final double x1, final double y1) {
 517         lineTo(x1, y1, false);
 518     }
 519 
 520     private void lineTo(final double x1, final double y1,
 521                         final boolean force)
 522     {
 523         final int outcode0 = this.cOutCode;
 524         if (!force && clipRect != null) {
 525             final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
 526             this.cOutCode = outcode1;
 527 
 528             // basic rejection criteria
 529             if ((outcode0 & outcode1) != 0) {
 530                 moveTo(x1, y1, outcode0);
 531                 opened = true;
 532                 return;
 533             }
 534         }
 535 
 536         double dx = x1 - cx0;
 537         double dy = y1 - cy0;
 538         if (dx == 0.0d && dy == 0.0d) {
 539             dx = 1.0d;
 540         }
 541         computeOffset(dx, dy, lineWidth2, offset0);
 542         final double mx = offset0[0];
 543         final double my = offset0[1];
 544 
 545         drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my, outcode0);
 546 
 547         emitLineTo(cx0 + mx, cy0 + my);
 548         emitLineTo( x1 + mx,  y1 + my);
 549 
 550         emitLineToRev(cx0 - mx, cy0 - my);
 551         emitLineToRev( x1 - mx,  y1 - my);
 552 
 553         this.prev = DRAWING_OP_TO;



 554         this.cx0 = x1;
 555         this.cy0 = y1;
 556         this.cdx = dx;
 557         this.cdy = dy;
 558         this.cmx = mx;
 559         this.cmy = my;
 560     }
 561 
 562     @Override
 563     public void closePath() {
 564         // distinguish empty path at all vs opened path ?
 565         if (prev != DRAWING_OP_TO && !opened) {
 566             if (prev == CLOSE) {
 567                 return;
 568             }
 569             emitMoveTo(cx0, cy0 - lineWidth2);
 570 
 571             this.sdx = 1.0d;
 572             this.sdy = 0.0d;
 573             this.cdx = 1.0d;
 574             this.cdy = 0.0d;
 575 
 576             this.smx = 0.0d;
 577             this.smy = -lineWidth2;
 578             this.cmx = 0.0d;
 579             this.cmy = -lineWidth2;
 580 
 581             finish(cOutCode);
 582             return;
 583         }
 584 
 585         // basic acceptance criteria
 586         if ((sOutCode & cOutCode) == 0) {
 587             if (cx0 != sx0 || cy0 != sy0) {
 588                 lineTo(sx0, sy0, true);
 589             }
 590 
 591             drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy, sOutCode);
 592 
 593             emitLineTo(sx0 + smx, sy0 + smy);
 594 
 595             if (opened) {
 596                 emitLineTo(sx0 - smx, sy0 - smy);
 597             } else {
 598                 emitMoveTo(sx0 - smx, sy0 - smy);
 599             }
 600         }
 601         // Ignore caps like finish(false)
 602         emitReverse();
 603 
 604         this.prev = CLOSE;
 605 
 606         if (opened) {
 607             // do not emit close
 608             opened = false;
 609         } else {
 610             emitClose();
 611         }
 612     }
 613 
 614     private void emitReverse() {
 615         reverse.popAll(out);
 616     }
 617 
 618     @Override
 619     public void pathDone() {
 620         if (prev == DRAWING_OP_TO) {
 621             finish(cOutCode);
 622         }
 623 
 624         out.pathDone();
 625 
 626         // this shouldn't matter since this object won't be used
 627         // after the call to this method.
 628         this.prev = CLOSE;
 629 
 630         // Dispose this instance:
 631         dispose();
 632     }
 633 
 634     private void finish(final int outcode) {
 635         // Problem: impossible to guess if the path will be closed in advance
 636         //          i.e. if caps must be drawn or not ?
 637         // Solution: use the ClosedPathDetector before Stroker to determine
 638         // if the path is a closed path or not
 639         if (!rdrCtx.closedPath) {
 640             if (outcode == 0) {
 641                 // current point = end's cap:
 642                 if (capStyle == CAP_ROUND) {
 643                     drawRoundCap(cx0, cy0, cmx, cmy);
 644                 } else if (capStyle == CAP_SQUARE) {
 645                     emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy);
 646                     emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy);
 647                 }
 648             }
 649             emitReverse();
 650 
 651             if (!capStart) {
 652                 capStart = true;
 653 
 654                 if (sOutCode == 0) {
 655                     // starting point = initial cap:
 656                     if (capStyle == CAP_ROUND) {
 657                         drawRoundCap(sx0, sy0, -smx, -smy);
 658                     } else if (capStyle == CAP_SQUARE) {
 659                         emitLineTo(sx0 + smy - smx, sy0 - smx - smy);
 660                         emitLineTo(sx0 + smy + smx, sy0 - smx + smy);
 661                     }
 662                 }
 663             }
 664         } else {
 665             emitReverse();
 666         }

 667         emitClose();
 668     }
 669 
 670     private void emitMoveTo(final double x0, final double y0) {
 671         out.moveTo(x0, y0);
 672     }
 673 
 674     private void emitLineTo(final double x1, final double y1) {
 675         out.lineTo(x1, y1);
 676     }
 677 
 678     private void emitLineToRev(final double x1, final double y1) {
 679         reverse.pushLine(x1, y1);
 680     }
 681 
 682     private void emitLineTo(final double x1, final double y1,
 683                             final boolean rev)
 684     {
 685         if (rev) {
 686             emitLineToRev(x1, y1);


 718     private void emitCurveTo(final double x0, final double y0,
 719                              final double x1, final double y1,
 720                              final double x2, final double y2,
 721                              final double x3, final double y3, final boolean rev)
 722     {
 723         if (rev) {
 724             reverse.pushCubic(x0, y0, x1, y1, x2, y2);
 725         } else {
 726             out.curveTo(x1, y1, x2, y2, x3, y3);
 727         }
 728     }
 729 
 730     private void emitClose() {
 731         out.closePath();
 732     }
 733 
 734     private void drawJoin(double pdx, double pdy,
 735                           double x0, double y0,
 736                           double dx, double dy,
 737                           double omx, double omy,
 738                           double mx, double my,
 739                           final int outcode)
 740     {
 741         if (prev != DRAWING_OP_TO) {
 742             emitMoveTo(x0 + mx, y0 + my);
 743             if (!opened) {
 744                 this.sdx = dx;
 745                 this.sdy = dy;
 746                 this.smx = mx;
 747                 this.smy = my;
 748             }
 749         } else {
 750             final boolean cw = isCW(pdx, pdy, dx, dy);
 751             if (outcode == 0) {
 752                 if (joinStyle == JOIN_MITER) {
 753                     drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
 754                 } else if (joinStyle == JOIN_ROUND) {
 755                     drawRoundJoin(x0, y0,
 756                                   omx, omy,
 757                                   mx, my, cw,
 758                                   ROUND_JOIN_THRESHOLD);
 759                 }
 760             }
 761             emitLineTo(x0, y0, !cw);
 762         }
 763         prev = DRAWING_OP_TO;
 764     }
 765 
 766     private static boolean within(final double x1, final double y1,
 767                                   final double x2, final double y2,
 768                                   final double ERR)
 769     {
 770         assert ERR > 0 : "";
 771         // compare taxicab distance. ERR will always be small, so using
 772         // true distance won't give much benefit
 773         return (DHelpers.within(x1, x2, ERR) &&  // we want to avoid calling Math.abs
 774                 DHelpers.within(y1, y2, ERR)); // this is just as good.
 775     }
 776 
 777     private void getLineOffsets(double x1, double y1,
 778                                 double x2, double y2,
 779                                 double[] left, double[] right) {


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



1179         this.cx0 = xf;
1180         this.cy0 = yf;
1181         this.cdx = dxf;
1182         this.cdy = dyf;
1183         this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
1184         this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
1185     }
1186 
1187     @Override
1188     public void quadTo(final double x1, final double y1,
1189                        final double x2, final double y2)
1190     {
1191         final int outcode0 = this.cOutCode;
1192         if (clipRect != null) {
1193             final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
1194             this.cOutCode = outcode2;
1195 
1196             if ((outcode0 & outcode2) != 0) {
1197                 final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
1198 
1199                 // basic rejection criteria
1200                 if ((outcode0 & outcode1 & outcode2) != 0) {
1201                     moveTo(x2, y2, outcode0);
1202                     opened = true;
1203                     return;
1204                 }
1205             }
1206         }
1207 
1208         final double[] mid = middle;
1209 
1210         mid[0] = cx0; mid[1] = cy0;
1211         mid[2] = x1;  mid[3] = y1;
1212         mid[4] = x2;  mid[5] = y2;
1213 
1214         // need these so we can update the state at the end of this method
1215         final double xf = x2, yf = y2;
1216         double dxs = mid[2] - mid[0];
1217         double dys = mid[3] - mid[1];
1218         double dxf = mid[4] - mid[2];
1219         double dyf = mid[5] - mid[3];
1220         if ((dxs == 0.0d && dys == 0.0d) || (dxf == 0.0d && dyf == 0.0d)) {
1221             dxs = dxf = mid[4] - mid[0];
1222             dys = dyf = mid[5] - mid[1];
1223         }
1224         if (dxs == 0.0d && dys == 0.0d) {
1225             // this happens if the "curve" is just a point
1226             // fix outcode0 for lineTo() call:
1227             if (clipRect != null) {
1228                 this.cOutCode = outcode0;
1229             }
1230             lineTo(mid[0], mid[1]);
1231             return;
1232         }
1233         // if these vectors are too small, normalize them, to avoid future
1234         // precision problems.
1235         if (Math.abs(dxs) < 0.1d && Math.abs(dys) < 0.1d) {
1236             double len = Math.sqrt(dxs*dxs + dys*dys);
1237             dxs /= len;
1238             dys /= len;
1239         }
1240         if (Math.abs(dxf) < 0.1d && Math.abs(dyf) < 0.1d) {
1241             double len = Math.sqrt(dxf*dxf + dyf*dyf);
1242             dxf /= len;
1243             dyf /= len;
1244         }
1245 
1246         computeOffset(dxs, dys, lineWidth2, offset0);
1247         drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
1248 
1249         int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2);
1250 
1251         double prevt = 0.0d;
1252         for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
1253             final double t = subdivTs[i];
1254             DHelpers.subdivideQuadAt((t - prevt) / (1.0d - prevt),
1255                                     mid, off, mid, off, mid, off + 4);
1256             prevt = t;
1257         }
1258 
1259         final double[] l = lp;
1260         final double[] r = rp;
1261 
1262         int kind = 0;
1263         for (int i = 0, off = 0; i <= nSplits; i++, off += 4) {
1264             kind = computeOffsetQuad(mid, off, l, r);
1265 
1266             emitLineTo(l[0], l[1]);
1267 
1268             switch(kind) {
1269             case 6:
1270                 emitQuadTo(l[2], l[3], l[4], l[5]);
1271                 emitQuadToRev(r[0], r[1], r[2], r[3]);
1272                 break;
1273             case 4:
1274                 emitLineTo(l[2], l[3]);
1275                 emitLineToRev(r[0], r[1]);
1276                 break;
1277             default:
1278             }
1279             emitLineToRev(r[kind - 2], r[kind - 1]);
1280         }
1281 
1282         this.prev = DRAWING_OP_TO;



1283         this.cx0 = xf;
1284         this.cy0 = yf;
1285         this.cdx = dxf;
1286         this.cdy = dyf;
1287         this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
1288         this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
1289     }
1290 
1291     @Override public long getNativeConsumer() {
1292         throw new InternalError("Stroker doesn't use a native consumer");






































































































































































































1293     }
1294 }
< prev index next >