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