src/java.desktop/share/classes/sun/java2d/marlin/Stroker.java
changeset 48284 fd7fbc929001
parent 47216 71c04702a3d5
child 49496 1ea202af7a97
equal deleted inserted replaced
48283:da1b57b17101 48284:fd7fbc929001
    26 package sun.java2d.marlin;
    26 package sun.java2d.marlin;
    27 
    27 
    28 import java.util.Arrays;
    28 import java.util.Arrays;
    29 
    29 
    30 import sun.awt.geom.PathConsumer2D;
    30 import sun.awt.geom.PathConsumer2D;
       
    31 import sun.java2d.marlin.Helpers.PolyStack;
    31 
    32 
    32 // TODO: some of the arithmetic here is too verbose and prone to hard to
    33 // TODO: some of the arithmetic here is too verbose and prone to hard to
    33 // debug typos. We should consider making a small Point/Vector class that
    34 // debug typos. We should consider making a small Point/Vector class that
    34 // has methods like plus(Point), minus(Point), dot(Point), cross(Point)and such
    35 // has methods like plus(Point), minus(Point), dot(Point), cross(Point)and such
    35 final class Stroker implements PathConsumer2D, MarlinConst {
    36 final class Stroker implements PathConsumer2D, MarlinConst {
    36 
    37 
    37     private static final int MOVE_TO = 0;
    38     private static final int MOVE_TO = 0;
    38     private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad
    39     private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad
    39     private static final int CLOSE = 2;
    40     private static final int CLOSE = 2;
    40 
    41 
    41     /**
       
    42      * Constant value for join style.
       
    43      */
       
    44     public static final int JOIN_MITER = 0;
       
    45 
       
    46     /**
       
    47      * Constant value for join style.
       
    48      */
       
    49     public static final int JOIN_ROUND = 1;
       
    50 
       
    51     /**
       
    52      * Constant value for join style.
       
    53      */
       
    54     public static final int JOIN_BEVEL = 2;
       
    55 
       
    56     /**
       
    57      * Constant value for end cap style.
       
    58      */
       
    59     public static final int CAP_BUTT = 0;
       
    60 
       
    61     /**
       
    62      * Constant value for end cap style.
       
    63      */
       
    64     public static final int CAP_ROUND = 1;
       
    65 
       
    66     /**
       
    67      * Constant value for end cap style.
       
    68      */
       
    69     public static final int CAP_SQUARE = 2;
       
    70 
       
    71     // pisces used to use fixed point arithmetic with 16 decimal digits. I
    42     // pisces used to use fixed point arithmetic with 16 decimal digits. I
    72     // didn't want to change the values of the constant below when I converted
    43     // didn't want to change the values of the constant below when I converted
    73     // it to floating point, so that's why the divisions by 2^16 are there.
    44     // it to floating point, so that's why the divisions by 2^16 are there.
    74     private static final float ROUND_JOIN_THRESHOLD = 1000.0f/65536.0f;
    45     private static final float ROUND_JOIN_THRESHOLD = 1000.0f/65536.0f;
    75 
    46 
    76     private static final float C = 0.5522847498307933f;
    47     // kappa = (4/3) * (SQRT(2) - 1)
       
    48     private static final float C = (float)(4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d);
       
    49 
       
    50     // SQRT(2)
       
    51     private static final float SQRT_2 = (float)Math.sqrt(2.0d);
    77 
    52 
    78     private static final int MAX_N_CURVES = 11;
    53     private static final int MAX_N_CURVES = 11;
    79 
    54 
    80     private PathConsumer2D out;
    55     private PathConsumer2D out;
    81 
    56 
   118     final RendererContext rdrCtx;
    93     final RendererContext rdrCtx;
   119 
    94 
   120     // dirty curve
    95     // dirty curve
   121     final Curve curve;
    96     final Curve curve;
   122 
    97 
       
    98     // Bounds of the drawing region, at pixel precision.
       
    99     private float[] clipRect;
       
   100 
       
   101     // the outcode of the current point
       
   102     private int cOutCode = 0;
       
   103 
       
   104     // the outcode of the starting point
       
   105     private int sOutCode = 0;
       
   106 
       
   107     // flag indicating if the path is opened (clipped)
       
   108     private boolean opened = false;
       
   109     // flag indicating if the starting point's cap is done
       
   110     private boolean capStart = false;
       
   111 
   123     /**
   112     /**
   124      * Constructs a <code>Stroker</code>.
   113      * Constructs a <code>Stroker</code>.
   125      * @param rdrCtx per-thread renderer context
   114      * @param rdrCtx per-thread renderer context
   126      */
   115      */
   127     Stroker(final RendererContext rdrCtx) {
   116     Stroker(final RendererContext rdrCtx) {
   128         this.rdrCtx = rdrCtx;
   117         this.rdrCtx = rdrCtx;
   129 
   118 
   130         this.reverse = new PolyStack(rdrCtx);
   119         this.reverse = (rdrCtx.stats != null) ?
       
   120             new PolyStack(rdrCtx,
       
   121                     rdrCtx.stats.stat_str_polystack_types,
       
   122                     rdrCtx.stats.stat_str_polystack_curves,
       
   123                     rdrCtx.stats.hist_str_polystack_curves,
       
   124                     rdrCtx.stats.stat_array_str_polystack_curves,
       
   125                     rdrCtx.stats.stat_array_str_polystack_types)
       
   126             : new PolyStack(rdrCtx);
       
   127 
   131         this.curve = rdrCtx.curve;
   128         this.curve = rdrCtx.curve;
   132     }
   129     }
   133 
   130 
   134     /**
   131     /**
   135      * Inits the <code>Stroker</code>.
   132      * Inits the <code>Stroker</code>.
   141      * <code>CAP_SQUARE</code>.
   138      * <code>CAP_SQUARE</code>.
   142      * @param joinStyle the desired line join style, one of
   139      * @param joinStyle the desired line join style, one of
   143      * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or
   140      * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or
   144      * <code>JOIN_BEVEL</code>.
   141      * <code>JOIN_BEVEL</code>.
   145      * @param miterLimit the desired miter limit
   142      * @param miterLimit the desired miter limit
       
   143      * @param scale scaling factor applied to clip boundaries
   146      * @return this instance
   144      * @return this instance
   147      */
   145      */
   148     Stroker init(PathConsumer2D pc2d,
   146     Stroker init(final PathConsumer2D pc2d,
   149               float lineWidth,
   147                  final float lineWidth,
   150               int capStyle,
   148                  final int capStyle,
   151               int joinStyle,
   149                  final int joinStyle,
   152               float miterLimit)
   150                  final float miterLimit,
       
   151                  final float scale)
   153     {
   152     {
   154         this.out = pc2d;
   153         this.out = pc2d;
   155 
   154 
   156         this.lineWidth2 = lineWidth / 2.0f;
   155         this.lineWidth2 = lineWidth / 2.0f;
   157         this.invHalfLineWidth2Sq = 1.0f / (2.0f * lineWidth2 * lineWidth2);
   156         this.invHalfLineWidth2Sq = 1.0f / (2.0f * lineWidth2 * lineWidth2);
   158         this.capStyle = capStyle;
   157         this.capStyle = capStyle;
   159         this.joinStyle = joinStyle;
   158         this.joinStyle = joinStyle;
   160 
   159 
   161         float limit = miterLimit * lineWidth2;
   160         final float limit = miterLimit * lineWidth2;
   162         this.miterLimitSq = limit * limit;
   161         this.miterLimitSq = limit * limit;
   163 
   162 
   164         this.prev = CLOSE;
   163         this.prev = CLOSE;
   165 
   164 
   166         rdrCtx.stroking = 1;
   165         rdrCtx.stroking = 1;
   167 
   166 
       
   167         if (rdrCtx.doClip) {
       
   168             // Adjust the clipping rectangle with the stroker margin (miter limit, width)
       
   169             float rdrOffX = 0.0f, rdrOffY = 0.0f;
       
   170             float margin = lineWidth2;
       
   171 
       
   172             if (capStyle == CAP_SQUARE) {
       
   173                 margin *= SQRT_2;
       
   174             }
       
   175             if ((joinStyle == JOIN_MITER) && (margin < limit)) {
       
   176                 margin = limit;
       
   177             }
       
   178             if (scale != 1.0f) {
       
   179                 margin *= scale;
       
   180                 rdrOffX = scale * Renderer.RDR_OFFSET_X;
       
   181                 rdrOffY = scale * Renderer.RDR_OFFSET_Y;
       
   182             }
       
   183             // add a small rounding error:
       
   184             margin += 1e-3f;
       
   185 
       
   186             // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY
       
   187             // adjust clip rectangle (ymin, ymax, xmin, xmax):
       
   188             final float[] _clipRect = rdrCtx.clipRect;
       
   189             _clipRect[0] -= margin - rdrOffY;
       
   190             _clipRect[1] += margin + rdrOffY;
       
   191             _clipRect[2] -= margin - rdrOffX;
       
   192             _clipRect[3] += margin + rdrOffX;
       
   193             this.clipRect = _clipRect;
       
   194         } else {
       
   195             this.clipRect = null;
       
   196             this.cOutCode = 0;
       
   197             this.sOutCode = 0;
       
   198         }
   168         return this; // fluent API
   199         return this; // fluent API
   169     }
   200     }
   170 
   201 
   171     /**
   202     /**
   172      * Disposes this stroker:
   203      * Disposes this stroker:
   173      * clean up before reusing this instance
   204      * clean up before reusing this instance
   174      */
   205      */
   175     void dispose() {
   206     void dispose() {
   176         reverse.dispose();
   207         reverse.dispose();
       
   208 
       
   209         opened   = false;
       
   210         capStart = false;
   177 
   211 
   178         if (DO_CLEAN_DIRTY) {
   212         if (DO_CLEAN_DIRTY) {
   179             // Force zero-fill dirty arrays:
   213             // Force zero-fill dirty arrays:
   180             Arrays.fill(offset0, 0.0f);
   214             Arrays.fill(offset0, 0.0f);
   181             Arrays.fill(offset1, 0.0f);
   215             Arrays.fill(offset1, 0.0f);
   443             emitLineTo(miterX, miterY, rev);
   477             emitLineTo(miterX, miterY, rev);
   444         }
   478         }
   445     }
   479     }
   446 
   480 
   447     @Override
   481     @Override
   448     public void moveTo(float x0, float y0) {
   482     public void moveTo(final float x0, final float y0) {
   449         if (prev == DRAWING_OP_TO) {
   483         moveTo(x0, y0, cOutCode);
   450             finish();
   484         // update starting point:
   451         }
   485         this.sx0 = x0;
   452         this.sx0 = this.cx0 = x0;
   486         this.sy0 = y0;
   453         this.sy0 = this.cy0 = y0;
   487         this.sdx = 1.0f;
   454         this.cdx = this.sdx = 1.0f;
   488         this.sdy = 0.0f;
   455         this.cdy = this.sdy = 0.0f;
   489         this.opened   = false;
   456         this.prev = MOVE_TO;
   490         this.capStart = false;
       
   491 
       
   492         if (clipRect != null) {
       
   493             final int outcode = Helpers.outcode(x0, y0, clipRect);
       
   494             this.cOutCode = outcode;
       
   495             this.sOutCode = outcode;
       
   496         }
       
   497     }
       
   498 
       
   499     private void moveTo(final float x0, final float y0,
       
   500                         final int outcode)
       
   501     {
       
   502         if (prev == MOVE_TO) {
       
   503             this.cx0 = x0;
       
   504             this.cy0 = y0;
       
   505         } else {
       
   506             if (prev == DRAWING_OP_TO) {
       
   507                 finish(outcode);
       
   508             }
       
   509             this.prev = MOVE_TO;
       
   510             this.cx0 = x0;
       
   511             this.cy0 = y0;
       
   512             this.cdx = 1.0f;
       
   513             this.cdy = 0.0f;
       
   514         }
   457     }
   515     }
   458 
   516 
   459     @Override
   517     @Override
   460     public void lineTo(float x1, float y1) {
   518     public void lineTo(final float x1, final float y1) {
       
   519         lineTo(x1, y1, false);
       
   520     }
       
   521 
       
   522     private void lineTo(final float x1, final float y1,
       
   523                         final boolean force)
       
   524     {
       
   525         final int outcode0 = this.cOutCode;
       
   526         if (!force && clipRect != null) {
       
   527             final int outcode1 = Helpers.outcode(x1, y1, clipRect);
       
   528             this.cOutCode = outcode1;
       
   529 
       
   530             // basic rejection criteria
       
   531             if ((outcode0 & outcode1) != 0) {
       
   532                 moveTo(x1, y1, outcode0);
       
   533                 opened = true;
       
   534                 return;
       
   535             }
       
   536         }
       
   537 
   461         float dx = x1 - cx0;
   538         float dx = x1 - cx0;
   462         float dy = y1 - cy0;
   539         float dy = y1 - cy0;
   463         if (dx == 0.0f && dy == 0.0f) {
   540         if (dx == 0.0f && dy == 0.0f) {
   464             dx = 1.0f;
   541             dx = 1.0f;
   465         }
   542         }
   466         computeOffset(dx, dy, lineWidth2, offset0);
   543         computeOffset(dx, dy, lineWidth2, offset0);
   467         final float mx = offset0[0];
   544         final float mx = offset0[0];
   468         final float my = offset0[1];
   545         final float my = offset0[1];
   469 
   546 
   470         drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my);
   547         drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my, outcode0);
   471 
   548 
   472         emitLineTo(cx0 + mx, cy0 + my);
   549         emitLineTo(cx0 + mx, cy0 + my);
   473         emitLineTo( x1 + mx,  y1 + my);
   550         emitLineTo( x1 + mx,  y1 + my);
   474 
   551 
   475         emitLineToRev(cx0 - mx, cy0 - my);
   552         emitLineToRev(cx0 - mx, cy0 - my);
   476         emitLineToRev( x1 - mx,  y1 - my);
   553         emitLineToRev( x1 - mx,  y1 - my);
   477 
   554 
       
   555         this.prev = DRAWING_OP_TO;
       
   556         this.cx0 = x1;
       
   557         this.cy0 = y1;
       
   558         this.cdx = dx;
       
   559         this.cdy = dy;
   478         this.cmx = mx;
   560         this.cmx = mx;
   479         this.cmy = my;
   561         this.cmy = my;
   480         this.cdx = dx;
       
   481         this.cdy = dy;
       
   482         this.cx0 = x1;
       
   483         this.cy0 = y1;
       
   484         this.prev = DRAWING_OP_TO;
       
   485     }
   562     }
   486 
   563 
   487     @Override
   564     @Override
   488     public void closePath() {
   565     public void closePath() {
   489         if (prev != DRAWING_OP_TO) {
   566         // distinguish empty path at all vs opened path ?
       
   567         if (prev != DRAWING_OP_TO && !opened) {
   490             if (prev == CLOSE) {
   568             if (prev == CLOSE) {
   491                 return;
   569                 return;
   492             }
   570             }
   493             emitMoveTo(cx0, cy0 - lineWidth2);
   571             emitMoveTo(cx0, cy0 - lineWidth2);
   494             this.cmx = this.smx = 0.0f;
   572 
   495             this.cmy = this.smy = -lineWidth2;
   573             this.sdx = 1.0f;
   496             this.cdx = this.sdx = 1.0f;
   574             this.sdy = 0.0f;
   497             this.cdy = this.sdy = 0.0f;
   575             this.cdx = 1.0f;
   498             finish();
   576             this.cdy = 0.0f;
       
   577 
       
   578             this.smx = 0.0f;
       
   579             this.smy = -lineWidth2;
       
   580             this.cmx = 0.0f;
       
   581             this.cmy = -lineWidth2;
       
   582 
       
   583             finish(cOutCode);
   499             return;
   584             return;
   500         }
   585         }
   501 
   586 
   502         if (cx0 != sx0 || cy0 != sy0) {
   587         // basic acceptance criteria
   503             lineTo(sx0, sy0);
   588         if ((sOutCode & cOutCode) == 0) {
   504         }
   589             if (cx0 != sx0 || cy0 != sy0) {
   505 
   590                 lineTo(sx0, sy0, true);
   506         drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy);
   591             }
   507 
   592 
   508         emitLineTo(sx0 + smx, sy0 + smy);
   593             drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy, sOutCode);
   509 
   594 
   510         emitMoveTo(sx0 - smx, sy0 - smy);
   595             emitLineTo(sx0 + smx, sy0 + smy);
       
   596 
       
   597             if (opened) {
       
   598                 emitLineTo(sx0 - smx, sy0 - smy);
       
   599             } else {
       
   600                 emitMoveTo(sx0 - smx, sy0 - smy);
       
   601             }
       
   602         }
       
   603         // Ignore caps like finish(false)
   511         emitReverse();
   604         emitReverse();
   512 
   605 
   513         this.prev = CLOSE;
   606         this.prev = CLOSE;
   514         emitClose();
   607 
       
   608         if (opened) {
       
   609             // do not emit close
       
   610             opened = false;
       
   611         } else {
       
   612             emitClose();
       
   613         }
   515     }
   614     }
   516 
   615 
   517     private void emitReverse() {
   616     private void emitReverse() {
   518         reverse.popAll(out);
   617         reverse.popAll(out);
   519     }
   618     }
   520 
   619 
   521     @Override
   620     @Override
   522     public void pathDone() {
   621     public void pathDone() {
   523         if (prev == DRAWING_OP_TO) {
   622         if (prev == DRAWING_OP_TO) {
   524             finish();
   623             finish(cOutCode);
   525         }
   624         }
   526 
   625 
   527         out.pathDone();
   626         out.pathDone();
   528 
   627 
   529         // this shouldn't matter since this object won't be used
   628         // this shouldn't matter since this object won't be used
   532 
   631 
   533         // Dispose this instance:
   632         // Dispose this instance:
   534         dispose();
   633         dispose();
   535     }
   634     }
   536 
   635 
   537     private void finish() {
   636     private void finish(final int outcode) {
   538         if (capStyle == CAP_ROUND) {
   637         // Problem: impossible to guess if the path will be closed in advance
   539             drawRoundCap(cx0, cy0, cmx, cmy);
   638         //          i.e. if caps must be drawn or not ?
   540         } else if (capStyle == CAP_SQUARE) {
   639         // Solution: use the ClosedPathDetector before Stroker to determine
   541             emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy);
   640         // if the path is a closed path or not
   542             emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy);
   641         if (!rdrCtx.closedPath) {
   543         }
   642             if (outcode == 0) {
   544 
   643                 // current point = end's cap:
   545         emitReverse();
   644                 if (capStyle == CAP_ROUND) {
   546 
   645                     drawRoundCap(cx0, cy0, cmx, cmy);
   547         if (capStyle == CAP_ROUND) {
   646                 } else if (capStyle == CAP_SQUARE) {
   548             drawRoundCap(sx0, sy0, -smx, -smy);
   647                     emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy);
   549         } else if (capStyle == CAP_SQUARE) {
   648                     emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy);
   550             emitLineTo(sx0 + smy - smx, sy0 - smx - smy);
   649                 }
   551             emitLineTo(sx0 + smy + smx, sy0 - smx + smy);
   650             }
   552         }
   651             emitReverse();
   553 
   652 
       
   653             if (!capStart) {
       
   654                 capStart = true;
       
   655 
       
   656                 if (sOutCode == 0) {
       
   657                     // starting point = initial cap:
       
   658                     if (capStyle == CAP_ROUND) {
       
   659                         drawRoundCap(sx0, sy0, -smx, -smy);
       
   660                     } else if (capStyle == CAP_SQUARE) {
       
   661                         emitLineTo(sx0 + smy - smx, sy0 - smx - smy);
       
   662                         emitLineTo(sx0 + smy + smx, sy0 - smx + smy);
       
   663                     }
       
   664                 }
       
   665             }
       
   666         } else {
       
   667             emitReverse();
       
   668         }
   554         emitClose();
   669         emitClose();
   555     }
   670     }
   556 
   671 
   557     private void emitMoveTo(final float x0, final float y0) {
   672     private void emitMoveTo(final float x0, final float y0) {
   558         out.moveTo(x0, y0);
   673         out.moveTo(x0, y0);
   620 
   735 
   621     private void drawJoin(float pdx, float pdy,
   736     private void drawJoin(float pdx, float pdy,
   622                           float x0, float y0,
   737                           float x0, float y0,
   623                           float dx, float dy,
   738                           float dx, float dy,
   624                           float omx, float omy,
   739                           float omx, float omy,
   625                           float mx, float my)
   740                           float mx, float my,
       
   741                           final int outcode)
   626     {
   742     {
   627         if (prev != DRAWING_OP_TO) {
   743         if (prev != DRAWING_OP_TO) {
   628             emitMoveTo(x0 + mx, y0 + my);
   744             emitMoveTo(x0 + mx, y0 + my);
   629             this.sdx = dx;
   745             if (!opened) {
   630             this.sdy = dy;
   746                 this.sdx = dx;
   631             this.smx = mx;
   747                 this.sdy = dy;
   632             this.smy = my;
   748                 this.smx = mx;
       
   749                 this.smy = my;
       
   750             }
   633         } else {
   751         } else {
   634             boolean cw = isCW(pdx, pdy, dx, dy);
   752             final boolean cw = isCW(pdx, pdy, dx, dy);
   635             if (joinStyle == JOIN_MITER) {
   753             if (outcode == 0) {
   636                 drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
   754                 if (joinStyle == JOIN_MITER) {
   637             } else if (joinStyle == JOIN_ROUND) {
   755                     drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
   638                 drawRoundJoin(x0, y0,
   756                 } else if (joinStyle == JOIN_ROUND) {
   639                               omx, omy,
   757                     drawRoundJoin(x0, y0,
   640                               mx, my, cw,
   758                                   omx, omy,
   641                               ROUND_JOIN_THRESHOLD);
   759                                   mx, my, cw,
       
   760                                   ROUND_JOIN_THRESHOLD);
       
   761                 }
   642             }
   762             }
   643             emitLineTo(x0, y0, !cw);
   763             emitLineTo(x0, y0, !cw);
   644         }
   764         }
   645         prev = DRAWING_OP_TO;
   765         prev = DRAWING_OP_TO;
   646     }
   766     }
   941         ret = Helpers.filterOutNotInAB(ts, 0, ret, 0.0001f, 0.9999f);
  1061         ret = Helpers.filterOutNotInAB(ts, 0, ret, 0.0001f, 0.9999f);
   942         Helpers.isort(ts, 0, ret);
  1062         Helpers.isort(ts, 0, ret);
   943         return ret;
  1063         return ret;
   944     }
  1064     }
   945 
  1065 
   946     @Override public void curveTo(float x1, float y1,
  1066     @Override
   947                                   float x2, float y2,
  1067     public void curveTo(final float x1, final float y1,
   948                                   float x3, float y3)
  1068                         final float x2, final float y2,
   949     {
  1069                         final float x3, final float y3)
       
  1070     {
       
  1071         final int outcode0 = this.cOutCode;
       
  1072         if (clipRect != null) {
       
  1073             final int outcode3 = Helpers.outcode(x3, y3, clipRect);
       
  1074             this.cOutCode = outcode3;
       
  1075 
       
  1076             if ((outcode0 & outcode3) != 0) {
       
  1077                 final int outcode1 = Helpers.outcode(x1, y1, clipRect);
       
  1078                 final int outcode2 = Helpers.outcode(x2, y2, clipRect);
       
  1079 
       
  1080                 // basic rejection criteria
       
  1081                 if ((outcode0 & outcode1 & outcode2 & outcode3) != 0) {
       
  1082                     moveTo(x3, y3, outcode0);
       
  1083                     opened = true;
       
  1084                     return;
       
  1085                 }
       
  1086             }
       
  1087         }
       
  1088 
   950         final float[] mid = middle;
  1089         final float[] mid = middle;
   951 
  1090 
   952         mid[0] = cx0; mid[1] = cy0;
  1091         mid[0] = cx0; mid[1] = cy0;
   953         mid[2] = x1;  mid[3] = y1;
  1092         mid[2] = x1;  mid[3] = y1;
   954         mid[4] = x2;  mid[5] = y2;
  1093         mid[4] = x2;  mid[5] = y2;
   955         mid[6] = x3;  mid[7] = y3;
  1094         mid[6] = x3;  mid[7] = y3;
   956 
  1095 
   957         // need these so we can update the state at the end of this method
  1096         // need these so we can update the state at the end of this method
   958         final float xf = mid[6], yf = mid[7];
  1097         final float xf = x3, yf = y3;
   959         float dxs = mid[2] - mid[0];
  1098         float dxs = mid[2] - mid[0];
   960         float dys = mid[3] - mid[1];
  1099         float dys = mid[3] - mid[1];
   961         float dxf = mid[6] - mid[4];
  1100         float dxf = mid[6] - mid[4];
   962         float dyf = mid[7] - mid[5];
  1101         float dyf = mid[7] - mid[5];
   963 
  1102 
   979                 dyf = mid[7] - mid[1];
  1118                 dyf = mid[7] - mid[1];
   980             }
  1119             }
   981         }
  1120         }
   982         if (dxs == 0.0f && dys == 0.0f) {
  1121         if (dxs == 0.0f && dys == 0.0f) {
   983             // this happens if the "curve" is just a point
  1122             // this happens if the "curve" is just a point
       
  1123             // fix outcode0 for lineTo() call:
       
  1124             if (clipRect != null) {
       
  1125                 this.cOutCode = outcode0;
       
  1126             }
   984             lineTo(mid[0], mid[1]);
  1127             lineTo(mid[0], mid[1]);
   985             return;
  1128             return;
   986         }
  1129         }
   987 
  1130 
   988         // if these vectors are too small, normalize them, to avoid future
  1131         // if these vectors are too small, normalize them, to avoid future
   997             dxf /= len;
  1140             dxf /= len;
   998             dyf /= len;
  1141             dyf /= len;
   999         }
  1142         }
  1000 
  1143 
  1001         computeOffset(dxs, dys, lineWidth2, offset0);
  1144         computeOffset(dxs, dys, lineWidth2, offset0);
  1002         drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]);
  1145         drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
  1003 
  1146 
  1004         final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2);
  1147         final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2);
  1005 
  1148 
  1006         float prevT = 0.0f;
  1149         float prevT = 0.0f;
  1007         for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
  1150         for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
  1032             default:
  1175             default:
  1033             }
  1176             }
  1034             emitLineToRev(r[kind - 2], r[kind - 1]);
  1177             emitLineToRev(r[kind - 2], r[kind - 1]);
  1035         }
  1178         }
  1036 
  1179 
       
  1180         this.prev = DRAWING_OP_TO;
       
  1181         this.cx0 = xf;
       
  1182         this.cy0 = yf;
       
  1183         this.cdx = dxf;
       
  1184         this.cdy = dyf;
  1037         this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
  1185         this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
  1038         this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f;
  1186         this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f;
  1039         this.cdx = dxf;
  1187     }
  1040         this.cdy = dyf;
  1188 
  1041         this.cx0 = xf;
  1189     @Override
  1042         this.cy0 = yf;
  1190     public void quadTo(final float x1, final float y1,
  1043         this.prev = DRAWING_OP_TO;
  1191                        final float x2, final float y2)
  1044     }
  1192     {
  1045 
  1193         final int outcode0 = this.cOutCode;
  1046     @Override public void quadTo(float x1, float y1, float x2, float y2) {
  1194         if (clipRect != null) {
       
  1195             final int outcode2 = Helpers.outcode(x2, y2, clipRect);
       
  1196             this.cOutCode = outcode2;
       
  1197 
       
  1198             if ((outcode0 & outcode2) != 0) {
       
  1199                 final int outcode1 = Helpers.outcode(x1, y1, clipRect);
       
  1200 
       
  1201                 // basic rejection criteria
       
  1202                 if ((outcode0 & outcode1 & outcode2) != 0) {
       
  1203                     moveTo(x2, y2, outcode0);
       
  1204                     opened = true;
       
  1205                     return;
       
  1206                 }
       
  1207             }
       
  1208         }
       
  1209 
  1047         final float[] mid = middle;
  1210         final float[] mid = middle;
  1048 
  1211 
  1049         mid[0] = cx0; mid[1] = cy0;
  1212         mid[0] = cx0; mid[1] = cy0;
  1050         mid[2] = x1;  mid[3] = y1;
  1213         mid[2] = x1;  mid[3] = y1;
  1051         mid[4] = x2;  mid[5] = y2;
  1214         mid[4] = x2;  mid[5] = y2;
  1052 
  1215 
  1053         // need these so we can update the state at the end of this method
  1216         // need these so we can update the state at the end of this method
  1054         final float xf = mid[4], yf = mid[5];
  1217         final float xf = x2, yf = y2;
  1055         float dxs = mid[2] - mid[0];
  1218         float dxs = mid[2] - mid[0];
  1056         float dys = mid[3] - mid[1];
  1219         float dys = mid[3] - mid[1];
  1057         float dxf = mid[4] - mid[2];
  1220         float dxf = mid[4] - mid[2];
  1058         float dyf = mid[5] - mid[3];
  1221         float dyf = mid[5] - mid[3];
  1059         if ((dxs == 0.0f && dys == 0.0f) || (dxf == 0.0f && dyf == 0.0f)) {
  1222         if ((dxs == 0.0f && dys == 0.0f) || (dxf == 0.0f && dyf == 0.0f)) {
  1060             dxs = dxf = mid[4] - mid[0];
  1223             dxs = dxf = mid[4] - mid[0];
  1061             dys = dyf = mid[5] - mid[1];
  1224             dys = dyf = mid[5] - mid[1];
  1062         }
  1225         }
  1063         if (dxs == 0.0f && dys == 0.0f) {
  1226         if (dxs == 0.0f && dys == 0.0f) {
  1064             // this happens if the "curve" is just a point
  1227             // this happens if the "curve" is just a point
       
  1228             // fix outcode0 for lineTo() call:
       
  1229             if (clipRect != null) {
       
  1230                 this.cOutCode = outcode0;
       
  1231             }
  1065             lineTo(mid[0], mid[1]);
  1232             lineTo(mid[0], mid[1]);
  1066             return;
  1233             return;
  1067         }
  1234         }
  1068         // if these vectors are too small, normalize them, to avoid future
  1235         // if these vectors are too small, normalize them, to avoid future
  1069         // precision problems.
  1236         // precision problems.
  1077             dxf /= len;
  1244             dxf /= len;
  1078             dyf /= len;
  1245             dyf /= len;
  1079         }
  1246         }
  1080 
  1247 
  1081         computeOffset(dxs, dys, lineWidth2, offset0);
  1248         computeOffset(dxs, dys, lineWidth2, offset0);
  1082         drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]);
  1249         drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
  1083 
  1250 
  1084         int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2);
  1251         int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2);
  1085 
  1252 
  1086         float prevt = 0.0f;
  1253         float prevt = 0.0f;
  1087         for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
  1254         for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
  1112             default:
  1279             default:
  1113             }
  1280             }
  1114             emitLineToRev(r[kind - 2], r[kind - 1]);
  1281             emitLineToRev(r[kind - 2], r[kind - 1]);
  1115         }
  1282         }
  1116 
  1283 
       
  1284         this.prev = DRAWING_OP_TO;
       
  1285         this.cx0 = xf;
       
  1286         this.cy0 = yf;
       
  1287         this.cdx = dxf;
       
  1288         this.cdy = dyf;
  1117         this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
  1289         this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
  1118         this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f;
  1290         this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f;
  1119         this.cdx = dxf;
       
  1120         this.cdy = dyf;
       
  1121         this.cx0 = xf;
       
  1122         this.cy0 = yf;
       
  1123         this.prev = DRAWING_OP_TO;
       
  1124     }
  1291     }
  1125 
  1292 
  1126     @Override public long getNativeConsumer() {
  1293     @Override public long getNativeConsumer() {
  1127         throw new InternalError("Stroker doesn't use a native consumer");
  1294         throw new InternalError("Stroker doesn't use a native consumer");
  1128     }
  1295     }
  1129 
       
  1130     // a stack of polynomial curves where each curve shares endpoints with
       
  1131     // adjacent ones.
       
  1132     static final class PolyStack {
       
  1133         private static final byte TYPE_LINETO  = (byte) 0;
       
  1134         private static final byte TYPE_QUADTO  = (byte) 1;
       
  1135         private static final byte TYPE_CUBICTO = (byte) 2;
       
  1136 
       
  1137         // curves capacity = edges count (8192) = edges x 2 (coords)
       
  1138         private static final int INITIAL_CURVES_COUNT = INITIAL_EDGES_COUNT << 1;
       
  1139 
       
  1140         // types capacity = edges count (4096)
       
  1141         private static final int INITIAL_TYPES_COUNT = INITIAL_EDGES_COUNT;
       
  1142 
       
  1143         float[] curves;
       
  1144         int end;
       
  1145         byte[] curveTypes;
       
  1146         int numCurves;
       
  1147 
       
  1148         // per-thread renderer context
       
  1149         final RendererContext rdrCtx;
       
  1150 
       
  1151         // curves ref (dirty)
       
  1152         final FloatArrayCache.Reference curves_ref;
       
  1153         // curveTypes ref (dirty)
       
  1154         final ByteArrayCache.Reference curveTypes_ref;
       
  1155 
       
  1156         // used marks (stats only)
       
  1157         int curveTypesUseMark;
       
  1158         int curvesUseMark;
       
  1159 
       
  1160         /**
       
  1161          * Constructor
       
  1162          * @param rdrCtx per-thread renderer context
       
  1163          */
       
  1164         PolyStack(final RendererContext rdrCtx) {
       
  1165             this.rdrCtx = rdrCtx;
       
  1166 
       
  1167             curves_ref = rdrCtx.newDirtyFloatArrayRef(INITIAL_CURVES_COUNT); // 32K
       
  1168             curves     = curves_ref.initial;
       
  1169 
       
  1170             curveTypes_ref = rdrCtx.newDirtyByteArrayRef(INITIAL_TYPES_COUNT); // 4K
       
  1171             curveTypes     = curveTypes_ref.initial;
       
  1172             numCurves = 0;
       
  1173             end = 0;
       
  1174 
       
  1175             if (DO_STATS) {
       
  1176                 curveTypesUseMark = 0;
       
  1177                 curvesUseMark = 0;
       
  1178             }
       
  1179         }
       
  1180 
       
  1181         /**
       
  1182          * Disposes this PolyStack:
       
  1183          * clean up before reusing this instance
       
  1184          */
       
  1185         void dispose() {
       
  1186             end = 0;
       
  1187             numCurves = 0;
       
  1188 
       
  1189             if (DO_STATS) {
       
  1190                 rdrCtx.stats.stat_rdr_poly_stack_types.add(curveTypesUseMark);
       
  1191                 rdrCtx.stats.stat_rdr_poly_stack_curves.add(curvesUseMark);
       
  1192                 rdrCtx.stats.hist_rdr_poly_stack_curves.add(curvesUseMark);
       
  1193 
       
  1194                 // reset marks
       
  1195                 curveTypesUseMark = 0;
       
  1196                 curvesUseMark = 0;
       
  1197             }
       
  1198 
       
  1199             // Return arrays:
       
  1200             // curves and curveTypes are kept dirty
       
  1201             curves     = curves_ref.putArray(curves);
       
  1202             curveTypes = curveTypes_ref.putArray(curveTypes);
       
  1203         }
       
  1204 
       
  1205         private void ensureSpace(final int n) {
       
  1206             // use substraction to avoid integer overflow:
       
  1207             if (curves.length - end < n) {
       
  1208                 if (DO_STATS) {
       
  1209                     rdrCtx.stats.stat_array_stroker_polystack_curves
       
  1210                         .add(end + n);
       
  1211                 }
       
  1212                 curves = curves_ref.widenArray(curves, end, end + n);
       
  1213             }
       
  1214             if (curveTypes.length <= numCurves) {
       
  1215                 if (DO_STATS) {
       
  1216                     rdrCtx.stats.stat_array_stroker_polystack_curveTypes
       
  1217                         .add(numCurves + 1);
       
  1218                 }
       
  1219                 curveTypes = curveTypes_ref.widenArray(curveTypes,
       
  1220                                                        numCurves,
       
  1221                                                        numCurves + 1);
       
  1222             }
       
  1223         }
       
  1224 
       
  1225         void pushCubic(float x0, float y0,
       
  1226                        float x1, float y1,
       
  1227                        float x2, float y2)
       
  1228         {
       
  1229             ensureSpace(6);
       
  1230             curveTypes[numCurves++] = TYPE_CUBICTO;
       
  1231             // we reverse the coordinate order to make popping easier
       
  1232             final float[] _curves = curves;
       
  1233             int e = end;
       
  1234             _curves[e++] = x2;    _curves[e++] = y2;
       
  1235             _curves[e++] = x1;    _curves[e++] = y1;
       
  1236             _curves[e++] = x0;    _curves[e++] = y0;
       
  1237             end = e;
       
  1238         }
       
  1239 
       
  1240         void pushQuad(float x0, float y0,
       
  1241                       float x1, float y1)
       
  1242         {
       
  1243             ensureSpace(4);
       
  1244             curveTypes[numCurves++] = TYPE_QUADTO;
       
  1245             final float[] _curves = curves;
       
  1246             int e = end;
       
  1247             _curves[e++] = x1;    _curves[e++] = y1;
       
  1248             _curves[e++] = x0;    _curves[e++] = y0;
       
  1249             end = e;
       
  1250         }
       
  1251 
       
  1252         void pushLine(float x, float y) {
       
  1253             ensureSpace(2);
       
  1254             curveTypes[numCurves++] = TYPE_LINETO;
       
  1255             curves[end++] = x;    curves[end++] = y;
       
  1256         }
       
  1257 
       
  1258         void popAll(PathConsumer2D io) {
       
  1259             if (DO_STATS) {
       
  1260                 // update used marks:
       
  1261                 if (numCurves > curveTypesUseMark) {
       
  1262                     curveTypesUseMark = numCurves;
       
  1263                 }
       
  1264                 if (end > curvesUseMark) {
       
  1265                     curvesUseMark = end;
       
  1266                 }
       
  1267             }
       
  1268             final byte[]  _curveTypes = curveTypes;
       
  1269             final float[] _curves = curves;
       
  1270             int nc = numCurves;
       
  1271             int e  = end;
       
  1272 
       
  1273             while (nc != 0) {
       
  1274                 switch(_curveTypes[--nc]) {
       
  1275                 case TYPE_LINETO:
       
  1276                     e -= 2;
       
  1277                     io.lineTo(_curves[e], _curves[e+1]);
       
  1278                     continue;
       
  1279                 case TYPE_QUADTO:
       
  1280                     e -= 4;
       
  1281                     io.quadTo(_curves[e+0], _curves[e+1],
       
  1282                               _curves[e+2], _curves[e+3]);
       
  1283                     continue;
       
  1284                 case TYPE_CUBICTO:
       
  1285                     e -= 6;
       
  1286                     io.curveTo(_curves[e+0], _curves[e+1],
       
  1287                                _curves[e+2], _curves[e+3],
       
  1288                                _curves[e+4], _curves[e+5]);
       
  1289                     continue;
       
  1290                 default:
       
  1291                 }
       
  1292             }
       
  1293             numCurves = 0;
       
  1294             end = 0;
       
  1295         }
       
  1296 
       
  1297         @Override
       
  1298         public String toString() {
       
  1299             String ret = "";
       
  1300             int nc = numCurves;
       
  1301             int last = end;
       
  1302             int len;
       
  1303             while (nc != 0) {
       
  1304                 switch(curveTypes[--nc]) {
       
  1305                 case TYPE_LINETO:
       
  1306                     len = 2;
       
  1307                     ret += "line: ";
       
  1308                     break;
       
  1309                 case TYPE_QUADTO:
       
  1310                     len = 4;
       
  1311                     ret += "quad: ";
       
  1312                     break;
       
  1313                 case TYPE_CUBICTO:
       
  1314                     len = 6;
       
  1315                     ret += "cubic: ";
       
  1316                     break;
       
  1317                 default:
       
  1318                     len = 0;
       
  1319                 }
       
  1320                 last -= len;
       
  1321                 ret += Arrays.toString(Arrays.copyOfRange(curves, last, last+len))
       
  1322                                        + "\n";
       
  1323             }
       
  1324             return ret;
       
  1325         }
       
  1326     }
       
  1327 }
  1296 }