< prev index next >

modules/javafx.graphics/src/main/java/com/sun/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 com.sun.marlin;
  27 
  28 import java.util.Arrays;
  29 
  30 import com.sun.javafx.geom.PathConsumer2D;
  31 import com.sun.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 public 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      * @param rdrOffX renderer's coordinate offset on X axis
 145      * @param rdrOffY renderer's coordinate offset on Y axis

 146      * @return this instance
 147      */
 148     public 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                         double rdrOffX,
 155                         double rdrOffY)

 156     {
 157         this.out = pc2d;
 158 
 159         this.lineWidth2 = lineWidth / 2.0f;
 160         this.invHalfLineWidth2Sq = 1.0f / (2.0f * lineWidth2 * lineWidth2);


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









 197         } else {
 198             this.clipRect = null;
 199             this.cOutCode = 0;
 200             this.sOutCode = 0;
 201         }
 202         return this; // fluent API
 203     }
 204 






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

 269             return;
 270         }
 271 
 272         if (rev) {
 273             omx = -omx;
 274             omy = -omy;
 275             mx  = -mx;
 276             my  = -my;
 277         }
 278         drawRoundJoin(x, y, omx, omy, mx, my, rev);
 279     }
 280 
 281     private void drawRoundJoin(float cx, float cy,
 282                                float omx, float omy,
 283                                float mx, float my,
 284                                boolean rev)
 285     {
 286         // The sign of the dot product of mx,my and omx,omy is equal to the
 287         // the sign of the cosine of ext
 288         // (ext is the angle between omx,omy and mx,my).
 289         final float cosext = omx * mx + omy * my;
 290         // If it is >=0, we know that abs(ext) is <= 90 degrees, so we only
 291         // need 1 curve to approximate the circle section that joins omx,omy
 292         // and mx,my.
 293         final int numCurves = (cosext >= 0.0f) ? 1 : 2;
 294 
 295         switch (numCurves) {
 296         case 1:
 297             drawBezApproxForArc(cx, cy, omx, omy, mx, my, rev);
 298             break;
 299         case 2:
 300             // we need to split the arc into 2 arcs spanning the same angle.
 301             // The point we want will be one of the 2 intersections of the
 302             // perpendicular bisector of the chord (omx,omy)->(mx,my) and the
 303             // circle. We could find this by scaling the vector
 304             // (omx+mx, omy+my)/2 so that it has length=lineWidth2 (and thus lies
 305             // on the circle), but that can have numerical problems when the angle
 306             // between omx,omy and mx,my is close to 180 degrees. So we compute a
 307             // normal of (omx,omy)-(mx,my). This will be the direction of the
 308             // perpendicular bisector. To get one of the intersections, we just scale
 309             // this vector that its length is lineWidth2 (this works because the
 310             // perpendicular bisector goes through the origin). This scaling doesn't
 311             // have numerical problems because we know that lineWidth2 divided by
 312             // this normal's length is at least 0.5 and at most sqrt(2)/2 (because
 313             // we know the angle of the arc is > 90 degrees).
 314             float nx = my - omy, ny = omx - mx;
 315             float nlen = (float) Math.sqrt(nx*nx + ny*ny);
 316             float scale = lineWidth2/nlen;
 317             float mmx = nx * scale, mmy = ny * scale;
 318 
 319             // if (isCW(omx, omy, mx, my) != isCW(mmx, mmy, mx, my)) then we've
 320             // computed the wrong intersection so we get the other one.
 321             // The test above is equivalent to if (rev).
 322             if (rev) {
 323                 mmx = -mmx;
 324                 mmy = -mmy;
 325             }
 326             drawBezApproxForArc(cx, cy, omx, omy, mmx, mmy, rev);
 327             drawBezApproxForArc(cx, cy, mmx, mmy, mx, my, rev);
 328             break;
 329         default:
 330         }
 331     }
 332 
 333     // the input arc defined by omx,omy and mx,my must span <= 90 degrees.
 334     private void drawBezApproxForArc(final float cx, final float cy,
 335                                      final float omx, final float omy,
 336                                      final float mx, final float my,
 337                                      boolean rev)
 338     {
 339         final float cosext2 = (omx * mx + omy * my) * invHalfLineWidth2Sq;
 340 
 341         // check round off errors producing cos(ext) > 1 and a NaN below
 342         // cos(ext) == 1 implies colinear segments and an empty join anyway
 343         if (cosext2 >= 0.5f) {
 344             // just return to avoid generating a flat curve:
 345             return;
 346         }
 347 
 348         // cv is the length of P1-P0 and P2-P3 divided by the radius of the arc
 349         // (so, cv assumes the arc has radius 1). P0, P1, P2, P3 are the points that


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




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

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

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






















 538             }


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


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

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

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

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

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

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


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

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

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

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

