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