< prev index next >

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

Print this page


   1 /*
   2  * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   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 import sun.awt.geom.PathConsumer2D;
  31 import sun.java2d.marlin.Helpers.PolyStack;


  32 
  33 // TODO: some of the arithmetic here is too verbose and prone to hard to
  34 // debug typos. We should consider making a small Point/Vector class that
  35 // has methods like plus(Point), minus(Point), dot(Point), cross(Point)and such
  36 final class Stroker implements PathConsumer2D, MarlinConst {
  37 
  38     private static final int MOVE_TO = 0;
  39     private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad
  40     private static final int CLOSE = 2;
  41 
  42     // pisces used to use fixed point arithmetic with 16 decimal digits. I
  43     // didn't want to change the values of the constant below when I converted
  44     // it to floating point, so that's why the divisions by 2^16 are there.
  45     private static final float ROUND_JOIN_THRESHOLD = 1000.0f/65536.0f;
  46 
  47     // kappa = (4/3) * (SQRT(2) - 1)
  48     private static final float C = (float)(4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d);
  49 
  50     // SQRT(2)
  51     private static final float SQRT_2 = (float)Math.sqrt(2.0d);
  52 
  53     private static final int MAX_N_CURVES = 11;
  54 
  55     private PathConsumer2D out;
  56 
  57     private int capStyle;
  58     private int joinStyle;
  59 
  60     private float lineWidth2;
  61     private float invHalfLineWidth2Sq;
  62 
  63     private final float[] offset0 = new float[2];
  64     private final float[] offset1 = new float[2];
  65     private final float[] offset2 = new float[2];
  66     private final float[] miter = new float[2];
  67     private float miterLimitSq;
  68 
  69     private int prev;
  70 
  71     // The starting point of the path, and the slope there.
  72     private float sx0, sy0, sdx, sdy;
  73     // the current point and the slope there.
  74     private float cx0, cy0, cdx, cdy; // c stands for current
  75     // vectors that when added to (sx0,sy0) and (cx0,cy0) respectively yield the
  76     // first and last points on the left parallel path. Since this path is
  77     // parallel, it's slope at any point is parallel to the slope of the
  78     // original path (thought they may have different directions), so these
  79     // could be computed from sdx,sdy and cdx,cdy (and vice versa), but that
  80     // would be error prone and hard to read, so we keep these anyway.
  81     private float smx, smy, cmx, cmy;
  82 
  83     private final PolyStack reverse;
  84 
  85     // This is where the curve to be processed is put. We give it
  86     // enough room to store all curves.
  87     private final float[] middle = new float[MAX_N_CURVES * 6 + 2];
  88     private final float[] lp = new float[8];
  89     private final float[] rp = new float[8];
  90     private final float[] subdivTs = new float[MAX_N_CURVES - 1];
  91 
  92     // per-thread renderer context
  93     final RendererContext rdrCtx;
  94 
  95     // dirty curve
  96     final Curve curve;
  97 
  98     // Bounds of the drawing region, at pixel precision.
  99     private float[] clipRect;
 100 
 101     // the outcode of the current point
 102     private int cOutCode = 0;
 103 
 104     // the outcode of the starting point
 105     private int sOutCode = 0;
 106 
 107     // flag indicating if the path is opened (clipped)
 108     private boolean opened = false;
 109     // flag indicating if the starting point's cap is done
 110     private boolean capStart = false;





 111 
 112     /**
 113      * Constructs a <code>Stroker</code>.
 114      * @param rdrCtx per-thread renderer context
 115      */
 116     Stroker(final RendererContext rdrCtx) {
 117         this.rdrCtx = rdrCtx;
 118 
 119         this.reverse = (rdrCtx.stats != null) ?
 120             new PolyStack(rdrCtx,
 121                     rdrCtx.stats.stat_str_polystack_types,
 122                     rdrCtx.stats.stat_str_polystack_curves,
 123                     rdrCtx.stats.hist_str_polystack_curves,
 124                     rdrCtx.stats.stat_array_str_polystack_curves,
 125                     rdrCtx.stats.stat_array_str_polystack_types)
 126             : new PolyStack(rdrCtx);
 127 
 128         this.curve = rdrCtx.curve;

 129     }
 130 
 131     /**
 132      * Inits the <code>Stroker</code>.
 133      *
 134      * @param pc2d an output <code>PathConsumer2D</code>.
 135      * @param lineWidth the desired line width in pixels
 136      * @param capStyle the desired end cap style, one of
 137      * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or
 138      * <code>CAP_SQUARE</code>.
 139      * @param joinStyle the desired line join style, one of
 140      * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or
 141      * <code>JOIN_BEVEL</code>.
 142      * @param miterLimit the desired miter limit
 143      * @param scale scaling factor applied to clip boundaries

 144      * @return this instance
 145      */
 146     Stroker init(final PathConsumer2D pc2d,
 147                  final float lineWidth,
 148                  final int capStyle,
 149                  final int joinStyle,
 150                  final float miterLimit,
 151                  final float scale)

 152     {
 153         this.out = pc2d;
 154 
 155         this.lineWidth2 = lineWidth / 2.0f;
 156         this.invHalfLineWidth2Sq = 1.0f / (2.0f * lineWidth2 * lineWidth2);


 157         this.capStyle = capStyle;
 158         this.joinStyle = joinStyle;
 159 
 160         final float limit = miterLimit * lineWidth2;
 161         this.miterLimitSq = limit * limit;
 162 
 163         this.prev = CLOSE;
 164 
 165         rdrCtx.stroking = 1;
 166 
 167         if (rdrCtx.doClip) {
 168             // Adjust the clipping rectangle with the stroker margin (miter limit, width)
 169             float rdrOffX = 0.0f, rdrOffY = 0.0f;
 170             float margin = lineWidth2;
 171 
 172             if (capStyle == CAP_SQUARE) {
 173                 margin *= SQRT_2;
 174             }
 175             if ((joinStyle == JOIN_MITER) && (margin < limit)) {
 176                 margin = limit;
 177             }
 178             if (scale != 1.0f) {
 179                 margin *= scale;
 180                 rdrOffX = scale * Renderer.RDR_OFFSET_X;
 181                 rdrOffY = scale * Renderer.RDR_OFFSET_Y;
 182             }
 183             // add a small rounding error:
 184             margin += 1e-3f;
 185 
 186             // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY
 187             // adjust clip rectangle (ymin, ymax, xmin, xmax):
 188             final float[] _clipRect = rdrCtx.clipRect;
 189             _clipRect[0] -= margin - rdrOffY;
 190             _clipRect[1] += margin + rdrOffY;
 191             _clipRect[2] -= margin - rdrOffX;
 192             _clipRect[3] += margin + rdrOffX;
 193             this.clipRect = _clipRect;









 194         } else {
 195             this.clipRect = null;
 196             this.cOutCode = 0;
 197             this.sOutCode = 0;
 198         }
 199         return this; // fluent API
 200     }
 201 






 202     /**
 203      * Disposes this stroker:
 204      * clean up before reusing this instance
 205      */
 206     void dispose() {
 207         reverse.dispose();
 208 
 209         opened   = false;
 210         capStart = false;
 211 
 212         if (DO_CLEAN_DIRTY) {
 213             // Force zero-fill dirty arrays:
 214             Arrays.fill(offset0, 0.0f);
 215             Arrays.fill(offset1, 0.0f);
 216             Arrays.fill(offset2, 0.0f);
 217             Arrays.fill(miter, 0.0f);
 218             Arrays.fill(middle, 0.0f);
 219             Arrays.fill(lp, 0.0f);
 220             Arrays.fill(rp, 0.0f);
 221             Arrays.fill(subdivTs, 0.0f);
 222         }
 223     }
 224 
 225     private static void computeOffset(final float lx, final float ly,
 226                                       final float w, final float[] m)
 227     {
 228         float len = lx*lx + ly*ly;
 229         if (len == 0.0f) {
 230             m[0] = 0.0f;
 231             m[1] = 0.0f;
 232         } else {
 233             len = (float) Math.sqrt(len);
 234             m[0] =  (ly * w) / len;
 235             m[1] = -(lx * w) / len;
 236         }
 237     }
 238 
 239     // Returns true if the vectors (dx1, dy1) and (dx2, dy2) are
 240     // clockwise (if dx1,dy1 needs to be rotated clockwise to close
 241     // the smallest angle between it and dx2,dy2).
 242     // This is equivalent to detecting whether a point q is on the right side
 243     // of a line passing through points p1, p2 where p2 = p1+(dx1,dy1) and
 244     // q = p2+(dx2,dy2), which is the same as saying p1, p2, q are in a
 245     // clockwise order.
 246     // NOTE: "clockwise" here assumes coordinates with 0,0 at the bottom left.
 247     private static boolean isCW(final float dx1, final float dy1,
 248                                 final float dx2, final float dy2)
 249     {
 250         return dx1 * dy2 <= dy1 * dx2;
 251     }
 252 
 253     private void drawRoundJoin(float x, float y,
 254                                float omx, float omy, float mx, float my,
 255                                boolean rev,
 256                                float threshold)
 257     {
 258         if ((omx == 0.0f && omy == 0.0f) || (mx == 0.0f && my == 0.0f)) {
 259             return;
 260         }
 261 
 262         float domx = omx - mx;
 263         float domy = omy - my;
 264         float len = domx*domx + domy*domy;
 265         if (len < threshold) {

 266             return;
 267         }
 268 
 269         if (rev) {
 270             omx = -omx;
 271             omy = -omy;
 272             mx  = -mx;
 273             my  = -my;
 274         }
 275         drawRoundJoin(x, y, omx, omy, mx, my, rev);
 276     }
 277 
 278     private void drawRoundJoin(float cx, float cy,
 279                                float omx, float omy,
 280                                float mx, float my,
 281                                boolean rev)
 282     {
 283         // The sign of the dot product of mx,my and omx,omy is equal to the
 284         // the sign of the cosine of ext
 285         // (ext is the angle between omx,omy and mx,my).
 286         final float cosext = omx * mx + omy * my;
 287         // If it is >=0, we know that abs(ext) is <= 90 degrees, so we only
 288         // need 1 curve to approximate the circle section that joins omx,omy
 289         // and mx,my.
 290         final int numCurves = (cosext >= 0.0f) ? 1 : 2;
 291 
 292         switch (numCurves) {
 293         case 1:
 294             drawBezApproxForArc(cx, cy, omx, omy, mx, my, rev);
 295             break;


 366         emitCurveTo(x1, y1, x2, y2, x3, y3, x4, y4, rev);
 367     }
 368 
 369     private void drawRoundCap(float cx, float cy, float mx, float my) {
 370         final float Cmx = C * mx;
 371         final float Cmy = C * my;
 372         emitCurveTo(cx + mx - Cmy, cy + my + Cmx,
 373                     cx - my + Cmx, cy + mx + Cmy,
 374                     cx - my,       cy + mx);
 375         emitCurveTo(cx - my - Cmx, cy + mx - Cmy,
 376                     cx - mx - Cmy, cy - my + Cmx,
 377                     cx - mx,       cy - my);
 378     }
 379 
 380     // Return the intersection point of the lines (x0, y0) -> (x1, y1)
 381     // and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1]
 382     private static void computeMiter(final float x0, final float y0,
 383                                      final float x1, final float y1,
 384                                      final float x0p, final float y0p,
 385                                      final float x1p, final float y1p,
 386                                      final float[] m, int off)
 387     {
 388         float x10 = x1 - x0;
 389         float y10 = y1 - y0;
 390         float x10p = x1p - x0p;
 391         float y10p = y1p - y0p;
 392 
 393         // if this is 0, the lines are parallel. If they go in the
 394         // same direction, there is no intersection so m[off] and
 395         // m[off+1] will contain infinity, so no miter will be drawn.
 396         // If they go in the same direction that means that the start of the
 397         // current segment and the end of the previous segment have the same
 398         // tangent, in which case this method won't even be involved in
 399         // miter drawing because it won't be called by drawMiter (because
 400         // (mx == omx && my == omy) will be true, and drawMiter will return
 401         // immediately).
 402         float den = x10*y10p - x10p*y10;
 403         float t = x10p*(y0-y0p) - y10p*(x0-x0p);
 404         t /= den;
 405         m[off++] = x0 + t*x10;
 406         m[off]   = y0 + t*y10;
 407     }
 408 
 409     // Return the intersection point of the lines (x0, y0) -> (x1, y1)
 410     // and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1]
 411     private static void safeComputeMiter(final float x0, final float y0,
 412                                          final float x1, final float y1,
 413                                          final float x0p, final float y0p,
 414                                          final float x1p, final float y1p,
 415                                          final float[] m, int off)
 416     {
 417         float x10 = x1 - x0;
 418         float y10 = y1 - y0;
 419         float x10p = x1p - x0p;
 420         float y10p = y1p - y0p;
 421 
 422         // if this is 0, the lines are parallel. If they go in the
 423         // same direction, there is no intersection so m[off] and
 424         // m[off+1] will contain infinity, so no miter will be drawn.
 425         // If they go in the same direction that means that the start of the
 426         // current segment and the end of the previous segment have the same
 427         // tangent, in which case this method won't even be involved in
 428         // miter drawing because it won't be called by drawMiter (because
 429         // (mx == omx && my == omy) will be true, and drawMiter will return
 430         // immediately).
 431         float den = x10*y10p - x10p*y10;
 432         if (den == 0.0f) {
 433             m[off++] = (x0 + x0p) / 2.0f;
 434             m[off]   = (y0 + y0p) / 2.0f;
 435             return;




 436         }
 437         float t = x10p*(y0-y0p) - y10p*(x0-x0p);
 438         t /= den;
 439         m[off++] = x0 + t*x10;
 440         m[off] = y0 + t*y10;
 441     }
 442 
 443     private void drawMiter(final float pdx, final float pdy,
 444                            final float x0, final float y0,
 445                            final float dx, final float dy,
 446                            float omx, float omy, float mx, float my,

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

 526         if (!force && clipRect != null) {
 527             final int outcode1 = Helpers.outcode(x1, y1, clipRect);
 528             this.cOutCode = outcode1;
 529 
 530             // basic rejection criteria
 531             if ((outcode0 & outcode1) != 0) {
 532                 moveTo(x1, y1, outcode0);
 533                 opened = true;
 534                 return;






















 535             }


 536         }
 537 
 538         float dx = x1 - cx0;
 539         float dy = y1 - cy0;
 540         if (dx == 0.0f && dy == 0.0f) {
 541             dx = 1.0f;
 542         }
 543         computeOffset(dx, dy, lineWidth2, offset0);
 544         final float mx = offset0[0];
 545         final float my = offset0[1];
 546 
 547         drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my, outcode0);
 548 
 549         emitLineTo(cx0 + mx, cy0 + my);
 550         emitLineTo( x1 + mx,  y1 + my);
 551 
 552         emitLineToRev(cx0 - mx, cy0 - my);
 553         emitLineToRev( x1 - mx,  y1 - my);
 554 
 555         this.prev = DRAWING_OP_TO;


 737                           float x0, float y0,
 738                           float dx, float dy,
 739                           float omx, float omy,
 740                           float mx, float my,
 741                           final int outcode)
 742     {
 743         if (prev != DRAWING_OP_TO) {
 744             emitMoveTo(x0 + mx, y0 + my);
 745             if (!opened) {
 746                 this.sdx = dx;
 747                 this.sdy = dy;
 748                 this.smx = mx;
 749                 this.smy = my;
 750             }
 751         } else {
 752             final boolean cw = isCW(pdx, pdy, dx, dy);
 753             if (outcode == 0) {
 754                 if (joinStyle == JOIN_MITER) {
 755                     drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
 756                 } else if (joinStyle == JOIN_ROUND) {
 757                     drawRoundJoin(x0, y0,
 758                                   omx, omy,
 759                                   mx, my, cw,
 760                                   ROUND_JOIN_THRESHOLD);
 761                 }
 762             }
 763             emitLineTo(x0, y0, !cw);
 764         }
 765         prev = DRAWING_OP_TO;
 766     }
 767 
 768     private static boolean within(final float x1, final float y1,
 769                                   final float x2, final float y2,
 770                                   final float ERR)
 771     {
 772         assert ERR > 0 : "";
 773         // compare taxicab distance. ERR will always be small, so using
 774         // true distance won't give much benefit
 775         return (Helpers.within(x1, x2, ERR) &&  // we want to avoid calling Math.abs
 776                 Helpers.within(y1, y2, ERR)); // this is just as good.
 777     }
 778 
 779     private void getLineOffsets(float x1, float y1,
 780                                 float x2, float y2,
 781                                 float[] left, float[] right) {

 782         computeOffset(x2 - x1, y2 - y1, lineWidth2, offset0);
 783         final float mx = offset0[0];
 784         final float my = offset0[1];
 785         left[0] = x1 + mx;
 786         left[1] = y1 + my;
 787         left[2] = x2 + mx;
 788         left[3] = y2 + my;

 789         right[0] = x1 - mx;
 790         right[1] = y1 - my;
 791         right[2] = x2 - mx;
 792         right[3] = y2 - my;
 793     }
 794 
 795     private int computeOffsetCubic(float[] pts, final int off,
 796                                    float[] leftOff, float[] rightOff)

 797     {
 798         // if p1=p2 or p3=p4 it means that the derivative at the endpoint
 799         // vanishes, which creates problems with computeOffset. Usually
 800         // this happens when this stroker object is trying to widen
 801         // a curve with a cusp. What happens is that curveTo splits
 802         // the input curve at the cusp, and passes it to this function.
 803         // because of inaccuracies in the splitting, we consider points
 804         // equal if they're very close to each other.
 805         final float x1 = pts[off + 0], y1 = pts[off + 1];
 806         final float x2 = pts[off + 2], y2 = pts[off + 3];
 807         final float x3 = pts[off + 4], y3 = pts[off + 5];
 808         final float x4 = pts[off + 6], y4 = pts[off + 7];
 809 
 810         float dx4 = x4 - x3;
 811         float dy4 = y4 - y3;
 812         float dx1 = x2 - x1;
 813         float dy1 = y2 - y1;
 814 
 815         // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4,
 816         // in which case ignore if p1 == p2
 817         final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0f * Math.ulp(y2));
 818         final boolean p3eqp4 = within(x3, y3, x4, y4, 6.0f * Math.ulp(y4));

 819         if (p1eqp2 && p3eqp4) {
 820             getLineOffsets(x1, y1, x4, y4, leftOff, rightOff);
 821             return 4;
 822         } else if (p1eqp2) {
 823             dx1 = x3 - x1;
 824             dy1 = y3 - y1;
 825         } else if (p3eqp4) {
 826             dx4 = x4 - x2;
 827             dy4 = y4 - y2;
 828         }
 829 
 830         // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line
 831         float dotsq = (dx1 * dx4 + dy1 * dy4);
 832         dotsq *= dotsq;
 833         float l1sq = dx1 * dx1 + dy1 * dy1, l4sq = dx4 * dx4 + dy4 * dy4;

 834         if (Helpers.within(dotsq, l1sq * l4sq, 4.0f * Math.ulp(dotsq))) {
 835             getLineOffsets(x1, y1, x4, y4, leftOff, rightOff);
 836             return 4;
 837         }
 838 
 839 //      What we're trying to do in this function is to approximate an ideal
 840 //      offset curve (call it I) of the input curve B using a bezier curve Bp.
 841 //      The constraints I use to get the equations are:
 842 //
 843 //      1. The computed curve Bp should go through I(0) and I(1). These are
 844 //      x1p, y1p, x4p, y4p, which are p1p and p4p. We still need to find
 845 //      4 variables: the x and y components of p2p and p3p (i.e. x2p, y2p, x3p, y3p).
 846 //
 847 //      2. Bp should have slope equal in absolute value to I at the endpoints. So,
 848 //      (by the way, the operator || in the comments below means "aligned with".
 849 //      It is defined on vectors, so when we say I'(0) || Bp'(0) we mean that
 850 //      vectors I'(0) and Bp'(0) are aligned, which is the same as saying
 851 //      that the tangent lines of I and Bp at 0 are parallel. Mathematically
 852 //      this means (I'(t) || Bp'(t)) <==> (I'(t) = c * Bp'(t)) where c is some
 853 //      nonzero constant.)


 927         two_pi_m_p1_m_p4x = 2.0f * xi - x1p - x4p;
 928         two_pi_m_p1_m_p4y = 2.0f * yi - y1p - y4p;
 929         c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y);
 930         c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x);
 931 
 932         x2p = x1p + c1*dx1;
 933         y2p = y1p + c1*dy1;
 934         x3p = x4p + c2*dx4;
 935         y3p = y4p + c2*dy4;
 936 
 937         rightOff[0] = x1p; rightOff[1] = y1p;
 938         rightOff[2] = x2p; rightOff[3] = y2p;
 939         rightOff[4] = x3p; rightOff[5] = y3p;
 940         rightOff[6] = x4p; rightOff[7] = y4p;
 941         return 8;
 942     }
 943 
 944     // compute offset curves using bezier spline through t=0.5 (i.e.
 945     // ComputedCurve(0.5) == IdealParallelCurve(0.5))
 946     // return the kind of curve in the right and left arrays.
 947     private int computeOffsetQuad(float[] pts, final int off,
 948                                   float[] leftOff, float[] rightOff)

 949     {
 950         final float x1 = pts[off + 0], y1 = pts[off + 1];
 951         final float x2 = pts[off + 2], y2 = pts[off + 3];
 952         final float x3 = pts[off + 4], y3 = pts[off + 5];
 953 
 954         final float dx3 = x3 - x2;
 955         final float dy3 = y3 - y2;
 956         final float dx1 = x2 - x1;
 957         final float dy1 = y2 - y1;
 958 
 959         // if p1=p2 or p3=p4 it means that the derivative at the endpoint
 960         // vanishes, which creates problems with computeOffset. Usually
 961         // this happens when this stroker object is trying to widen
 962         // a curve with a cusp. What happens is that curveTo splits
 963         // the input curve at the cusp, and passes it to this function.
 964         // because of inaccuracies in the splitting, we consider points
 965         // equal if they're very close to each other.
 966 
 967         // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4,
 968         // in which case ignore.
 969         final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0f * Math.ulp(y2));
 970         final boolean p2eqp3 = within(x2, y2, x3, y3, 6.0f * Math.ulp(y3));

 971         if (p1eqp2 || p2eqp3) {
 972             getLineOffsets(x1, y1, x3, y3, leftOff, rightOff);
 973             return 4;
 974         }
 975 
 976         // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line
 977         float dotsq = (dx1 * dx3 + dy1 * dy3);
 978         dotsq *= dotsq;
 979         float l1sq = dx1 * dx1 + dy1 * dy1, l3sq = dx3 * dx3 + dy3 * dy3;

 980         if (Helpers.within(dotsq, l1sq * l3sq, 4.0f * Math.ulp(dotsq))) {
 981             getLineOffsets(x1, y1, x3, y3, leftOff, rightOff);
 982             return 4;
 983         }
 984 
 985         // this computes the offsets at t=0, 0.5, 1, using the property that
 986         // for any bezier curve the vectors p2-p1 and p4-p3 are parallel to
 987         // the (dx/dt, dy/dt) vectors at the endpoints.
 988         computeOffset(dx1, dy1, lineWidth2, offset0);
 989         computeOffset(dx3, dy3, lineWidth2, offset1);
 990 
 991         float x1p = x1 + offset0[0]; // start
 992         float y1p = y1 + offset0[1]; // point
 993         float x3p = x3 + offset1[0]; // end
 994         float y3p = y3 + offset1[1]; // point
 995         safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, leftOff, 2);
 996         leftOff[0] = x1p; leftOff[1] = y1p;
 997         leftOff[4] = x3p; leftOff[5] = y3p;
 998 
 999         x1p = x1 - offset0[0]; y1p = y1 - offset0[1];
