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