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