1000         x3p = x3 - offset1[0]; y3p = y3 - offset1[1];
1001         safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, rightOff, 2);
1002         rightOff[0] = x1p; rightOff[1] = y1p;
1003         rightOff[4] = x3p; rightOff[5] = y3p;
1004         return 6;
1005     }
1006 
1007     // finds values of t where the curve in pts should be subdivided in order
1008     // to get good offset curves a distance of w away from the middle curve.
1009     // Stores the points in ts, and returns how many of them there were.
1010     private static int findSubdivPoints(final Curve c, float[] pts, float[] ts,
1011                                         final int type, final float w)
1012     {
1013         final float x12 = pts[2] - pts[0];
1014         final float y12 = pts[3] - pts[1];
1015         // if the curve is already parallel to either axis we gain nothing
1016         // from rotating it.
1017         if (y12 != 0.0f && x12 != 0.0f) {
1018             // we rotate it so that the first vector in the control polygon is
1019             // parallel to the x-axis. This will ensure that rotated quarter
1020             // circles won't be subdivided.
1021             final float hypot = (float) Math.sqrt(x12 * x12 + y12 * y12);
1022             final float cos = x12 / hypot;
1023             final float sin = y12 / hypot;
1024             final float x1 = cos * pts[0] + sin * pts[1];
1025             final float y1 = cos * pts[1] - sin * pts[0];
1026             final float x2 = cos * pts[2] + sin * pts[3];
1027             final float y2 = cos * pts[3] - sin * pts[2];
1028             final float x3 = cos * pts[4] + sin * pts[5];
1029             final float y3 = cos * pts[5] - sin * pts[4];
1030 
1031             switch(type) {
1032             case 8:
1033                 final float x4 = cos * pts[6] + sin * pts[7];
1034                 final float y4 = cos * pts[7] - sin * pts[6];
1035                 c.set(x1, y1, x2, y2, x3, y3, x4, y4);
1036                 break;
1037             case 6:
1038                 c.set(x1, y1, x2, y2, x3, y3);
1039                 break;
1040             default:
1041             }
1042         } else {
1043             c.set(pts, type);
1044         }
1045 
1046         int ret = 0;
1047         // we subdivide at values of t such that the remaining rotated
1048         // curves are monotonic in x and y.
1049         ret += c.dxRoots(ts, ret);
1050         ret += c.dyRoots(ts, ret);
1051         // subdivide at inflection points.
1052         if (type == 8) {
1053             // quadratic curves can't have inflection points
1054             ret += c.infPoints(ts, ret);
1055         }
1056 
1057         // now we must subdivide at points where one of the offset curves will have
1058         // a cusp. This happens at ts where the radius of curvature is equal to w.
1059         ret += c.rootsOfROCMinusW(ts, ret, w, 0.0001f);
1060 
1061         ret = Helpers.filterOutNotInAB(ts, 0, ret, 0.0001f, 0.9999f);
1062         Helpers.isort(ts, 0, ret);
1063         return ret;
1064     }
1065 
1066     @Override
1067     public void curveTo(final float x1, final float y1,
1068                         final float x2, final float y2,
1069                         final float x3, final float y3)
1070     {
1071         final int outcode0 = this.cOutCode;

1072         if (clipRect != null) {


1073             final int outcode3 = Helpers.outcode(x3, y3, clipRect);
1074             this.cOutCode = outcode3;
1075 
1076             if ((outcode0 & outcode3) != 0) {
1077                 final int outcode1 = Helpers.outcode(x1, y1, clipRect);
1078                 final int outcode2 = Helpers.outcode(x2, y2, clipRect);
1079 
1080                 // basic rejection criteria
1081                 if ((outcode0 & outcode1 & outcode2 & outcode3) != 0) {
1082                     moveTo(x3, y3, outcode0);


















1083                     opened = true;
1084                     return;
1085                 }
1086             }
1087         }
1088 
1089         final float[] mid = middle;
1090 
1091         mid[0] = cx0; mid[1] = cy0;
1092         mid[2] = x1;  mid[3] = y1;
1093         mid[4] = x2;  mid[5] = y2;
1094         mid[6] = x3;  mid[7] = y3;
1095 





1096         // need these so we can update the state at the end of this method
1097         final float xf = x3, yf = y3;
1098         float dxs = mid[2] - mid[0];
1099         float dys = mid[3] - mid[1];
1100         float dxf = mid[6] - mid[4];
1101         float dyf = mid[7] - mid[5];
1102 
1103         boolean p1eqp2 = (dxs == 0.0f && dys == 0.0f);
1104         boolean p3eqp4 = (dxf == 0.0f && dyf == 0.0f);
1105         if (p1eqp2) {
1106             dxs = mid[4] - mid[0];
1107             dys = mid[5] - mid[1];
1108             if (dxs == 0.0f && dys == 0.0f) {
1109                 dxs = mid[6] - mid[0];
1110                 dys = mid[7] - mid[1];
1111             }
1112         }
1113         if (p3eqp4) {
1114             dxf = mid[6] - mid[2];
1115             dyf = mid[7] - mid[3];
1116             if (dxf == 0.0f && dyf == 0.0f) {
1117                 dxf = mid[6] - mid[0];
1118                 dyf = mid[7] - mid[1];
1119             }
1120         }
1121         if (dxs == 0.0f && dys == 0.0f) {
1122             // this happens if the "curve" is just a point
1123             // fix outcode0 for lineTo() call:
1124             if (clipRect != null) {
1125                 this.cOutCode = outcode0;
1126             }
1127             lineTo(mid[0], mid[1]);
1128             return;
1129         }
1130 
1131         // if these vectors are too small, normalize them, to avoid future
1132         // precision problems.
1133         if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) {
1134             float len = (float) Math.sqrt(dxs*dxs + dys*dys);
1135             dxs /= len;
1136             dys /= len;
1137         }
1138         if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) {
1139             float len = (float) Math.sqrt(dxf*dxf + dyf*dyf);
1140             dxf /= len;
1141             dyf /= len;
1142         }
1143 
1144         computeOffset(dxs, dys, lineWidth2, offset0);
1145         drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
1146 
1147         final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2);


