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 350 // define the bezier curve we're computing. 351 // It is computed using the constraints that P1-P0 and P3-P2 are parallel 352 // to the arc tangents at the endpoints, and that |P1-P0|=|P3-P2|. 353 float cv = (float) ((4.0d / 3.0d) * Math.sqrt(0.5d - cosext2) / 354 (1.0d + Math.sqrt(cosext2 + 0.5d))); 355 // if clockwise, we need to negate cv. 356 if (rev) { // rev is equivalent to isCW(omx, omy, mx, my) 357 cv = -cv; 358 } 359 final float x1 = cx + omx; 360 final float y1 = cy + omy; 361 final float x2 = x1 - cv * omy; 362 final float y2 = y1 + cv * omx; 363 364 final float x4 = cx + mx; 365 final float y4 = cy + my; 366 final float x3 = x4 + cv * my; 367 final float y3 = y4 - cv * mx; 368 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; 559 this.cx0 = x1; 560 this.cy0 = y1; 561 this.cdx = dx; 562 this.cdy = dy; 563 this.cmx = mx; 564 this.cmy = my; 565 } 566 567 @Override 568 public void closePath() { 569 // distinguish empty path at all vs opened path ? 570 if (prev != DRAWING_OP_TO && !opened) { 571 if (prev == CLOSE) { 572 return; 573 } 574 emitMoveTo(cx0, cy0 - lineWidth2); 575 576 this.sdx = 1.0f; 577 this.sdy = 0.0f; 578 this.cdx = 1.0f; 579 this.cdy = 0.0f; 580 581 this.smx = 0.0f; 582 this.smy = -lineWidth2; 583 this.cmx = 0.0f; 584 this.cmy = -lineWidth2; 585 586 finish(cOutCode); 587 return; 588 } 589 590 // basic acceptance criteria 591 if ((sOutCode & cOutCode) == 0) { 592 if (cx0 != sx0 || cy0 != sy0) { 593 lineTo(sx0, sy0, true); 594 } 595 596 drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy, sOutCode); 597 598 emitLineTo(sx0 + smx, sy0 + smy); 599 600 if (opened) { 601 emitLineTo(sx0 - smx, sy0 - smy); 602 } else { 603 emitMoveTo(sx0 - smx, sy0 - smy); 604 } 605 } 606 // Ignore caps like finish(false) 607 emitReverse(); 608 609 this.prev = CLOSE; 610 611 if (opened) { 612 // do not emit close 613 opened = false; 614 } else { 615 emitClose(); 616 } 617 } 618 619 private void emitReverse() { 620 reverse.popAll(out); 621 } 622 623 @Override 624 public void pathDone() { 625 if (prev == DRAWING_OP_TO) { 626 finish(cOutCode); 627 } 628 629 out.pathDone(); 630 631 // this shouldn't matter since this object won't be used 632 // after the call to this method. 633 this.prev = CLOSE; 634 635 // Dispose this instance: 636 dispose(); 637 } 638 639 private void finish(final int outcode) { 640 // Problem: impossible to guess if the path will be closed in advance 641 // i.e. if caps must be drawn or not ? 642 // Solution: use the ClosedPathDetector before Stroker to determine 643 // if the path is a closed path or not 644 if (!rdrCtx.closedPath) { 645 if (outcode == 0) { 646 // current point = end's cap: 647 if (capStyle == CAP_ROUND) { 648 drawRoundCap(cx0, cy0, cmx, cmy); 649 } else if (capStyle == CAP_SQUARE) { 650 emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy); 651 emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy); 652 } 653 } 654 emitReverse(); 655 656 if (!capStart) { 657 capStart = true; 658 659 if (sOutCode == 0) { 660 // starting point = initial cap: 661 if (capStyle == CAP_ROUND) { 662 drawRoundCap(sx0, sy0, -smx, -smy); 663 } else if (capStyle == CAP_SQUARE) { 664 emitLineTo(sx0 + smy - smx, sy0 - smx - smy); 665 emitLineTo(sx0 + smy + smx, sy0 - smx + smy); 666 } 667 } 668 } 669 } else { 670 emitReverse(); 671 } 672 emitClose(); 673 } 674 675 private void emitMoveTo(final float x0, final float y0) { 676 out.moveTo(x0, y0); 677 } 678 679 private void emitLineTo(final float x1, final float y1) { 680 out.lineTo(x1, y1); 681 } 682 683 private void emitLineToRev(final float x1, final float y1) { 684 reverse.pushLine(x1, y1); 685 } 686 687 private void emitLineTo(final float x1, final float y1, 688 final boolean rev) 689 { 690 if (rev) { 691 emitLineToRev(x1, y1); 692 } else { 693 emitLineTo(x1, y1); 694 } 695 } 696 697 private void emitQuadTo(final float x1, final float y1, 698 final float x2, final float y2) 699 { 700 out.quadTo(x1, y1, x2, y2); 701 } 702 703 private void emitQuadToRev(final float x0, final float y0, 704 final float x1, final float y1) 705 { 706 reverse.pushQuad(x0, y0, x1, y1); 707 } 708 709 private void emitCurveTo(final float x1, final float y1, 710 final float x2, final float y2, 711 final float x3, final float y3) 712 { 713 out.curveTo(x1, y1, x2, y2, x3, y3); 714 } 715 716 private void emitCurveToRev(final float x0, final float y0, 717 final float x1, final float y1, 718 final float x2, final float y2) 719 { 720 reverse.pushCubic(x0, y0, x1, y1, x2, y2); 721 } 722 723 private void emitCurveTo(final float x0, final float y0, 724 final float x1, final float y1, 725 final float x2, final float y2, 726 final float x3, final float y3, final boolean rev) 727 { 728 if (rev) { 729 reverse.pushCubic(x0, y0, x1, y1, x2, y2); 730 } else { 731 out.curveTo(x1, y1, x2, y2, x3, y3); 732 } 733 } 734 735 private void emitClose() { 736 out.closePath(); 737 } 738 739 private void drawJoin(float pdx, float pdy, 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.) 857 // I'(0) || Bp'(0) and I'(1) || Bp'(1). Obviously, I'(0) || B'(0) and 858 // I'(1) || B'(1); therefore, Bp'(0) || B'(0) and Bp'(1) || B'(1). 859 // We know that Bp'(0) || (p2p-p1p) and Bp'(1) || (p4p-p3p) and the same 860 // is true for any bezier curve; therefore, we get the equations 861 // (1) p2p = c1 * (p2-p1) + p1p 862 // (2) p3p = c2 * (p4-p3) + p4p 863 // We know p1p, p4p, p2, p1, p3, and p4; therefore, this reduces the number 864 // of unknowns from 4 to 2 (i.e. just c1 and c2). 865 // To eliminate these 2 unknowns we use the following constraint: 866 // 867 // 3. Bp(0.5) == I(0.5). Bp(0.5)=(x,y) and I(0.5)=(xi,yi), and I should note 868 // that I(0.5) is *the only* reason for computing dxm,dym. This gives us 869 // (3) Bp(0.5) = (p1p + 3 * (p2p + p3p) + p4p)/8, which is equivalent to 870 // (4) p2p + p3p = (Bp(0.5)*8 - p1p - p4p) / 3 871 // We can substitute (1) and (2) from above into (4) and we get: 872 // (5) c1*(p2-p1) + c2*(p4-p3) = (Bp(0.5)*8 - p1p - p4p)/3 - p1p - p4p 873 // which is equivalent to 874 // (6) c1*(p2-p1) + c2*(p4-p3) = (4/3) * (Bp(0.5) * 2 - p1p - p4p) 875 // 876 // The right side of this is a 2D vector, and we know I(0.5), which gives us 877 // Bp(0.5), which gives us the value of the right side. 878 // The left side is just a matrix vector multiplication in disguise. It is 879 // 880 // [x2-x1, x4-x3][c1] 881 // [y2-y1, y4-y3][c2] 882 // which, is equal to 883 // [dx1, dx4][c1] 884 // [dy1, dy4][c2] 885 // At this point we are left with a simple linear system and we solve it by 886 // getting the inverse of the matrix above. Then we use [c1,c2] to compute 887 // p2p and p3p. 888 889 float x = (x1 + 3.0f * (x2 + x3) + x4) / 8.0f; 890 float y = (y1 + 3.0f * (y2 + y3) + y4) / 8.0f; 891 // (dxm,dym) is some tangent of B at t=0.5. This means it's equal to 892 // c*B'(0.5) for some constant c. 893 float dxm = x3 + x4 - x1 - x2, dym = y3 + y4 - y1 - y2; 894 895 // this computes the offsets at t=0, 0.5, 1, using the property that 896 // for any bezier curve the vectors p2-p1 and p4-p3 are parallel to 897 // the (dx/dt, dy/dt) vectors at the endpoints. 898 computeOffset(dx1, dy1, lineWidth2, offset0); 899 computeOffset(dxm, dym, lineWidth2, offset1); 900 computeOffset(dx4, dy4, lineWidth2, offset2); 901 float x1p = x1 + offset0[0]; // start 902 float y1p = y1 + offset0[1]; // point 903 float xi = x + offset1[0]; // interpolation 904 float yi = y + offset1[1]; // point 905 float x4p = x4 + offset2[0]; // end 906 float y4p = y4 + offset2[1]; // point 907 908 float invdet43 = 4.0f / (3.0f * (dx1 * dy4 - dy1 * dx4)); 909 910 float two_pi_m_p1_m_p4x = 2.0f * xi - x1p - x4p; 911 float two_pi_m_p1_m_p4y = 2.0f * yi - y1p - y4p; 912 float c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y); 913 float c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x); 914 915 float x2p, y2p, x3p, y3p; 916 x2p = x1p + c1*dx1; 917 y2p = y1p + c1*dy1; 918 x3p = x4p + c2*dx4; 919 y3p = y4p + c2*dy4; 920 921 leftOff[0] = x1p; leftOff[1] = y1p; 922 leftOff[2] = x2p; leftOff[3] = y2p; 923 leftOff[4] = x3p; leftOff[5] = y3p; 924 leftOff[6] = x4p; leftOff[7] = y4p; 925 926 x1p = x1 - offset0[0]; y1p = y1 - offset0[1]; 927 xi = xi - 2.0f * offset1[0]; yi = yi - 2.0f * offset1[1]; 928 x4p = x4 - offset2[0]; y4p = y4 - offset2[1]; 929 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 }