1075         if (clipRect != null) {


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


















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





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


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









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

1197         if (clipRect != null) {

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





















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

1218 




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


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








1265         final float[] r = rp;
1266 
1267         int kind = 0;
1268         for (int i = 0, off = 0; i <= nSplits; i++, off += 4) {
1269             kind = computeOffsetQuad(mid, off, l, r);
1270 
1271             emitLineTo(l[0], l[1]);
1272 
1273             switch(kind) {
1274             case 6:
1275                 emitQuadTo(l[2], l[3], l[4], l[5]);
1276                 emitQuadToRev(r[0], r[1], r[2], r[3]);
1277                 break;
1278             case 4:
1279                 emitLineTo(l[2], l[3]);
1280                 emitLineToRev(r[0], r[1]);
1281                 break;
1282             default:
1283             }
1284             emitLineToRev(r[kind - 2], r[kind - 1]);
1285         }
1286 
1287         this.prev = DRAWING_OP_TO;
1288         this.cx0 = xf;
1289         this.cy0 = yf;
1290         this.cdx = dxf;
1291         this.cdy = dyf;
1292         this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
1293         this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f;
1294     }
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 com.sun.marlin;
  27 
  28 import java.util.Arrays;
  29 
  30 import com.sun.javafx.geom.PathConsumer2D;
  31 import com.sun.marlin.Helpers.PolyStack;
  32 import com.sun.marlin.TransformingPathConsumer2D.CurveBasicMonotonizer;
  33 import com.sun.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 public 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 rdrOffX renderer's coordinate offset on X axis
 146      * @param rdrOffY renderer's coordinate offset on Y axis
 147      * @param subdivideCurves true to indicate to subdivide curves, false if dasher does
 148      * @return this instance
 149      */
 150     public Stroker init(final PathConsumer2D pc2d,
 151                         final float lineWidth,
 152                         final int capStyle,
 153                         final int joinStyle,
 154                         final float miterLimit,
 155                         final float scale,
 156                         double rdrOffX,
 157                         double rdrOffY,
 158                         final boolean subdivideCurves)
 159     {
 160         this.out = pc2d;
 161 
 162         this.lineWidth2 = lineWidth / 2.0f;
 163         this.invHalfLineWidth2Sq = 1.0f / (2.0f * lineWidth2 * lineWidth2);
 164         this.monotonize = subdivideCurves;
 165 
 166         this.capStyle = capStyle;
 167         this.joinStyle = joinStyle;
 168 
 169         final float limit = miterLimit * lineWidth2;
 170         this.miterLimitSq = limit * limit;
 171 
 172         this.prev = CLOSE;
 173 
 174         rdrCtx.stroking = 1;
 175 
 176         if (rdrCtx.doClip) {
 177             // Adjust the clipping rectangle with the stroker margin (miter limit, width)
 178             float margin = lineWidth2;
 179 
 180             if (capStyle == CAP_SQUARE) {
 181                 margin *= SQRT_2;
 182             }
 183             if ((joinStyle == JOIN_MITER) && (margin < limit)) {
 184                 margin = limit;
 185             }
 186             if (scale != 1.0f) {
 187                 margin  *= scale;
 188                 rdrOffX *= scale;
 189                 rdrOffY *= scale;
 190             }
 191             // add a small rounding error:
 192             margin += 1e-3f;
 193 
 194             // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY
 195             // adjust clip rectangle (ymin, ymax, xmin, xmax):
 196             final float[] _clipRect = rdrCtx.clipRect;
 197             _clipRect[0] -= margin - rdrOffY;
 198             _clipRect[1] += margin + rdrOffY;
 199             _clipRect[2] -= margin - rdrOffX;
 200             _clipRect[3] += margin + rdrOffX;
 201             this.clipRect = _clipRect;
 202 
 203             // initialize curve splitter here for stroker & dasher:
 204             if (DO_CLIP_SUBDIVIDER) {
 205                 subdivide = subdivideCurves;
 206                 // adjust padded clip rectangle:
 207                 curveSplitter.init();
 208             } else {
 209                 subdivide = false;
 210             }
 211         } else {
 212             this.clipRect = null;
 213             this.cOutCode = 0;
 214             this.sOutCode = 0;
 215         }
 216         return this; // fluent API
 217     }
 218 
 219     public void disableClipping() {
 220         this.clipRect = null;
 221         this.cOutCode = 0;
 222         this.sOutCode = 0;
 223     }
 224 
 225     /**
 226      * Disposes this stroker:
 227      * clean up before reusing this instance
 228      */
 229     void dispose() {
 230         reverse.dispose();
 231 
 232         opened   = false;
 233         capStart = false;
 234 
 235         if (DO_CLEAN_DIRTY) {
 236             // Force zero-fill dirty arrays:
 237             Arrays.fill(offset0, 0.0f);
 238             Arrays.fill(offset1, 0.0f);
 239             Arrays.fill(offset2, 0.0f);
 240             Arrays.fill(miter, 0.0f);

 241             Arrays.fill(lp, 0.0f);
 242             Arrays.fill(rp, 0.0f);

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



 313             drawBezApproxForArc(cx, cy, omx, omy, mx, my, rev);
 314         } else {

 315             // we need to split the arc into 2 arcs spanning the same angle.
 316             // The point we want will be one of the 2 intersections of the
 317             // perpendicular bisector of the chord (omx,omy)->(mx,my) and the
 318             // circle. We could find this by scaling the vector
 319             // (omx+mx, omy+my)/2 so that it has length=lineWidth2 (and thus lies
 320             // on the circle), but that can have numerical problems when the angle
 321             // between omx,omy and mx,my is close to 180 degrees. So we compute a
 322             // normal of (omx,omy)-(mx,my). This will be the direction of the
 323             // perpendicular bisector. To get one of the intersections, we just scale
 324             // this vector that its length is lineWidth2 (this works because the
 325             // perpendicular bisector goes through the origin). This scaling doesn't
 326             // have numerical problems because we know that lineWidth2 divided by
 327             // this normal's length is at least 0.5 and at most sqrt(2)/2 (because
 328             // we know the angle of the arc is > 90 degrees).
 329             float nx = my - omy, ny = omx - mx;
 330             float nlen = (float) Math.sqrt(nx*nx + ny*ny);
 331             float scale = lineWidth2/nlen;
 332             float mmx = nx * scale, mmy = ny * scale;
 333 
 334             // if (isCW(omx, omy, mx, my) != isCW(mmx, mmy, mx, my)) then we've
 335             // computed the wrong intersection so we get the other one.
 336             // The test above is equivalent to if (rev).
 337             if (rev) {
 338                 mmx = -mmx;
 339                 mmy = -mmy;
 340             }
 341             drawBezApproxForArc(cx, cy, omx, omy, mmx, mmy, rev);
 342             drawBezApproxForArc(cx, cy, mmx, mmy, mx, my, rev);


 343         }
 344     }
 345 
 346     // the input arc defined by omx,omy and mx,my must span <= 90 degrees.
 347     private void drawBezApproxForArc(final float cx, final float cy,
 348                                      final float omx, final float omy,
 349                                      final float mx, final float my,
 350                                      boolean rev)
 351     {
 352         final float cosext2 = (omx * mx + omy * my) * invHalfLineWidth2Sq;
 353 
 354         // check round off errors producing cos(ext) > 1 and a NaN below
 355         // cos(ext) == 1 implies colinear segments and an empty join anyway
 356         if (cosext2 >= 0.5f) {
 357             // just return to avoid generating a flat curve:
 358             return;
 359         }
 360 
 361         // cv is the length of P1-P0 and P2-P3 divided by the radius of the arc
 362         // (so, cv assumes the arc has radius 1). P0, P1, P2, P3 are the points that


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




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

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

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


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



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


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



























































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

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

1093 
1094             this.cOutCode = outcode3;
1095         }
1096         _curveTo(x1, y1, x2, y2, x3, y3, outcode0);
1097     }


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



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



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




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



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

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