1148 
1149         float prevT = 0.0f;
1150         for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
1151             final float t = subdivTs[i];
1152             Helpers.subdivideCubicAt((t - prevT) / (1.0f - prevT),
1153                                      mid, off, mid, off, mid, off + 6);
1154             prevT = t;
1155         }
1156 
1157         final float[] l = lp;









1158         final float[] r = rp;
1159 
1160         int kind = 0;
1161         for (int i = 0, off = 0; i <= nSplits; i++, off += 6) {
1162             kind = computeOffsetCubic(mid, off, l, r);
1163 
1164             emitLineTo(l[0], l[1]);
1165 
1166             switch(kind) {
1167             case 8:
1168                 emitCurveTo(l[2], l[3], l[4], l[5], l[6], l[7]);
1169                 emitCurveToRev(r[0], r[1], r[2], r[3], r[4], r[5]);
1170                 break;
1171             case 4:
1172                 emitLineTo(l[2], l[3]);
1173                 emitLineToRev(r[0], r[1]);
1174                 break;
1175             default:
1176             }
1177             emitLineToRev(r[kind - 2], r[kind - 1]);
1178         }
1179 
1180         this.prev = DRAWING_OP_TO;
1181         this.cx0 = xf;
1182         this.cy0 = yf;
1183         this.cdx = dxf;
1184         this.cdy = dyf;
1185         this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
1186         this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f;
1187     }
1188 
1189     @Override
1190     public void quadTo(final float x1, final float y1,
1191                        final float x2, final float y2)
1192     {
1193         final int outcode0 = this.cOutCode;

1194         if (clipRect != null) {

1195             final int outcode2 = Helpers.outcode(x2, y2, clipRect);
1196             this.cOutCode = outcode2;
1197 
1198             if ((outcode0 & outcode2) != 0) {
1199                 final int outcode1 = Helpers.outcode(x1, y1, clipRect);
1200 
1201                 // basic rejection criteria
1202                 if ((outcode0 & outcode1 & outcode2) != 0) {
1203                     moveTo(x2, y2, outcode0);





















1204                     opened = true;
1205                     return;
1206                 }
1207             }
1208         }
1209 
1210         final float[] mid = middle;
1211 
1212         mid[0] = cx0; mid[1] = cy0;
1213         mid[2] = x1;  mid[3] = y1;
1214         mid[4] = x2;  mid[5] = y2;
1215 




1216         // need these so we can update the state at the end of this method
1217         final float xf = x2, yf = y2;
1218         float dxs = mid[2] - mid[0];
1219         float dys = mid[3] - mid[1];
1220         float dxf = mid[4] - mid[2];
1221         float dyf = mid[5] - mid[3];
1222         if ((dxs == 0.0f && dys == 0.0f) || (dxf == 0.0f && dyf == 0.0f)) {
1223             dxs = dxf = mid[4] - mid[0];
1224             dys = dyf = mid[5] - mid[1];
1225         }
1226         if (dxs == 0.0f && dys == 0.0f) {
1227             // this happens if the "curve" is just a point
1228             // fix outcode0 for lineTo() call:
1229             if (clipRect != null) {
1230                 this.cOutCode = outcode0;
1231             }
1232             lineTo(mid[0], mid[1]);
1233             return;
1234         }
1235         // if these vectors are too small, normalize them, to avoid future
1236         // precision problems.
1237         if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) {
1238             float len = (float) Math.sqrt(dxs*dxs + dys*dys);
1239             dxs /= len;
1240             dys /= len;
1241         }
1242         if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) {
1243             float len = (float) Math.sqrt(dxf*dxf + dyf*dyf);
1244             dxf /= len;
1245             dyf /= len;
1246         }
1247 
1248         computeOffset(dxs, dys, lineWidth2, offset0);
1249         drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
1250 
1251         int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2);


