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 /** 43 * Constant value for join style. 44 */ 45 public static final int JOIN_MITER = 0; 46 47 /** 48 * Constant value for join style. 49 */ 50 public static final int JOIN_ROUND = 1; 51 52 /** 53 * Constant value for join style. 54 */ 55 public static final int JOIN_BEVEL = 2; 56 57 /** 58 * Constant value for end cap style. 59 */ 60 public static final int CAP_BUTT = 0; 61 62 /** 63 * Constant value for end cap style. 64 */ 65 public static final int CAP_ROUND = 1; 66 67 /** 68 * Constant value for end cap style. 69 */ 70 public static final int CAP_SQUARE = 2; 71 72 // pisces used to use fixed point arithmetic with 16 decimal digits. I 73 // didn't want to change the values of the constant below when I converted 74 // it to floating point, so that's why the divisions by 2^16 are there. 75 private static final float ROUND_JOIN_THRESHOLD = 1000.0f/65536.0f; 76 77 private static final float C = 0.5522847498307933f; 78 79 private static final int MAX_N_CURVES = 11; 80 81 private PathConsumer2D out; 82 83 private int capStyle; 84 private int joinStyle; 85 86 private float lineWidth2; 87 private float invHalfLineWidth2Sq; 88 89 private final float[] offset0 = new float[2]; 90 private final float[] offset1 = new float[2]; 91 private final float[] offset2 = new float[2]; 92 private final float[] miter = new float[2]; 93 private float miterLimitSq; 94 95 private int prev; 96 97 // The starting point of the path, and the slope there. 98 private float sx0, sy0, sdx, sdy; 99 // the current point and the slope there. 100 private float cx0, cy0, cdx, cdy; // c stands for current 101 // vectors that when added to (sx0,sy0) and (cx0,cy0) respectively yield the 102 // first and last points on the left parallel path. Since this path is 103 // parallel, it's slope at any point is parallel to the slope of the 104 // original path (thought they may have different directions), so these 105 // could be computed from sdx,sdy and cdx,cdy (and vice versa), but that 106 // would be error prone and hard to read, so we keep these anyway. 107 private float smx, smy, cmx, cmy; 108 109 private final PolyStack reverse; 110 111 // This is where the curve to be processed is put. We give it 112 // enough room to store all curves. 113 private final float[] middle = new float[MAX_N_CURVES * 6 + 2]; 114 private final float[] lp = new float[8]; 115 private final float[] rp = new float[8]; 116 private final float[] subdivTs = new float[MAX_N_CURVES - 1]; 117 118 // per-thread renderer context 119 final RendererContext rdrCtx; 120 121 // dirty curve 122 final Curve curve; 123 124 // Bounds of the drawing region, at pixel precision. 125 private float[] clipRect; 126 127 // the outcode of the current point 128 private int cOutCode = 0; 129 130 // the outcode of the starting point 131 private int sOutCode = 0; 132 133 // flag indicating if the path is opened (clipped) 134 private boolean opened = false; 135 // flag indicating if the starting point's cap is done 136 private boolean capStart = false; 137 138 /** 139 * Constructs a <code>Stroker</code>. 140 * @param rdrCtx per-thread renderer context 141 */ 142 Stroker(final RendererContext rdrCtx) { 143 this.rdrCtx = rdrCtx; 144 145 this.reverse = (rdrCtx.stats != null) ? 146 new PolyStack(rdrCtx, 147 rdrCtx.stats.stat_str_polystack_types, 148 rdrCtx.stats.stat_str_polystack_curves, 149 rdrCtx.stats.hist_str_polystack_curves, 150 rdrCtx.stats.stat_array_str_polystack_curves, 151 rdrCtx.stats.stat_array_str_polystack_types) 152 : new PolyStack(rdrCtx); 153 154 this.curve = rdrCtx.curve; 155 } 156 157 /** 158 * Inits the <code>Stroker</code>. 159 * 160 * @param pc2d an output <code>PathConsumer2D</code>. 161 * @param lineWidth the desired line width in pixels 162 * @param capStyle the desired end cap style, one of 163 * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or 164 * <code>CAP_SQUARE</code>. 165 * @param joinStyle the desired line join style, one of 166 * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or 167 * <code>JOIN_BEVEL</code>. 168 * @param miterLimit the desired miter limit 169 * @param scale scaling factor applied to clip boundaries 170 * @param rdrOffX renderer's coordinate offset on X axis 171 * @param rdrOffY renderer's coordinate offset on Y axis 172 * @return this instance 173 */ 174 public Stroker init(final PathConsumer2D pc2d, 175 final float lineWidth, 176 final int capStyle, 177 final int joinStyle, 178 final float miterLimit, 179 final float scale, 180 double rdrOffX, 181 double rdrOffY) 182 { 183 this.out = pc2d; 184 185 this.lineWidth2 = lineWidth / 2.0f; 186 this.invHalfLineWidth2Sq = 1.0f / (2.0f * lineWidth2 * lineWidth2); 187 this.capStyle = capStyle; 188 this.joinStyle = joinStyle; 189 190 final float limit = miterLimit * lineWidth2; 191 this.miterLimitSq = limit * limit; 192 193 this.prev = CLOSE; 194 195 rdrCtx.stroking = 1; 196 197 if (rdrCtx.doClip) { 198 // Adjust the clipping rectangle with the stroker margin (miter limit, width) 199 200 // round joins / caps: 201 final float widthLimit = 202 ((joinStyle == JOIN_ROUND) || (capStyle == CAP_ROUND)) ? C * lineWidth // why 0.55 ? 203 : lineWidth2; 204 205 float boundsMargin; 206 if (joinStyle == JOIN_MITER) { 207 boundsMargin = Math.max(widthLimit, limit); 208 } else { 209 boundsMargin = widthLimit; 210 } 211 if (scale != 1.0f) { 212 boundsMargin *= scale; 213 rdrOffX *= scale; 214 rdrOffY *= scale; 215 } 216 217 // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY 218 // adjust clip rectangle (ymin, ymax, xmin, xmax): 219 final float[] _clipRect = rdrCtx.clipRect; 220 _clipRect[0] -= boundsMargin - rdrOffY; 221 _clipRect[1] += boundsMargin + rdrOffY; 222 _clipRect[2] -= boundsMargin - rdrOffX; 223 _clipRect[3] += boundsMargin + rdrOffX; 224 this.clipRect = _clipRect; 225 } else { 226 this.clipRect = null; 227 } 228 return this; // fluent API 229 } 230 231 /** 232 * Disposes this stroker: 233 * clean up before reusing this instance 234 */ 235 void dispose() { 236 reverse.dispose(); 237 238 opened = false; 239 capStart = false; 240 241 if (DO_CLEAN_DIRTY) { 242 // Force zero-fill dirty arrays: 243 Arrays.fill(offset0, 0.0f); 244 Arrays.fill(offset1, 0.0f); 245 Arrays.fill(offset2, 0.0f); 246 Arrays.fill(miter, 0.0f); 247 Arrays.fill(middle, 0.0f); 248 Arrays.fill(lp, 0.0f); 249 Arrays.fill(rp, 0.0f); 250 Arrays.fill(subdivTs, 0.0f); 251 } 252 } 253 254 private static void computeOffset(final float lx, final float ly, 255 final float w, final float[] m) 256 { 257 float len = lx*lx + ly*ly; 258 if (len == 0.0f) { 259 m[0] = 0.0f; 260 m[1] = 0.0f; 261 } else { 262 len = (float) Math.sqrt(len); 263 m[0] = (ly * w) / len; 264 m[1] = -(lx * w) / len; 265 } 266 } 267 268 // Returns true if the vectors (dx1, dy1) and (dx2, dy2) are 269 // clockwise (if dx1,dy1 needs to be rotated clockwise to close 270 // the smallest angle between it and dx2,dy2). 271 // This is equivalent to detecting whether a point q is on the right side 272 // of a line passing through points p1, p2 where p2 = p1+(dx1,dy1) and 273 // q = p2+(dx2,dy2), which is the same as saying p1, p2, q are in a 274 // clockwise order. 275 // NOTE: "clockwise" here assumes coordinates with 0,0 at the bottom left. 276 private static boolean isCW(final float dx1, final float dy1, 277 final float dx2, final float dy2) 278 { 279 return dx1 * dy2 <= dy1 * dx2; 280 } 281 282 private void drawRoundJoin(float x, float y, 283 float omx, float omy, float mx, float my, 284 boolean rev, 285 float threshold) 286 { 287 if ((omx == 0.0f && omy == 0.0f) || (mx == 0.0f && my == 0.0f)) { 288 return; 289 } 290 291 float domx = omx - mx; 292 float domy = omy - my; 293 float len = domx*domx + domy*domy; 294 if (len < threshold) { 295 return; 296 } 297 298 if (rev) { 299 omx = -omx; 300 omy = -omy; 301 mx = -mx; 302 my = -my; 303 } 304 drawRoundJoin(x, y, omx, omy, mx, my, rev); 305 } 306 307 private void drawRoundJoin(float cx, float cy, 308 float omx, float omy, 309 float mx, float my, 310 boolean rev) 311 { 312 // The sign of the dot product of mx,my and omx,omy is equal to the 313 // the sign of the cosine of ext 314 // (ext is the angle between omx,omy and mx,my). 315 final float cosext = omx * mx + omy * my; 316 // If it is >=0, we know that abs(ext) is <= 90 degrees, so we only 317 // need 1 curve to approximate the circle section that joins omx,omy 318 // and mx,my. 319 final int numCurves = (cosext >= 0.0f) ? 1 : 2; 320 321 switch (numCurves) { 322 case 1: 323 drawBezApproxForArc(cx, cy, omx, omy, mx, my, rev); 324 break; 325 case 2: 326 // we need to split the arc into 2 arcs spanning the same angle. 327 // The point we want will be one of the 2 intersections of the 328 // perpendicular bisector of the chord (omx,omy)->(mx,my) and the 329 // circle. We could find this by scaling the vector 330 // (omx+mx, omy+my)/2 so that it has length=lineWidth2 (and thus lies 331 // on the circle), but that can have numerical problems when the angle 332 // between omx,omy and mx,my is close to 180 degrees. So we compute a 333 // normal of (omx,omy)-(mx,my). This will be the direction of the 334 // perpendicular bisector. To get one of the intersections, we just scale 335 // this vector that its length is lineWidth2 (this works because the 336 // perpendicular bisector goes through the origin). This scaling doesn't 337 // have numerical problems because we know that lineWidth2 divided by 338 // this normal's length is at least 0.5 and at most sqrt(2)/2 (because 339 // we know the angle of the arc is > 90 degrees). 340 float nx = my - omy, ny = omx - mx; 341 float nlen = (float) Math.sqrt(nx*nx + ny*ny); 342 float scale = lineWidth2/nlen; 343 float mmx = nx * scale, mmy = ny * scale; 344 345 // if (isCW(omx, omy, mx, my) != isCW(mmx, mmy, mx, my)) then we've 346 // computed the wrong intersection so we get the other one. 347 // The test above is equivalent to if (rev). 348 if (rev) { 349 mmx = -mmx; 350 mmy = -mmy; 351 } 352 drawBezApproxForArc(cx, cy, omx, omy, mmx, mmy, rev); 353 drawBezApproxForArc(cx, cy, mmx, mmy, mx, my, rev); 354 break; 355 default: 356 } 357 } 358 359 // the input arc defined by omx,omy and mx,my must span <= 90 degrees. 360 private void drawBezApproxForArc(final float cx, final float cy, 361 final float omx, final float omy, 362 final float mx, final float my, 363 boolean rev) 364 { 365 final float cosext2 = (omx * mx + omy * my) * invHalfLineWidth2Sq; 366 367 // check round off errors producing cos(ext) > 1 and a NaN below 368 // cos(ext) == 1 implies colinear segments and an empty join anyway 369 if (cosext2 >= 0.5f) { 370 // just return to avoid generating a flat curve: 371 return; 372 } 373 374 // cv is the length of P1-P0 and P2-P3 divided by the radius of the arc 375 // (so, cv assumes the arc has radius 1). P0, P1, P2, P3 are the points that 376 // define the bezier curve we're computing. 377 // It is computed using the constraints that P1-P0 and P3-P2 are parallel 378 // to the arc tangents at the endpoints, and that |P1-P0|=|P3-P2|. 379 float cv = (float) ((4.0d / 3.0d) * Math.sqrt(0.5d - cosext2) / 380 (1.0d + Math.sqrt(cosext2 + 0.5d))); 381 // if clockwise, we need to negate cv. 382 if (rev) { // rev is equivalent to isCW(omx, omy, mx, my) 383 cv = -cv; 384 } 385 final float x1 = cx + omx; 386 final float y1 = cy + omy; 387 final float x2 = x1 - cv * omy; 388 final float y2 = y1 + cv * omx; 389 390 final float x4 = cx + mx; 391 final float y4 = cy + my; 392 final float x3 = x4 + cv * my; 393 final float y3 = y4 - cv * mx; 394 395 emitCurveTo(x1, y1, x2, y2, x3, y3, x4, y4, rev); 396 } 397 398 private void drawRoundCap(float cx, float cy, float mx, float my) { 399 final float Cmx = C * mx; 400 final float Cmy = C * my; 401 emitCurveTo(cx + mx - Cmy, cy + my + Cmx, 402 cx - my + Cmx, cy + mx + Cmy, 403 cx - my, cy + mx); 404 emitCurveTo(cx - my - Cmx, cy + mx - Cmy, 405 cx - mx - Cmy, cy - my + Cmx, 406 cx - mx, cy - my); 407 } 408 409 // Return the intersection point of the lines (x0, y0) -> (x1, y1) 410 // and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1] 411 private static void computeMiter(final float x0, final float y0, 412 final float x1, final float y1, 413 final float x0p, final float y0p, 414 final float x1p, final float y1p, 415 final float[] m, int off) 416 { 417 float x10 = x1 - x0; 418 float y10 = y1 - y0; 419 float x10p = x1p - x0p; 420 float y10p = y1p - y0p; 421 422 // if this is 0, the lines are parallel. If they go in the 423 // same direction, there is no intersection so m[off] and 424 // m[off+1] will contain infinity, so no miter will be drawn. 425 // If they go in the same direction that means that the start of the 426 // current segment and the end of the previous segment have the same 427 // tangent, in which case this method won't even be involved in 428 // miter drawing because it won't be called by drawMiter (because 429 // (mx == omx && my == omy) will be true, and drawMiter will return 430 // immediately). 431 float den = x10*y10p - x10p*y10; 432 float t = x10p*(y0-y0p) - y10p*(x0-x0p); 433 t /= den; 434 m[off++] = x0 + t*x10; 435 m[off] = y0 + t*y10; 436 } 437 438 // Return the intersection point of the lines (x0, y0) -> (x1, y1) 439 // and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1] 440 private static void safeComputeMiter(final float x0, final float y0, 441 final float x1, final float y1, 442 final float x0p, final float y0p, 443 final float x1p, final float y1p, 444 final float[] m, int off) 445 { 446 float x10 = x1 - x0; 447 float y10 = y1 - y0; 448 float x10p = x1p - x0p; 449 float y10p = y1p - y0p; 450 451 // if this is 0, the lines are parallel. If they go in the 452 // same direction, there is no intersection so m[off] and 453 // m[off+1] will contain infinity, so no miter will be drawn. 454 // If they go in the same direction that means that the start of the 455 // current segment and the end of the previous segment have the same 456 // tangent, in which case this method won't even be involved in 457 // miter drawing because it won't be called by drawMiter (because 458 // (mx == omx && my == omy) will be true, and drawMiter will return 459 // immediately). 460 float den = x10*y10p - x10p*y10; 461 if (den == 0.0f) { 462 m[off++] = (x0 + x0p) / 2.0f; 463 m[off] = (y0 + y0p) / 2.0f; 464 return; 465 } 466 float t = x10p*(y0-y0p) - y10p*(x0-x0p); 467 t /= den; 468 m[off++] = x0 + t*x10; 469 m[off] = y0 + t*y10; 470 } 471 472 private void drawMiter(final float pdx, final float pdy, 473 final float x0, final float y0, 474 final float dx, final float dy, 475 float omx, float omy, float mx, float my, 476 boolean rev) 477 { 478 if ((mx == omx && my == omy) || 479 (pdx == 0.0f && pdy == 0.0f) || 480 (dx == 0.0f && dy == 0.0f)) 481 { 482 return; 483 } 484 485 if (rev) { 486 omx = -omx; 487 omy = -omy; 488 mx = -mx; 489 my = -my; 490 } 491 492 computeMiter((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy, 493 (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my, 494 miter, 0); 495 496 final float miterX = miter[0]; 497 final float miterY = miter[1]; 498 float lenSq = (miterX-x0)*(miterX-x0) + (miterY-y0)*(miterY-y0); 499 500 // If the lines are parallel, lenSq will be either NaN or +inf 501 // (actually, I'm not sure if the latter is possible. The important 502 // thing is that -inf is not possible, because lenSq is a square). 503 // For both of those values, the comparison below will fail and 504 // no miter will be drawn, which is correct. 505 if (lenSq < miterLimitSq) { 506 emitLineTo(miterX, miterY, rev); 507 } 508 } 509 510 @Override 511 public void moveTo(float x0, float y0) { 512 moveTo(x0, y0, cOutCode); 513 // update starting point: 514 this.sx0 = x0; 515 this.sy0 = y0; 516 this.sdx = 1.0f; 517 this.sdy = 0.0f; 518 this.opened = false; 519 this.capStart = false; 520 521 if (clipRect != null) { 522 final int outcode = Helpers.outcode(x0, y0, clipRect); 523 this.cOutCode = outcode; 524 this.sOutCode = outcode; 525 } 526 } 527 528 private void moveTo(float x0, float y0, 529 final int outcode) 530 { 531 if (prev == MOVE_TO) { 532 this.cx0 = x0; 533 this.cy0 = y0; 534 } else { 535 if (prev == DRAWING_OP_TO) { 536 finish(outcode); 537 } 538 this.prev = MOVE_TO; 539 this.cx0 = x0; 540 this.cy0 = y0; 541 this.cdx = 1.0f; 542 this.cdy = 0.0f; 543 } 544 } 545 546 @Override 547 public void lineTo(float x1, float y1) { 548 lineTo(x1, y1, false); 549 } 550 551 private void lineTo(float x1, float y1, boolean force) { 552 final int outcode0 = this.cOutCode; 553 if (!force && clipRect != null) { 554 final int outcode1 = Helpers.outcode(x1, y1, clipRect); 555 this.cOutCode = outcode1; 556 557 // basic rejection criteria 558 if ((outcode0 & outcode1) != 0) { 559 moveTo(x1, y1, outcode0); 560 opened = true; 561 return; 562 } 563 } 564 565 float dx = x1 - cx0; 566 float dy = y1 - cy0; 567 if (dx == 0.0f && dy == 0.0f) { 568 dx = 1.0f; 569 } 570 computeOffset(dx, dy, lineWidth2, offset0); 571 final float mx = offset0[0]; 572 final float my = offset0[1]; 573 574 drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my, outcode0); 575 576 emitLineTo(cx0 + mx, cy0 + my); 577 emitLineTo( x1 + mx, y1 + my); 578 579 emitLineToRev(cx0 - mx, cy0 - my); 580 emitLineToRev( x1 - mx, y1 - my); 581 582 this.prev = DRAWING_OP_TO; 583 this.cx0 = x1; 584 this.cy0 = y1; 585 this.cdx = dx; 586 this.cdy = dy; 587 this.cmx = mx; 588 this.cmy = my; 589 } 590 591 @Override 592 public void closePath() { 593 // distinguish empty path at all vs opened path ? 594 if (prev != DRAWING_OP_TO && !opened) { 595 if (prev == CLOSE) { 596 return; 597 } 598 emitMoveTo(cx0, cy0 - lineWidth2); 599 600 this.sdx = 1.0f; 601 this.sdy = 0.0f; 602 this.cdx = 1.0f; 603 this.cdy = 0.0f; 604 605 this.smx = 0.0f; 606 this.smy = -lineWidth2; 607 this.cmx = 0.0f; 608 this.cmy = -lineWidth2; 609 610 finish(cOutCode); 611 return; 612 } 613 614 if (sOutCode == 0) { 615 if (cx0 != sx0 || cy0 != sy0) { 616 lineTo(sx0, sy0, true); 617 } 618 619 drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy, cOutCode); 620 621 emitLineTo(sx0 + smx, sy0 + smy); 622 623 if (opened) { 624 emitLineTo(sx0 - smx, sy0 - smy); 625 } else { 626 emitMoveTo(sx0 - smx, sy0 - smy); 627 } 628 } 629 // Ignore caps like finish(false) 630 emitReverse(); 631 632 this.prev = CLOSE; 633 634 if (opened) { 635 // do not emit close 636 opened = false; 637 } else { 638 emitClose(); 639 } 640 } 641 642 private void emitReverse() { 643 reverse.popAll(out); 644 } 645 646 @Override 647 public void pathDone() { 648 if (prev == DRAWING_OP_TO) { 649 finish(cOutCode); 650 } 651 652 out.pathDone(); 653 654 // this shouldn't matter since this object won't be used 655 // after the call to this method. 656 this.prev = CLOSE; 657 658 // Dispose this instance: 659 dispose(); 660 } 661 662 private void finish(final int outcode) { 663 // Problem: impossible to guess if the path will be closed in advance 664 // i.e. if caps must be drawn or not ? 665 // Solution: use the ClosedPathDetector before Stroker to determine 666 // if the path is a closed path or not 667 if (!rdrCtx.closedPath) { 668 if (outcode == 0) { 669 // current point = end's cap: 670 if (capStyle == CAP_ROUND) { 671 drawRoundCap(cx0, cy0, cmx, cmy); 672 } else if (capStyle == CAP_SQUARE) { 673 emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy); 674 emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy); 675 } 676 } 677 emitReverse(); 678 679 if (!capStart) { 680 capStart = true; 681 682 if (sOutCode == 0) { 683 // starting point = initial cap: 684 if (capStyle == CAP_ROUND) { 685 drawRoundCap(sx0, sy0, -smx, -smy); 686 } else if (capStyle == CAP_SQUARE) { 687 emitLineTo(sx0 + smy - smx, sy0 - smx - smy); 688 emitLineTo(sx0 + smy + smx, sy0 - smx + smy); 689 } 690 } 691 } 692 } else { 693 emitReverse(); 694 } 695 emitClose(); 696 } 697 698 private void emitMoveTo(final float x0, final float y0) { 699 out.moveTo(x0, y0); 700 } 701 702 private void emitLineTo(final float x1, final float y1) { 703 out.lineTo(x1, y1); 704 } 705 706 private void emitLineToRev(final float x1, final float y1) { 707 reverse.pushLine(x1, y1); 708 } 709 710 private void emitLineTo(final float x1, final float y1, 711 final boolean rev) 712 { 713 if (rev) { 714 emitLineToRev(x1, y1); 715 } else { 716 emitLineTo(x1, y1); 717 } 718 } 719 720 private void emitQuadTo(final float x1, final float y1, 721 final float x2, final float y2) 722 { 723 out.quadTo(x1, y1, x2, y2); 724 } 725 726 private void emitQuadToRev(final float x0, final float y0, 727 final float x1, final float y1) 728 { 729 reverse.pushQuad(x0, y0, x1, y1); 730 } 731 732 private void emitCurveTo(final float x1, final float y1, 733 final float x2, final float y2, 734 final float x3, final float y3) 735 { 736 out.curveTo(x1, y1, x2, y2, x3, y3); 737 } 738 739 private void emitCurveToRev(final float x0, final float y0, 740 final float x1, final float y1, 741 final float x2, final float y2) 742 { 743 reverse.pushCubic(x0, y0, x1, y1, x2, y2); 744 } 745 746 private void emitCurveTo(final float x0, final float y0, 747 final float x1, final float y1, 748 final float x2, final float y2, 749 final float x3, final float y3, final boolean rev) 750 { 751 if (rev) { 752 reverse.pushCubic(x0, y0, x1, y1, x2, y2); 753 } else { 754 out.curveTo(x1, y1, x2, y2, x3, y3); 755 } 756 } 757 758 private void emitClose() { 759 out.closePath(); 760 } 761 762 private void drawJoin(float pdx, float pdy, 763 float x0, float y0, 764 float dx, float dy, 765 float omx, float omy, 766 float mx, float my, 767 final int outcode) 768 { 769 if (prev != DRAWING_OP_TO) { 770 emitMoveTo(x0 + mx, y0 + my); 771 if (!opened) { 772 this.sdx = dx; 773 this.sdy = dy; 774 this.smx = mx; 775 this.smy = my; 776 } 777 } else { 778 final boolean cw = isCW(pdx, pdy, dx, dy); 779 if (outcode == 0) { 780 if (joinStyle == JOIN_MITER) { 781 drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw); 782 } else if (joinStyle == JOIN_ROUND) { 783 drawRoundJoin(x0, y0, 784 omx, omy, 785 mx, my, cw, 786 ROUND_JOIN_THRESHOLD); 787 } 788 } 789 emitLineTo(x0, y0, !cw); 790 } 791 prev = DRAWING_OP_TO; 792 } 793 794 private static boolean within(final float x1, final float y1, 795 final float x2, final float y2, 796 final float ERR) 797 { 798 assert ERR > 0 : ""; 799 // compare taxicab distance. ERR will always be small, so using 800 // true distance won't give much benefit 801 return (Helpers.within(x1, x2, ERR) && // we want to avoid calling Math.abs 802 Helpers.within(y1, y2, ERR)); // this is just as good. 803 } 804 805 private void getLineOffsets(float x1, float y1, 806 float x2, float y2, 807 float[] left, float[] right) { 808 computeOffset(x2 - x1, y2 - y1, lineWidth2, offset0); 809 final float mx = offset0[0]; 810 final float my = offset0[1]; 811 left[0] = x1 + mx; 812 left[1] = y1 + my; 813 left[2] = x2 + mx; 814 left[3] = y2 + my; 815 right[0] = x1 - mx; 816 right[1] = y1 - my; 817 right[2] = x2 - mx; 818 right[3] = y2 - my; 819 } 820 821 private int computeOffsetCubic(float[] pts, final int off, 822 float[] leftOff, float[] rightOff) 823 { 824 // if p1=p2 or p3=p4 it means that the derivative at the endpoint 825 // vanishes, which creates problems with computeOffset. Usually 826 // this happens when this stroker object is trying to widen 827 // a curve with a cusp. What happens is that curveTo splits 828 // the input curve at the cusp, and passes it to this function. 829 // because of inaccuracies in the splitting, we consider points 830 // equal if they're very close to each other. 831 final float x1 = pts[off + 0], y1 = pts[off + 1]; 832 final float x2 = pts[off + 2], y2 = pts[off + 3]; 833 final float x3 = pts[off + 4], y3 = pts[off + 5]; 834 final float x4 = pts[off + 6], y4 = pts[off + 7]; 835 836 float dx4 = x4 - x3; 837 float dy4 = y4 - y3; 838 float dx1 = x2 - x1; 839 float dy1 = y2 - y1; 840 841 // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4, 842 // in which case ignore if p1 == p2 843 final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0f * Math.ulp(y2)); 844 final boolean p3eqp4 = within(x3, y3, x4, y4, 6.0f * Math.ulp(y4)); 845 if (p1eqp2 && p3eqp4) { 846 getLineOffsets(x1, y1, x4, y4, leftOff, rightOff); 847 return 4; 848 } else if (p1eqp2) { 849 dx1 = x3 - x1; 850 dy1 = y3 - y1; 851 } else if (p3eqp4) { 852 dx4 = x4 - x2; 853 dy4 = y4 - y2; 854 } 855 856 // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line 857 float dotsq = (dx1 * dx4 + dy1 * dy4); 858 dotsq *= dotsq; 859 float l1sq = dx1 * dx1 + dy1 * dy1, l4sq = dx4 * dx4 + dy4 * dy4; 860 if (Helpers.within(dotsq, l1sq * l4sq, 4.0f * Math.ulp(dotsq))) { 861 getLineOffsets(x1, y1, x4, y4, leftOff, rightOff); 862 return 4; 863 } 864 865 // What we're trying to do in this function is to approximate an ideal 866 // offset curve (call it I) of the input curve B using a bezier curve Bp. 867 // The constraints I use to get the equations are: 868 // 869 // 1. The computed curve Bp should go through I(0) and I(1). These are 870 // x1p, y1p, x4p, y4p, which are p1p and p4p. We still need to find 871 // 4 variables: the x and y components of p2p and p3p (i.e. x2p, y2p, x3p, y3p). 872 // 873 // 2. Bp should have slope equal in absolute value to I at the endpoints. So, 874 // (by the way, the operator || in the comments below means "aligned with". 875 // It is defined on vectors, so when we say I'(0) || Bp'(0) we mean that 876 // vectors I'(0) and Bp'(0) are aligned, which is the same as saying 877 // that the tangent lines of I and Bp at 0 are parallel. Mathematically 878 // this means (I'(t) || Bp'(t)) <==> (I'(t) = c * Bp'(t)) where c is some 879 // nonzero constant.) 880 // I'(0) || Bp'(0) and I'(1) || Bp'(1). Obviously, I'(0) || B'(0) and 881 // I'(1) || B'(1); therefore, Bp'(0) || B'(0) and Bp'(1) || B'(1). 882 // We know that Bp'(0) || (p2p-p1p) and Bp'(1) || (p4p-p3p) and the same 883 // is true for any bezier curve; therefore, we get the equations 884 // (1) p2p = c1 * (p2-p1) + p1p 885 // (2) p3p = c2 * (p4-p3) + p4p 886 // We know p1p, p4p, p2, p1, p3, and p4; therefore, this reduces the number 887 // of unknowns from 4 to 2 (i.e. just c1 and c2). 888 // To eliminate these 2 unknowns we use the following constraint: 889 // 890 // 3. Bp(0.5) == I(0.5). Bp(0.5)=(x,y) and I(0.5)=(xi,yi), and I should note 891 // that I(0.5) is *the only* reason for computing dxm,dym. This gives us 892 // (3) Bp(0.5) = (p1p + 3 * (p2p + p3p) + p4p)/8, which is equivalent to 893 // (4) p2p + p3p = (Bp(0.5)*8 - p1p - p4p) / 3 894 // We can substitute (1) and (2) from above into (4) and we get: 895 // (5) c1*(p2-p1) + c2*(p4-p3) = (Bp(0.5)*8 - p1p - p4p)/3 - p1p - p4p 896 // which is equivalent to 897 // (6) c1*(p2-p1) + c2*(p4-p3) = (4/3) * (Bp(0.5) * 2 - p1p - p4p) 898 // 899 // The right side of this is a 2D vector, and we know I(0.5), which gives us 900 // Bp(0.5), which gives us the value of the right side. 901 // The left side is just a matrix vector multiplication in disguise. It is 902 // 903 // [x2-x1, x4-x3][c1] 904 // [y2-y1, y4-y3][c2] 905 // which, is equal to 906 // [dx1, dx4][c1] 907 // [dy1, dy4][c2] 908 // At this point we are left with a simple linear system and we solve it by 909 // getting the inverse of the matrix above. Then we use [c1,c2] to compute 910 // p2p and p3p. 911 912 float x = (x1 + 3.0f * (x2 + x3) + x4) / 8.0f; 913 float y = (y1 + 3.0f * (y2 + y3) + y4) / 8.0f; 914 // (dxm,dym) is some tangent of B at t=0.5. This means it's equal to 915 // c*B'(0.5) for some constant c. 916 float dxm = x3 + x4 - x1 - x2, dym = y3 + y4 - y1 - y2; 917 918 // this computes the offsets at t=0, 0.5, 1, using the property that 919 // for any bezier curve the vectors p2-p1 and p4-p3 are parallel to 920 // the (dx/dt, dy/dt) vectors at the endpoints. 921 computeOffset(dx1, dy1, lineWidth2, offset0); 922 computeOffset(dxm, dym, lineWidth2, offset1); 923 computeOffset(dx4, dy4, lineWidth2, offset2); 924 float x1p = x1 + offset0[0]; // start 925 float y1p = y1 + offset0[1]; // point 926 float xi = x + offset1[0]; // interpolation 927 float yi = y + offset1[1]; // point 928 float x4p = x4 + offset2[0]; // end 929 float y4p = y4 + offset2[1]; // point 930 931 float invdet43 = 4.0f / (3.0f * (dx1 * dy4 - dy1 * dx4)); 932 933 float two_pi_m_p1_m_p4x = 2.0f * xi - x1p - x4p; 934 float two_pi_m_p1_m_p4y = 2.0f * yi - y1p - y4p; 935 float c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y); 936 float c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x); 937 938 float x2p, y2p, x3p, y3p; 939 x2p = x1p + c1*dx1; 940 y2p = y1p + c1*dy1; 941 x3p = x4p + c2*dx4; 942 y3p = y4p + c2*dy4; 943 944 leftOff[0] = x1p; leftOff[1] = y1p; 945 leftOff[2] = x2p; leftOff[3] = y2p; 946 leftOff[4] = x3p; leftOff[5] = y3p; 947 leftOff[6] = x4p; leftOff[7] = y4p; 948 949 x1p = x1 - offset0[0]; y1p = y1 - offset0[1]; 950 xi = xi - 2.0f * offset1[0]; yi = yi - 2.0f * offset1[1]; 951 x4p = x4 - offset2[0]; y4p = y4 - offset2[1]; 952 953 two_pi_m_p1_m_p4x = 2.0f * xi - x1p - x4p; 954 two_pi_m_p1_m_p4y = 2.0f * yi - y1p - y4p; 955 c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y); 956 c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x); 957 958 x2p = x1p + c1*dx1; 959 y2p = y1p + c1*dy1; 960 x3p = x4p + c2*dx4; 961 y3p = y4p + c2*dy4; 962 963 rightOff[0] = x1p; rightOff[1] = y1p; 964 rightOff[2] = x2p; rightOff[3] = y2p; 965 rightOff[4] = x3p; rightOff[5] = y3p; 966 rightOff[6] = x4p; rightOff[7] = y4p; 967 return 8; 968 } 969 970 // compute offset curves using bezier spline through t=0.5 (i.e. 971 // ComputedCurve(0.5) == IdealParallelCurve(0.5)) 972 // return the kind of curve in the right and left arrays. 973 private int computeOffsetQuad(float[] pts, final int off, 974 float[] leftOff, float[] rightOff) 975 { 976 final float x1 = pts[off + 0], y1 = pts[off + 1]; 977 final float x2 = pts[off + 2], y2 = pts[off + 3]; 978 final float x3 = pts[off + 4], y3 = pts[off + 5]; 979 980 final float dx3 = x3 - x2; 981 final float dy3 = y3 - y2; 982 final float dx1 = x2 - x1; 983 final float dy1 = y2 - y1; 984 985 // if p1=p2 or p3=p4 it means that the derivative at the endpoint 986 // vanishes, which creates problems with computeOffset. Usually 987 // this happens when this stroker object is trying to widen 988 // a curve with a cusp. What happens is that curveTo splits 989 // the input curve at the cusp, and passes it to this function. 990 // because of inaccuracies in the splitting, we consider points 991 // equal if they're very close to each other. 992 993 // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4, 994 // in which case ignore. 995 final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0f * Math.ulp(y2)); 996 final boolean p2eqp3 = within(x2, y2, x3, y3, 6.0f * Math.ulp(y3)); 997 if (p1eqp2 || p2eqp3) { 998 getLineOffsets(x1, y1, x3, y3, leftOff, rightOff); 999 return 4; 1000 } 1001 1002 // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line 1003 float dotsq = (dx1 * dx3 + dy1 * dy3); 1004 dotsq *= dotsq; 1005 float l1sq = dx1 * dx1 + dy1 * dy1, l3sq = dx3 * dx3 + dy3 * dy3; 1006 if (Helpers.within(dotsq, l1sq * l3sq, 4.0f * Math.ulp(dotsq))) { 1007 getLineOffsets(x1, y1, x3, y3, leftOff, rightOff); 1008 return 4; 1009 } 1010 1011 // this computes the offsets at t=0, 0.5, 1, using the property that 1012 // for any bezier curve the vectors p2-p1 and p4-p3 are parallel to 1013 // the (dx/dt, dy/dt) vectors at the endpoints. 1014 computeOffset(dx1, dy1, lineWidth2, offset0); 1015 computeOffset(dx3, dy3, lineWidth2, offset1); 1016 1017 float x1p = x1 + offset0[0]; // start 1018 float y1p = y1 + offset0[1]; // point 1019 float x3p = x3 + offset1[0]; // end 1020 float y3p = y3 + offset1[1]; // point 1021 safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, leftOff, 2); 1022 leftOff[0] = x1p; leftOff[1] = y1p; 1023 leftOff[4] = x3p; leftOff[5] = y3p; 1024 1025 x1p = x1 - offset0[0]; y1p = y1 - offset0[1]; 1026 x3p = x3 - offset1[0]; y3p = y3 - offset1[1]; 1027 safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, rightOff, 2); 1028 rightOff[0] = x1p; rightOff[1] = y1p; 1029 rightOff[4] = x3p; rightOff[5] = y3p; 1030 return 6; 1031 } 1032 1033 // finds values of t where the curve in pts should be subdivided in order 1034 // to get good offset curves a distance of w away from the middle curve. 1035 // Stores the points in ts, and returns how many of them there were. 1036 private static int findSubdivPoints(final Curve c, float[] pts, float[] ts, 1037 final int type, final float w) 1038 { 1039 final float x12 = pts[2] - pts[0]; 1040 final float y12 = pts[3] - pts[1]; 1041 // if the curve is already parallel to either axis we gain nothing 1042 // from rotating it. 1043 if (y12 != 0.0f && x12 != 0.0f) { 1044 // we rotate it so that the first vector in the control polygon is 1045 // parallel to the x-axis. This will ensure that rotated quarter 1046 // circles won't be subdivided. 1047 final float hypot = (float) Math.sqrt(x12 * x12 + y12 * y12); 1048 final float cos = x12 / hypot; 1049 final float sin = y12 / hypot; 1050 final float x1 = cos * pts[0] + sin * pts[1]; 1051 final float y1 = cos * pts[1] - sin * pts[0]; 1052 final float x2 = cos * pts[2] + sin * pts[3]; 1053 final float y2 = cos * pts[3] - sin * pts[2]; 1054 final float x3 = cos * pts[4] + sin * pts[5]; 1055 final float y3 = cos * pts[5] - sin * pts[4]; 1056 1057 switch(type) { 1058 case 8: 1059 final float x4 = cos * pts[6] + sin * pts[7]; 1060 final float y4 = cos * pts[7] - sin * pts[6]; 1061 c.set(x1, y1, x2, y2, x3, y3, x4, y4); 1062 break; 1063 case 6: 1064 c.set(x1, y1, x2, y2, x3, y3); 1065 break; 1066 default: 1067 } 1068 } else { 1069 c.set(pts, type); 1070 } 1071 1072 int ret = 0; 1073 // we subdivide at values of t such that the remaining rotated 1074 // curves are monotonic in x and y. 1075 ret += c.dxRoots(ts, ret); 1076 ret += c.dyRoots(ts, ret); 1077 // subdivide at inflection points. 1078 if (type == 8) { 1079 // quadratic curves can't have inflection points 1080 ret += c.infPoints(ts, ret); 1081 } 1082 1083 // now we must subdivide at points where one of the offset curves will have 1084 // a cusp. This happens at ts where the radius of curvature is equal to w. 1085 ret += c.rootsOfROCMinusW(ts, ret, w, 0.0001f); 1086 1087 ret = Helpers.filterOutNotInAB(ts, 0, ret, 0.0001f, 0.9999f); 1088 Helpers.isort(ts, 0, ret); 1089 return ret; 1090 } 1091 1092 @Override 1093 public void curveTo(float x1, float y1, 1094 float x2, float y2, 1095 float x3, float y3) 1096 { 1097 final int outcode0 = this.cOutCode; 1098 if (clipRect != null) { 1099 final int outcode3 = Helpers.outcode(x3, y3, clipRect); 1100 this.cOutCode = outcode3; 1101 1102 if (outcode3 != 0) { 1103 final int outcode1 = Helpers.outcode(x1, y1, clipRect); 1104 final int outcode2 = Helpers.outcode(x2, y2, clipRect); 1105 1106 // basic rejection criteria 1107 if ((outcode0 & outcode1 & outcode2 & outcode3) != 0) { 1108 moveTo(x3, y3, outcode0); 1109 opened = true; 1110 return; 1111 } 1112 } 1113 } 1114 1115 final float[] mid = middle; 1116 1117 mid[0] = cx0; mid[1] = cy0; 1118 mid[2] = x1; mid[3] = y1; 1119 mid[4] = x2; mid[5] = y2; 1120 mid[6] = x3; mid[7] = y3; 1121 1122 // need these so we can update the state at the end of this method 1123 final float xf = x3, yf = y3; 1124 float dxs = mid[2] - mid[0]; 1125 float dys = mid[3] - mid[1]; 1126 float dxf = mid[6] - mid[4]; 1127 float dyf = mid[7] - mid[5]; 1128 1129 boolean p1eqp2 = (dxs == 0.0f && dys == 0.0f); 1130 boolean p3eqp4 = (dxf == 0.0f && dyf == 0.0f); 1131 if (p1eqp2) { 1132 dxs = mid[4] - mid[0]; 1133 dys = mid[5] - mid[1]; 1134 if (dxs == 0.0f && dys == 0.0f) { 1135 dxs = mid[6] - mid[0]; 1136 dys = mid[7] - mid[1]; 1137 } 1138 } 1139 if (p3eqp4) { 1140 dxf = mid[6] - mid[2]; 1141 dyf = mid[7] - mid[3]; 1142 if (dxf == 0.0f && dyf == 0.0f) { 1143 dxf = mid[6] - mid[0]; 1144 dyf = mid[7] - mid[1]; 1145 } 1146 } 1147 if (dxs == 0.0f && dys == 0.0f) { 1148 // this happens if the "curve" is just a point 1149 // fix outcode0 for lineTo() call: 1150 if (clipRect != null) { 1151 this.cOutCode = outcode0; 1152 } 1153 lineTo(mid[0], mid[1]); 1154 return; 1155 } 1156 1157 // if these vectors are too small, normalize them, to avoid future 1158 // precision problems. 1159 if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) { 1160 float len = (float) Math.sqrt(dxs*dxs + dys*dys); 1161 dxs /= len; 1162 dys /= len; 1163 } 1164 if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) { 1165 float len = (float) Math.sqrt(dxf*dxf + dyf*dyf); 1166 dxf /= len; 1167 dyf /= len; 1168 } 1169 1170 computeOffset(dxs, dys, lineWidth2, offset0); 1171 drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0); 1172 1173 final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2); 1174 1175 float prevT = 0.0f; 1176 for (int i = 0, off = 0; i < nSplits; i++, off += 6) { 1177 final float t = subdivTs[i]; 1178 Helpers.subdivideCubicAt((t - prevT) / (1.0f - prevT), 1179 mid, off, mid, off, mid, off + 6); 1180 prevT = t; 1181 } 1182 1183 final float[] l = lp; 1184 final float[] r = rp; 1185 1186 int kind = 0; 1187 for (int i = 0, off = 0; i <= nSplits; i++, off += 6) { 1188 kind = computeOffsetCubic(mid, off, l, r); 1189 1190 emitLineTo(l[0], l[1]); 1191 1192 switch(kind) { 1193 case 8: 1194 emitCurveTo(l[2], l[3], l[4], l[5], l[6], l[7]); 1195 emitCurveToRev(r[0], r[1], r[2], r[3], r[4], r[5]); 1196 break; 1197 case 4: 1198 emitLineTo(l[2], l[3]); 1199 emitLineToRev(r[0], r[1]); 1200 break; 1201 default: 1202 } 1203 emitLineToRev(r[kind - 2], r[kind - 1]); 1204 } 1205 1206 this.prev = DRAWING_OP_TO; 1207 this.cx0 = xf; 1208 this.cy0 = yf; 1209 this.cdx = dxf; 1210 this.cdy = dyf; 1211 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f; 1212 this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f; 1213 } 1214 1215 @Override 1216 public void quadTo(float x1, float y1, 1217 float x2, float y2) 1218 { 1219 final int outcode0 = this.cOutCode; 1220 if (clipRect != null) { 1221 final int outcode2 = Helpers.outcode(x2, y2, clipRect); 1222 this.cOutCode = outcode2; 1223 1224 if (outcode2 != 0) { 1225 final int outcode1 = Helpers.outcode(x1, y1, clipRect); 1226 1227 // basic rejection criteria 1228 if ((outcode0 & outcode1 & outcode2) != 0) { 1229 moveTo(x2, y2, outcode0); 1230 opened = true; 1231 return; 1232 } 1233 } 1234 } 1235 1236 final float[] mid = middle; 1237 1238 mid[0] = cx0; mid[1] = cy0; 1239 mid[2] = x1; mid[3] = y1; 1240 mid[4] = x2; mid[5] = y2; 1241 1242 // need these so we can update the state at the end of this method 1243 final float xf = x2, yf = y2; 1244 float dxs = mid[2] - mid[0]; 1245 float dys = mid[3] - mid[1]; 1246 float dxf = mid[4] - mid[2]; 1247 float dyf = mid[5] - mid[3]; 1248 if ((dxs == 0.0f && dys == 0.0f) || (dxf == 0.0f && dyf == 0.0f)) { 1249 dxs = dxf = mid[4] - mid[0]; 1250 dys = dyf = mid[5] - mid[1]; 1251 } 1252 if (dxs == 0.0f && dys == 0.0f) { 1253 // this happens if the "curve" is just a point 1254 // fix outcode0 for lineTo() call: 1255 if (clipRect != null) { 1256 this.cOutCode = outcode0; 1257 } 1258 lineTo(mid[0], mid[1]); 1259 return; 1260 } 1261 // if these vectors are too small, normalize them, to avoid future 1262 // precision problems. 1263 if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) { 1264 float len = (float) Math.sqrt(dxs*dxs + dys*dys); 1265 dxs /= len; 1266 dys /= len; 1267 } 1268 if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) { 1269 float len = (float) Math.sqrt(dxf*dxf + dyf*dyf); 1270 dxf /= len; 1271 dyf /= len; 1272 } 1273 1274 computeOffset(dxs, dys, lineWidth2, offset0); 1275 drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0); 1276 1277 int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2); 1278 1279 float prevt = 0.0f; 1280 for (int i = 0, off = 0; i < nSplits; i++, off += 4) { 1281 final float t = subdivTs[i]; 1282 Helpers.subdivideQuadAt((t - prevt) / (1.0f - prevt), 1283 mid, off, mid, off, mid, off + 4); 1284 prevt = t; 1285 } 1286 1287 final float[] l = lp; 1288 final float[] r = rp; 1289 1290 int kind = 0; 1291 for (int i = 0, off = 0; i <= nSplits; i++, off += 4) { 1292 kind = computeOffsetQuad(mid, off, l, r); 1293 1294 emitLineTo(l[0], l[1]); 1295 1296 switch(kind) { 1297 case 6: 1298 emitQuadTo(l[2], l[3], l[4], l[5]); 1299 emitQuadToRev(r[0], r[1], r[2], r[3]); 1300 break; 1301 case 4: 1302 emitLineTo(l[2], l[3]); 1303 emitLineToRev(r[0], r[1]); 1304 break; 1305 default: 1306 } 1307 emitLineToRev(r[kind - 2], r[kind - 1]); 1308 } 1309 1310 this.prev = DRAWING_OP_TO; 1311 this.cx0 = xf; 1312 this.cy0 = yf; 1313 this.cdx = dxf; 1314 this.cdy = dyf; 1315 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f; 1316 this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f; 1317 } 1318 1319 }