1292 
1293             nSplits = monotonizer.nbSplits;
1294             mid = monotonizer.middle;
1295         } else {
1296             // use left instead:
1297             mid = l;
1298             mid[0] = cx0; mid[1] = cy0;
1299             mid[2] = x1;  mid[3] = y1;
1300             mid[4] = x2;  mid[5] = y2;
1301         }
1302         final float[] r = rp;
1303 
1304         int kind = 0;
1305         for (int i = 0, off = 0; i <= nSplits; i++, off += 4) {
1306             kind = computeOffsetQuad(mid, off, l, r);
1307 
1308             emitLineTo(l[0], l[1]);
1309 
1310             switch(kind) {
1311             case 6:
1312                 emitQuadTo(l[2], l[3], l[4], l[5]);
1313                 emitQuadToRev(r[0], r[1], r[2], r[3]);
1314                 break;
1315             case 4:
1316                 emitLineTo(l[2], l[3]);
1317                 emitLineToRev(r[0], r[1]);
1318                 break;
1319             default:
1320             }
1321             emitLineToRev(r[kind - 2], r[kind - 1]);
1322         }
1323 
1324         this.prev = DRAWING_OP_TO;
1325         this.cx0 = x2;
1326         this.cy0 = y2;
1327         this.cdx = dxf;
1328         this.cdy = dyf;
1329         this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
1330         this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f;
1331     }

1332 }
< prev index next >