1252 
1253         float prevt = 0.0f;
1254         for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
1255             final float t = subdivTs[i];
1256             Helpers.subdivideQuadAt((t - prevt) / (1.0f - prevt),
1257                                     mid, off, mid, off, mid, off + 4);
1258             prevt = t;
1259         }
1260 
1261         final float[] l = lp;








1262         final float[] r = rp;
1263 
1264         int kind = 0;
1265         for (int i = 0, off = 0; i <= nSplits; i++, off += 4) {
1266             kind = computeOffsetQuad(mid, off, l, r);
1267 
1268             emitLineTo(l[0], l[1]);
1269 
1270             switch(kind) {
1271             case 6:
1272                 emitQuadTo(l[2], l[3], l[4], l[5]);
1273                 emitQuadToRev(r[0], r[1], r[2], r[3]);
1274                 break;
1275             case 4:
1276                 emitLineTo(l[2], l[3]);
1277                 emitLineToRev(r[0], r[1]);
1278                 break;
1279             default:
1280             }
1281             emitLineToRev(r[kind - 2], r[kind - 1]);
1282         }
1283 
1284         this.prev = DRAWING_OP_TO;
1285         this.cx0 = xf;
1286         this.cy0 = yf;
1287         this.cdx = dxf;
1288         this.cdy = dyf;
1289         this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
1290         this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f;
1291     }
1292 
1293     @Override public long getNativeConsumer() {
1294         throw new InternalError("Stroker doesn't use a native consumer");
1295     }
1296 }
   1 /*
   2  * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   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 import sun.awt.geom.PathConsumer2D;
  31 import sun.java2d.marlin.Helpers.PolyStack;
  32 import sun.java2d.marlin.TransformingPathConsumer2D.CurveBasicMonotonizer;
  33 import sun.java2d.marlin.TransformingPathConsumer2D.CurveClipSplitter;
  34 
  35 // TODO: some of the arithmetic here is too verbose and prone to hard to
  36 // debug typos. We should consider making a small Point/Vector class that
  37 // has methods like plus(Point), minus(Point), dot(Point), cross(Point)and such
  38 final class Stroker implements PathConsumer2D, MarlinConst {
  39 
  40     private static final int MOVE_TO = 0;
  41     private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad
  42     private static final int CLOSE = 2;
  43 
  44     // round join threshold = 1 subpixel
  45     private static final float ERR_JOIN = (1.0f / MIN_SUBPIXELS);
  46     private static final float ROUND_JOIN_THRESHOLD = ERR_JOIN * ERR_JOIN;

  47 
  48     // kappa = (4/3) * (SQRT(2) - 1)
  49     private static final float C = (float)(4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d);
  50 
  51     // SQRT(2)
  52     private static final float SQRT_2 = (float)Math.sqrt(2.0d);
  53 


  54     private PathConsumer2D out;
  55 
  56     private int capStyle;
  57     private int joinStyle;
  58 
  59     private float lineWidth2;
  60     private float invHalfLineWidth2Sq;
  61 
  62     private final float[] offset0 = new float[2];
  63     private final float[] offset1 = new float[2];
  64     private final float[] offset2 = new float[2];
  65     private final float[] miter = new float[2];
  66     private float miterLimitSq;
  67 
  68     private int prev;
  69 
  70     // The starting point of the path, and the slope there.
  71     private float sx0, sy0, sdx, sdy;
  72     // the current point and the slope there.
  73     private float cx0, cy0, cdx, cdy; // c stands for current
  74     // vectors that when added to (sx0,sy0) and (cx0,cy0) respectively yield the
  75     // first and last points on the left parallel path. Since this path is
  76     // parallel, it's slope at any point is parallel to the slope of the
  77     // original path (thought they may have different directions), so these
  78     // could be computed from sdx,sdy and cdx,cdy (and vice versa), but that
  79     // would be error prone and hard to read, so we keep these anyway.
  80     private float smx, smy, cmx, cmy;
  81 
  82     private final PolyStack reverse;
  83 



  84     private final float[] lp = new float[8];
  85     private final float[] rp = new float[8];

  86 
  87     // per-thread renderer context
  88     final RendererContext rdrCtx;
  89 
  90     // dirty curve
  91     final Curve curve;
  92 
  93     // Bounds of the drawing region, at pixel precision.
  94     private float[] clipRect;
  95 
  96     // the outcode of the current point
  97     private int cOutCode = 0;
  98 
  99     // the outcode of the starting point
 100     private int sOutCode = 0;
 101 
 102     // flag indicating if the path is opened (clipped)
 103     private boolean opened = false;
 104     // flag indicating if the starting point's cap is done
 105     private boolean capStart = false;
 106     // flag indicating to monotonize curves
 107     private boolean monotonize;
 108 
 109     private boolean subdivide = DO_CLIP_SUBDIVIDER;
 110     private final CurveClipSplitter curveSplitter;
 111 
 112     /**
 113      * Constructs a <code>Stroker</code>.
 114      * @param rdrCtx per-thread renderer context
 115      */
 116     Stroker(final RendererContext rdrCtx) {
 117         this.rdrCtx = rdrCtx;
 118 
 119         this.reverse = (rdrCtx.stats != null) ?
 120             new PolyStack(rdrCtx,
 121                     rdrCtx.stats.stat_str_polystack_types,
 122                     rdrCtx.stats.stat_str_polystack_curves,
 123                     rdrCtx.stats.hist_str_polystack_curves,
 124                     rdrCtx.stats.stat_array_str_polystack_curves,
 125                     rdrCtx.stats.stat_array_str_polystack_types)
 126             : new PolyStack(rdrCtx);
 127 
 128         this.curve = rdrCtx.curve;
 129         this.curveSplitter = rdrCtx.curveClipSplitter;
 130     }
 131 
 132     /**
 133      * Inits the <code>Stroker</code>.
 134      *
 135      * @param pc2d an output <code>PathConsumer2D</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      * @param scale scaling factor applied to clip boundaries
 145      * @param subdivideCurves true to indicate to subdivide curves, false if dasher does
 146      * @return this instance
 147      */
 148     Stroker init(final PathConsumer2D pc2d,
 149                  final float lineWidth,
 150                  final int capStyle,
 151                  final int joinStyle,
 152                  final float miterLimit,
 153                  final float scale,
 154                  final boolean subdivideCurves)
 155     {
 156         this.out = pc2d;
 157 
 158         this.lineWidth2 = lineWidth / 2.0f;
 159         this.invHalfLineWidth2Sq = 1.0f / (2.0f * lineWidth2 * lineWidth2);
 160         this.monotonize = subdivideCurves;
 161 
 162         this.capStyle = capStyle;
 163         this.joinStyle = joinStyle;
 164 
 165         final float limit = miterLimit * lineWidth2;
 166         this.miterLimitSq = limit * limit;
 167 
 168         this.prev = CLOSE;
 169 
 170         rdrCtx.stroking = 1;
 171 
 172         if (rdrCtx.doClip) {
 173             // Adjust the clipping rectangle with the stroker margin (miter limit, width)
 174             float rdrOffX = 0.0f, rdrOffY = 0.0f;
 175             float margin = lineWidth2;
 176 
 177             if (capStyle == CAP_SQUARE) {
 178                 margin *= SQRT_2;
 179             }
 180             if ((joinStyle == JOIN_MITER) && (margin < limit)) {
 181                 margin = limit;
 182             }
 183             if (scale != 1.0f) {
 184                 margin *= scale;
 185                 rdrOffX = scale * Renderer.RDR_OFFSET_X;
 186                 rdrOffY = scale * Renderer.RDR_OFFSET_Y;
 187             }
 188             // add a small rounding error:
 189             margin += 1e-3f;
 190 
 191             // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY
 192             // adjust clip rectangle (ymin, ymax, xmin, xmax):
 193             final float[] _clipRect = rdrCtx.clipRect;
 194             _clipRect[0] -= margin - rdrOffY;
 195             _clipRect[1] += margin + rdrOffY;
 196             _clipRect[2] -= margin - rdrOffX;
 197             _clipRect[3] += margin + rdrOffX;
 198             this.clipRect = _clipRect;
 199 
 200             // initialize curve splitter here for stroker & dasher:
 201             if (DO_CLIP_SUBDIVIDER) {
 202                 subdivide = subdivideCurves;
 203                 // adjust padded clip rectangle:
 204                 curveSplitter.init();
 205             } else {
 206                 subdivide = false;
 207             }
 208         } else {
 209             this.clipRect = null;
 210             this.cOutCode = 0;
 211             this.sOutCode = 0;
 212         }
 213         return this; // fluent API
 214     }
 215 
 216     void disableClipping() {
 217         this.clipRect = null;
 218         this.cOutCode = 0;
 219         this.sOutCode = 0;
 220     }
 221 
 222     /**
 223      * Disposes this stroker:
 224      * clean up before reusing this instance
 225      */
 226     void dispose() {
 227         reverse.dispose();
 228 
 229         opened   = false;
 230         capStart = false;
 231 
 232         if (DO_CLEAN_DIRTY) {
 233             // Force zero-fill dirty arrays:
 234             Arrays.fill(offset0, 0.0f);
 235             Arrays.fill(offset1, 0.0f);
 236             Arrays.fill(offset2, 0.0f);
 237             Arrays.fill(miter, 0.0f);

 238             Arrays.fill(lp, 0.0f);
 239             Arrays.fill(rp, 0.0f);

 240         }
 241     }
 242 
 243     private static void computeOffset(final float lx, final float ly,
 244                                       final float w, final float[] m)
 245     {
 246         float len = lx*lx + ly*ly;
 247         if (len == 0.0f) {
 248             m[0] = 0.0f;
 249             m[1] = 0.0f;
 250         } else {
 251             len = (float) Math.sqrt(len);
 252             m[0] =  (ly * w) / len;
 253             m[1] = -(lx * w) / len;
 254         }
 255     }
 256 
 257     // Returns true if the vectors (dx1, dy1) and (dx2, dy2) are
 258     // clockwise (if dx1,dy1 needs to be rotated clockwise to close
 259     // the smallest angle between it and dx2,dy2).
 260     // This is equivalent to detecting whether a point q is on the right side
 261     // of a line passing through points p1, p2 where p2 = p1+(dx1,dy1) and
 262     // q = p2+(dx2,dy2), which is the same as saying p1, p2, q are in a
 263     // clockwise order.
 264     // NOTE: "clockwise" here assumes coordinates with 0,0 at the bottom left.
 265     private static boolean isCW(final float dx1, final float dy1,
 266                                 final float dx2, final float dy2)
 267     {
 268         return dx1 * dy2 <= dy1 * dx2;
 269     }
 270 
 271     private void mayDrawRoundJoin(float cx, float cy,
 272                                   float omx, float omy,
 273                                   float mx, float my,
 274                                   boolean rev)
 275     {
 276         if ((omx == 0.0f && omy == 0.0f) || (mx == 0.0f && my == 0.0f)) {
 277             return;
 278         }
 279 
 280         final float domx = omx - mx;
 281         final float domy = omy - my;
 282         final float lenSq = domx*domx + domy*domy;
 283 
 284         if (lenSq < ROUND_JOIN_THRESHOLD) {
 285             return;
 286         }
 287 
 288         if (rev) {
 289             omx = -omx;
 290             omy = -omy;
 291             mx  = -mx;
 292             my  = -my;
 293         }
 294         drawRoundJoin(cx, cy, omx, omy, mx, my, rev);
 295     }
 296 
 297     private void drawRoundJoin(float cx, float cy,
 298                                float omx, float omy,
 299                                float mx, float my,
 300                                boolean rev)
 301     {
 302         // The sign of the dot product of mx,my and omx,omy is equal to the
 303         // the sign of the cosine of ext
 304         // (ext is the angle between omx,omy and mx,my).
 305         final float cosext = omx * mx + omy * my;
 306         // If it is >=0, we know that abs(ext) is <= 90 degrees, so we only
 307         // need 1 curve to approximate the circle section that joins omx,omy
 308         // and mx,my.
 309         final int numCurves = (cosext >= 0.0f) ? 1 : 2;
 310 
 311         switch (numCurves) {
 312         case 1:
 313             drawBezApproxForArc(cx, cy, omx, omy, mx, my, rev);
 314             break;


 385         emitCurveTo(x1, y1, x2, y2, x3, y3, x4, y4, rev);
 386     }
 387 
 388     private void drawRoundCap(float cx, float cy, float mx, float my) {
 389         final float Cmx = C * mx;
 390         final float Cmy = C * my;
 391         emitCurveTo(cx + mx - Cmy, cy + my + Cmx,
 392                     cx - my + Cmx, cy + mx + Cmy,
 393                     cx - my,       cy + mx);
 394         emitCurveTo(cx - my - Cmx, cy + mx - Cmy,
 395                     cx - mx - Cmy, cy - my + Cmx,
 396                     cx - mx,       cy - my);
 397     }
 398 
 399     // Return the intersection point of the lines (x0, y0) -> (x1, y1)
 400     // and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1]
 401     private static void computeMiter(final float x0, final float y0,
 402                                      final float x1, final float y1,
 403                                      final float x0p, final float y0p,
 404                                      final float x1p, final float y1p,
 405                                      final float[] m)
 406     {
 407         float x10 = x1 - x0;
 408         float y10 = y1 - y0;
 409         float x10p = x1p - x0p;
 410         float y10p = y1p - y0p;
 411 
 412         // if this is 0, the lines are parallel. If they go in the
 413         // same direction, there is no intersection so m[off] and
 414         // m[off+1] will contain infinity, so no miter will be drawn.
 415         // If they go in the same direction that means that the start of the
 416         // current segment and the end of the previous segment have the same
 417         // tangent, in which case this method won't even be involved in
 418         // miter drawing because it won't be called by drawMiter (because
 419         // (mx == omx && my == omy) will be true, and drawMiter will return
 420         // immediately).
 421         float den = x10*y10p - x10p*y10;
 422         float t = x10p*(y0-y0p) - y10p*(x0-x0p);
 423         t /= den;
 424         m[0] = x0 + t*x10;
 425         m[1] = y0 + t*y10;
 426     }
 427 
 428     // Return the intersection point of the lines (x0, y0) -> (x1, y1)
 429     // and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1]
 430     private static void safeComputeMiter(final float x0, final float y0,
 431                                          final float x1, final float y1,
 432                                          final float x0p, final float y0p,
 433                                          final float x1p, final float y1p,
 434                                          final float[] m)
 435     {
 436         float x10 = x1 - x0;
 437         float y10 = y1 - y0;
 438         float x10p = x1p - x0p;
 439         float y10p = y1p - y0p;
 440 
 441         // if this is 0, the lines are parallel. If they go in the
 442         // same direction, there is no intersection so m[off] and
 443         // m[off+1] will contain infinity, so no miter will be drawn.
 444         // If they go in the same direction that means that the start of the
 445         // current segment and the end of the previous segment have the same
 446         // tangent, in which case this method won't even be involved in
 447         // miter drawing because it won't be called by drawMiter (because
 448         // (mx == omx && my == omy) will be true, and drawMiter will return
 449         // immediately).
 450         float den = x10*y10p - x10p*y10;
 451         if (den == 0.0f) {
 452             m[2] = (x0 + x0p) / 2.0f;
 453             m[3] = (y0 + y0p) / 2.0f;
 454         } else {
 455             float t = x10p*(y0-y0p) - y10p*(x0-x0p);
 456             t /= den;
 457             m[2] = x0 + t*x10;
 458             m[3] = y0 + t*y10;
 459         }




 460     }
 461 
 462     private void drawMiter(final float pdx, final float pdy,
 463                            final float x0, final float y0,
 464                            final float dx, final float dy,
 465                            float omx, float omy,
 466                            float mx, float my,
 467                            boolean rev)
 468     {
 469         if ((mx == omx && my == omy) ||
 470             (pdx == 0.0f && pdy == 0.0f) ||
 471             (dx == 0.0f && dy == 0.0f))
 472         {
 473             return;
 474         }
 475 
 476         if (rev) {
 477             omx = -omx;
 478             omy = -omy;
 479             mx  = -mx;
 480             my  = -my;
 481         }
 482 
 483         computeMiter((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy,
 484                      (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my, miter);

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

 548 
 549             // Should clip
 550             final int orCode = (outcode0 | outcode1);
 551             if (orCode != 0) {
 552                 final int sideCode = outcode0 & outcode1;
 553 
 554                 // basic rejection criteria:
 555                 if (sideCode == 0) {
 556                     // ovelap clip:
 557                     if (subdivide) {
 558                         // avoid reentrance
 559                         subdivide = false;
 560                         // subdivide curve => callback with subdivided parts:
 561                         boolean ret = curveSplitter.splitLine(cx0, cy0, x1, y1,
 562                                                               orCode, this);
 563                         // reentrance is done:
 564                         subdivide = true;
 565                         if (ret) {
 566                             return;
 567                         }
 568                     }
 569                     // already subdivided so render it
 570                 } else {
 571                     this.cOutCode = outcode1;
 572                     _moveTo(x1, y1, outcode0);
 573                     opened = true;
 574                     return;
 575                 }
 576             }
 577 
 578             this.cOutCode = outcode1;
 579         }
 580 
 581         float dx = x1 - cx0;
 582         float dy = y1 - cy0;
 583         if (dx == 0.0f && dy == 0.0f) {
 584             dx = 1.0f;
 585         }
 586         computeOffset(dx, dy, lineWidth2, offset0);
 587         final float mx = offset0[0];
 588         final float my = offset0[1];
 589 
 590         drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my, outcode0);
 591 
 592         emitLineTo(cx0 + mx, cy0 + my);
 593         emitLineTo( x1 + mx,  y1 + my);
 594 
 595         emitLineToRev(cx0 - mx, cy0 - my);
 596         emitLineToRev( x1 - mx,  y1 - my);
 597 
 598         this.prev = DRAWING_OP_TO;


 780                           float x0, float y0,
 781                           float dx, float dy,
 782                           float omx, float omy,
 783                           float mx, float my,
 784                           final int outcode)
 785     {
 786         if (prev != DRAWING_OP_TO) {
 787             emitMoveTo(x0 + mx, y0 + my);
 788             if (!opened) {
 789                 this.sdx = dx;
 790                 this.sdy = dy;
 791                 this.smx = mx;
 792                 this.smy = my;
 793             }
 794         } else {
 795             final boolean cw = isCW(pdx, pdy, dx, dy);
 796             if (outcode == 0) {
 797                 if (joinStyle == JOIN_MITER) {
 798                     drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
 799                 } else if (joinStyle == JOIN_ROUND) {
 800                     mayDrawRoundJoin(x0, y0, omx, omy, mx, my, cw);



 801                 }
 802             }
 803             emitLineTo(x0, y0, !cw);
 804         }
 805         prev = DRAWING_OP_TO;
 806     }
 807 
 808     private static boolean within(final float x1, final float y1,
 809                                   final float x2, final float y2,
 810                                   final float err)
 811     {
 812         assert err > 0 : "";
 813         // compare taxicab distance. ERR will always be small, so using
 814         // true distance won't give much benefit
 815         return (Helpers.within(x1, x2, err) && // we want to avoid calling Math.abs
 816                 Helpers.within(y1, y2, err));  // this is just as good.
 817     }
 818 
 819     private void getLineOffsets(final float x1, final float y1,
 820                                 final float x2, final float y2,
 821                                 final float[] left, final float[] right)
 822     {
 823         computeOffset(x2 - x1, y2 - y1, lineWidth2, offset0);
 824         final float mx = offset0[0];
 825         final float my = offset0[1];
 826         left[0] = x1 + mx;
 827         left[1] = y1 + my;
 828         left[2] = x2 + mx;
 829         left[3] = y2 + my;
 830 
 831         right[0] = x1 - mx;
 832         right[1] = y1 - my;
 833         right[2] = x2 - mx;
 834         right[3] = y2 - my;
 835     }
 836 
 837     private int computeOffsetCubic(final float[] pts, final int off,
 838                                    final float[] leftOff,
 839                                    final float[] rightOff)
 840     {
 841         // if p1=p2 or p3=p4 it means that the derivative at the endpoint
 842         // vanishes, which creates problems with computeOffset. Usually
 843         // this happens when this stroker object is trying to widen
 844         // a curve with a cusp. What happens is that curveTo splits
 845         // the input curve at the cusp, and passes it to this function.
 846         // because of inaccuracies in the splitting, we consider points
 847         // equal if they're very close to each other.
 848         final float x1 = pts[off    ], y1 = pts[off + 1];
 849         final float x2 = pts[off + 2], y2 = pts[off + 3];
 850         final float x3 = pts[off + 4], y3 = pts[off + 5];
 851         final float x4 = pts[off + 6], y4 = pts[off + 7];
 852 
 853         float dx4 = x4 - x3;
 854         float dy4 = y4 - y3;
 855         float dx1 = x2 - x1;
 856         float dy1 = y2 - y1;
 857 
 858         // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4,
 859         // in which case ignore if p1 == p2
 860         final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0f * Math.ulp(y2));
 861         final boolean p3eqp4 = within(x3, y3, x4, y4, 6.0f * Math.ulp(y4));
 862 
 863         if (p1eqp2 && p3eqp4) {
 864             getLineOffsets(x1, y1, x4, y4, leftOff, rightOff);
 865             return 4;
 866         } else if (p1eqp2) {
 867             dx1 = x3 - x1;
 868             dy1 = y3 - y1;
 869         } else if (p3eqp4) {
 870             dx4 = x4 - x2;
 871             dy4 = y4 - y2;
 872         }
 873 
 874         // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line
 875         float dotsq = (dx1 * dx4 + dy1 * dy4);
 876         dotsq *= dotsq;
 877         float l1sq = dx1 * dx1 + dy1 * dy1, l4sq = dx4 * dx4 + dy4 * dy4;
 878 
 879         if (Helpers.within(dotsq, l1sq * l4sq, 4.0f * Math.ulp(dotsq))) {
 880             getLineOffsets(x1, y1, x4, y4, leftOff, rightOff);
 881             return 4;
 882         }
 883 
 884 //      What we're trying to do in this function is to approximate an ideal
 885 //      offset curve (call it I) of the input curve B using a bezier curve Bp.
 886 //      The constraints I use to get the equations are:
 887 //
 888 //      1. The computed curve Bp should go through I(0) and I(1). These are
 889 //      x1p, y1p, x4p, y4p, which are p1p and p4p. We still need to find
 890 //      4 variables: the x and y components of p2p and p3p (i.e. x2p, y2p, x3p, y3p).
 891 //
 892 //      2. Bp should have slope equal in absolute value to I at the endpoints. So,
 893 //      (by the way, the operator || in the comments below means "aligned with".
 894 //      It is defined on vectors, so when we say I'(0) || Bp'(0) we mean that
 895 //      vectors I'(0) and Bp'(0) are aligned, which is the same as saying
 896 //      that the tangent lines of I and Bp at 0 are parallel. Mathematically
 897 //      this means (I'(t) || Bp'(t)) <==> (I'(t) = c * Bp'(t)) where c is some
 898 //      nonzero constant.)


 972         two_pi_m_p1_m_p4x = 2.0f * xi - x1p - x4p;
 973         two_pi_m_p1_m_p4y = 2.0f * yi - y1p - y4p;
 974         c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y);
 975         c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x);
 976 
 977         x2p = x1p + c1*dx1;
 978         y2p = y1p + c1*dy1;
 979         x3p = x4p + c2*dx4;
 980         y3p = y4p + c2*dy4;
 981 
 982         rightOff[0] = x1p; rightOff[1] = y1p;
 983         rightOff[2] = x2p; rightOff[3] = y2p;
 984         rightOff[4] = x3p; rightOff[5] = y3p;
 985         rightOff[6] = x4p; rightOff[7] = y4p;
 986         return 8;
 987     }
 988 
 989     // compute offset curves using bezier spline through t=0.5 (i.e.
 990     // ComputedCurve(0.5) == IdealParallelCurve(0.5))
 991     // return the kind of curve in the right and left arrays.
 992     private int computeOffsetQuad(final float[] pts, final int off,
 993                                   final float[] leftOff,
 994                                   final float[] rightOff)
 995     {
 996         final float x1 = pts[off    ], y1 = pts[off + 1];
 997         final float x2 = pts[off + 2], y2 = pts[off + 3];
 998         final float x3 = pts[off + 4], y3 = pts[off + 5];
 999 
1000         final float dx3 = x3 - x2;
1001         final float dy3 = y3 - y2;
1002         final float dx1 = x2 - x1;
1003         final float dy1 = y2 - y1;
1004 
1005         // if p1=p2 or p3=p4 it means that the derivative at the endpoint
1006         // vanishes, which creates problems with computeOffset. Usually
1007         // this happens when this stroker object is trying to widen
1008         // a curve with a cusp. What happens is that curveTo splits
1009         // the input curve at the cusp, and passes it to this function.
1010         // because of inaccuracies in the splitting, we consider points
1011         // equal if they're very close to each other.
1012 
1013         // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4,
1014         // in which case ignore.
1015         final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0f * Math.ulp(y2));
1016         final boolean p2eqp3 = within(x2, y2, x3, y3, 6.0f * Math.ulp(y3));
1017 
1018         if (p1eqp2 || p2eqp3) {
1019             getLineOffsets(x1, y1, x3, y3, leftOff, rightOff);
1020             return 4;
1021         }
1022 
1023         // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line
1024         float dotsq = (dx1 * dx3 + dy1 * dy3);
1025         dotsq *= dotsq;
1026         float l1sq = dx1 * dx1 + dy1 * dy1, l3sq = dx3 * dx3 + dy3 * dy3;
1027 
1028         if (Helpers.within(dotsq, l1sq * l3sq, 4.0f * Math.ulp(dotsq))) {
1029             getLineOffsets(x1, y1, x3, y3, leftOff, rightOff);
1030             return 4;
1031         }
1032 
1033         // this computes the offsets at t=0, 0.5, 1, using the property that
1034         // for any bezier curve the vectors p2-p1 and p4-p3 are parallel to
1035         // the (dx/dt, dy/dt) vectors at the endpoints.
1036         computeOffset(dx1, dy1, lineWidth2, offset0);
1037         computeOffset(dx3, dy3, lineWidth2, offset1);
1038 
1039         float x1p = x1 + offset0[0]; // start
1040         float y1p = y1 + offset0[1]; // point
1041         float x3p = x3 + offset1[0]; // end
1042         float y3p = y3 + offset1[1]; // point
1043         safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, leftOff);
1044         leftOff[0] = x1p; leftOff[1] = y1p;
1045         leftOff[4] = x3p; leftOff[5] = y3p;
1046 
1047         x1p = x1 - offset0[0]; y1p = y1 - offset0[1];
1048         x3p = x3 - offset1[0]; y3p = y3 - offset1[1];
1049         safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, rightOff);
1050         rightOff[0] = x1p; rightOff[1] = y1p;
1051         rightOff[4] = x3p; rightOff[5] = y3p;
1052         return 6;
1053     }
1054 



























































