< 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 


  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     /**
  41      * Constant value for join style.
  42      */
  43     public static final int JOIN_MITER = 0;
  44 
  45     /**
  46      * Constant value for join style.
  47      */
  48     public static final int JOIN_ROUND = 1;
  49 


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


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


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





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



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

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


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


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



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



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






































































































































































































1318     }
1319 }
< prev index next >