1055     @Override
1056     public void curveTo(final float x1, final float y1,
1057                         final float x2, final float y2,
1058                         final float x3, final float y3)
1059     {
1060         final int outcode0 = this.cOutCode;
1061 
1062         if (clipRect != null) {
1063             final int outcode1 = Helpers.outcode(x1, y1, clipRect);
1064             final int outcode2 = Helpers.outcode(x2, y2, clipRect);
1065             final int outcode3 = Helpers.outcode(x3, y3, clipRect);

1066 
1067             // Should clip
1068             final int orCode = (outcode0 | outcode1 | outcode2 | outcode3);
1069             if (orCode != 0) {
1070                 final int sideCode = outcode0 & outcode1 & outcode2 & outcode3;
1071 
1072                 // basic rejection criteria:
1073                 if (sideCode == 0) {
1074                     // ovelap clip:
1075                     if (subdivide) {
1076                         // avoid reentrance
1077                         subdivide = false;
1078                         // subdivide curve => callback with subdivided parts:
1079                         boolean ret = curveSplitter.splitCurve(cx0, cy0, x1, y1,
1080                                                                x2, y2, x3, y3,
1081                                                                orCode, this);
1082                         // reentrance is done:
1083                         subdivide = true;
1084                         if (ret) {
1085                             return;
1086                         }
1087                     }
1088                     // already subdivided so render it
1089                 } else {
1090                     this.cOutCode = outcode3;
1091                     _moveTo(x3, y3, outcode0);
1092                     opened = true;
1093                     return;
1094                 }
1095             }

1096 
1097             this.cOutCode = outcode3;
1098         }
1099         _curveTo(x1, y1, x2, y2, x3, y3, outcode0);
1100     }


1101 
1102     private void _curveTo(final float x1, final float y1,
1103                           final float x2, final float y2,
1104                           final float x3, final float y3,
1105                           final int outcode0)
1106     {
1107         // need these so we can update the state at the end of this method
1108         float dxs = x1 - cx0;
1109         float dys = y1 - cy0;
1110         float dxf = x3 - x2;
1111         float dyf = y3 - y2;
1112 
1113         if ((dxs == 0.0f) && (dys == 0.0f)) {
1114             dxs = x2 - cx0;
1115             dys = y2 - cy0;
1116             if ((dxs == 0.0f) && (dys == 0.0f)) {
1117                 dxs = x3 - cx0;
1118                 dys = y3 - cy0;
1119             }
1120         }
1121         if ((dxf == 0.0f) && (dyf == 0.0f)) {
1122             dxf = x3 - x1;
1123             dyf = y3 - y1;
1124             if ((dxf == 0.0f) && (dyf == 0.0f)) {
1125                 dxf = x3 - cx0;
1126                 dyf = y3 - cy0;



1127             }
1128         }
1129         if ((dxs == 0.0f) && (dys == 0.0f)) {
1130             // this happens if the "curve" is just a point
1131             // fix outcode0 for lineTo() call:
1132             if (clipRect != null) {
1133                 this.cOutCode = outcode0;
1134             }
1135             lineTo(cx0, cy0);
1136             return;
1137         }
1138 
1139         // if these vectors are too small, normalize them, to avoid future
1140         // precision problems.
1141         if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) {
1142             final float len = (float)Math.sqrt(dxs * dxs + dys * dys);
1143             dxs /= len;
1144             dys /= len;
1145         }
1146         if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) {
1147             final float len = (float)Math.sqrt(dxf * dxf + dyf * dyf);
1148             dxf /= len;
1149             dyf /= len;
1150         }
1151 
1152         computeOffset(dxs, dys, lineWidth2, offset0);
1153         drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
1154 
1155         int nSplits = 0;
1156         final float[] mid;
1157         final float[] l = lp;
1158 
1159         if (monotonize) {
1160             // monotonize curve:
1161             final CurveBasicMonotonizer monotonizer
1162                 = rdrCtx.monotonizer.curve(cx0, cy0, x1, y1, x2, y2, x3, y3);



1163 
1164             nSplits = monotonizer.nbSplits;
1165             mid = monotonizer.middle;
1166         } else {
1167             // use left instead:
1168             mid = l;
1169             mid[0] = cx0; mid[1] = cy0;
1170             mid[2] = x1;  mid[3] = y1;
1171             mid[4] = x2;  mid[5] = y2;
1172             mid[6] = x3;  mid[7] = y3;
1173         }
1174         final float[] r = rp;
1175 
1176         int kind = 0;
1177         for (int i = 0, off = 0; i <= nSplits; i++, off += 6) {
1178             kind = computeOffsetCubic(mid, off, l, r);
1179 
1180             emitLineTo(l[0], l[1]);
1181 
1182             switch(kind) {
1183             case 8:
1184                 emitCurveTo(l[2], l[3], l[4], l[5], l[6], l[7]);
1185                 emitCurveToRev(r[0], r[1], r[2], r[3], r[4], r[5]);
1186                 break;
1187             case 4:
1188                 emitLineTo(l[2], l[3]);
1189                 emitLineToRev(r[0], r[1]);
1190                 break;
1191             default:
1192             }
1193             emitLineToRev(r[kind - 2], r[kind - 1]);
1194         }
1195 
1196         this.prev = DRAWING_OP_TO;
1197         this.cx0 = x3;
1198         this.cy0 = y3;
1199         this.cdx = dxf;
1200         this.cdy = dyf;
1201         this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
1202         this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f;
1203     }
1204 
1205     @Override
1206     public void quadTo(final float x1, final float y1,
1207                        final float x2, final float y2)
1208     {
1209         final int outcode0 = this.cOutCode;
1210 
1211         if (clipRect != null) {
1212             final int outcode1 = Helpers.outcode(x1, y1, clipRect);
1213             final int outcode2 = Helpers.outcode(x2, y2, clipRect);




1214 
1215             // Should clip
1216             final int orCode = (outcode0 | outcode1 | outcode2);
1217             if (orCode != 0) {
1218                 final int sideCode = outcode0 & outcode1 & outcode2;
1219 
1220                 // basic rejection criteria:
1221                 if (sideCode == 0) {
1222                     // ovelap clip:
1223                     if (subdivide) {
1224                         // avoid reentrance
1225                         subdivide = false;
1226                         // subdivide curve => call lineTo() with subdivided curves:
1227                         boolean ret = curveSplitter.splitQuad(cx0, cy0, x1, y1,
1228                                                               x2, y2, orCode, this);
1229                         // reentrance is done:
1230                         subdivide = true;
1231                         if (ret) {
1232                             return;
1233                         }
1234                     }
1235                     // already subdivided so render it
1236                 } else {
1237                     this.cOutCode = outcode2;
1238                     _moveTo(x2, y2, outcode0);
1239                     opened = true;
1240                     return;
1241                 }
1242             }

1243 
1244             this.cOutCode = outcode2;
1245         }
1246         _quadTo(x1, y1, x2, y2, outcode0);
1247     }

1248 
1249     private void _quadTo(final float x1, final float y1,
1250                           final float x2, final float y2,
1251                           final int outcode0)
1252     {
1253         // need these so we can update the state at the end of this method
1254         float dxs = x1 - cx0;
1255         float dys = y1 - cy0;
1256         float dxf = x2 - x1;
1257         float dyf = y2 - y1;
1258 
1259         if (((dxs == 0.0f) && (dys == 0.0f)) || ((dxf == 0.0f) && (dyf == 0.0f))) {
1260             dxs = dxf = x2 - cx0;
1261             dys = dyf = y2 - cy0;
1262         }
1263         if ((dxs == 0.0f) && (dys == 0.0f)) {
1264             // this happens if the "curve" is just a point
1265             // fix outcode0 for lineTo() call:
1266             if (clipRect != null) {
1267                 this.cOutCode = outcode0;
1268             }
1269             lineTo(cx0, cy0);
1270             return;
1271         }
1272         // if these vectors are too small, normalize them, to avoid future
1273         // precision problems.
1274         if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) {
1275             final float len = (float)Math.sqrt(dxs * dxs + dys * dys);
1276             dxs /= len;
1277             dys /= len;
1278         }
1279         if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) {
1280             final float len = (float)Math.sqrt(dxf * dxf + dyf * dyf);
1281             dxf /= len;
1282             dyf /= len;
1283         }

1284         computeOffset(dxs, dys, lineWidth2, offset0);
1285         drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
1286 
1287         int nSplits = 0;
1288         final float[] mid;
1289         final float[] l = lp;
1290 
1291         if (monotonize) {
1292             // monotonize quad:
1293             final CurveBasicMonotonizer monotonizer
1294                 = rdrCtx.monotonizer.quad(cx0, cy0, x1, y1, x2, y2);



1295 
1296             nSplits = monotonizer.nbSplits;
1297             mid = monotonizer.middle;
1298         } else {
1299             // use left instead:
1300             mid = l;
1301             mid[0] = cx0; mid[1] = cy0;
1302             mid[2] = x1;  mid[3] = y1;
1303             mid[4] = x2;  mid[5] = y2;
1304         }
1305         final float[] r = rp;
1306 
1307         int kind = 0;
1308         for (int i = 0, off = 0; i <= nSplits; i++, off += 4) {
1309             kind = computeOffsetQuad(mid, off, l, r);
1310 
1311             emitLineTo(l[0], l[1]);
1312 
1313             switch(kind) {
1314             case 6:
1315                 emitQuadTo(l[2], l[3], l[4], l[5]);
1316                 emitQuadToRev(r[0], r[1], r[2], r[3]);
1317                 break;
1318             case 4:
1319                 emitLineTo(l[2], l[3]);
1320                 emitLineToRev(r[0], r[1]);
1321                 break;
1322             default:
1323             }
1324             emitLineToRev(r[kind - 2], r[kind - 1]);
1325         }
1326 
1327         this.prev = DRAWING_OP_TO;
1328         this.cx0 = x2;
1329         this.cy0 = y2;
1330         this.cdx = dxf;
1331         this.cdy = dyf;
1332         this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
1333         this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f;
1334     }
1335 
1336     @Override public long getNativeConsumer() {
1337         throw new InternalError("Stroker doesn't use a native consumer");
1338     }
1339 }
< prev index next >