1 /* 2 * Copyright (c) 2007, 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.pisces; 27 28 public class Stroker extends LineSink { 29 30 private static final int MOVE_TO = 0; 31 private static final int LINE_TO = 1; 32 private static final int CLOSE = 2; 33 34 /** 35 * Constant value for join style. 36 */ 37 public static final int JOIN_MITER = 0; 38 39 /** 40 * Constant value for join style. 41 */ 42 public static final int JOIN_ROUND = 1; 43 44 /** 45 * Constant value for join style. 46 */ 47 public static final int JOIN_BEVEL = 2; 48 49 /** 50 * Constant value for end cap style. 51 */ 52 public static final int CAP_BUTT = 0; 53 54 /** 55 * Constant value for end cap style. 56 */ 57 public static final int CAP_ROUND = 1; 58 59 /** 60 * Constant value for end cap style. 61 */ 62 public static final int CAP_SQUARE = 2; 63 64 LineSink output; 65 66 int lineWidth; 67 int capStyle; 68 int joinStyle; 69 int miterLimit; 70 71 Transform4 transform; 72 int m00, m01; 73 int m10, m11; 74 75 int lineWidth2; 76 long scaledLineWidth2; 77 78 // For any pen offset (pen_dx, pen_dy) that does not depend on 79 // the line orientation, the pen should be transformed so that: 80 // 81 // pen_dx' = m00*pen_dx + m01*pen_dy 82 // pen_dy' = m10*pen_dx + m11*pen_dy 83 // 84 // For a round pen, this means: 85 // 86 // pen_dx(r, theta) = r*cos(theta) 87 // pen_dy(r, theta) = r*sin(theta) 88 // 89 // pen_dx'(r, theta) = r*(m00*cos(theta) + m01*sin(theta)) 90 // pen_dy'(r, theta) = r*(m10*cos(theta) + m11*sin(theta)) 91 int numPenSegments; 92 int[] pen_dx; 93 int[] pen_dy; 94 boolean[] penIncluded; 95 int[] join; 96 97 int[] offset = new int[2]; 98 int[] reverse = new int[100]; 99 int[] miter = new int[2]; 100 long miterLimitSq; 101 102 int prev; 103 int rindex; 104 boolean started; 105 boolean lineToOrigin; 106 boolean joinToOrigin; 107 108 int sx0, sy0, sx1, sy1, x0, y0, x1, y1; 109 int mx0, my0, mx1, my1, omx, omy; 110 int lx0, ly0, lx1, ly1, lx0p, ly0p, px0, py0; 111 112 double m00_2_m01_2; 113 double m10_2_m11_2; 114 double m00_m10_m01_m11; 115 116 // An upper bound on the width the the transformed line and the 117 // untransformed line. 118 int wub; 119 120 /** 121 * Empty constructor. <code>setOutput</code> and 122 * <code>setParameters</code> must be called prior to calling any 123 * other methods. 124 */ 125 public Stroker() {} 126 127 /** 128 * Constructs a <code>Stroker</code>. 129 * 130 * @param output an output <code>LineSink</code>. 131 * @param lineWidth the desired line width in pixels, in S15.16 132 * format. 133 * @param capStyle the desired end cap style, one of 134 * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or 135 * <code>CAP_SQUARE</code>. 136 * @param joinStyle the desired line join style, one of 137 * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or 138 * <code>JOIN_BEVEL</code>. 139 * @param miterLimit the desired miter limit, in S15.16 format. 140 * @param transform a <code>Transform4</code> object indicating 141 * the transform that has been previously applied to all incoming 142 * coordinates. This is required in order to produce consistently 143 * shaped end caps and joins. 144 */ 145 public Stroker(LineSink output, 146 int lineWidth, 147 int capStyle, 148 int joinStyle, 149 int miterLimit, 150 Transform4 transform) { 151 setOutput(output); 152 setParameters(lineWidth, capStyle, joinStyle, miterLimit, transform); 153 } 154 155 /** 156 * Sets the output <code>LineSink</code> of this 157 * <code>Stroker</code>. 158 * 159 * @param output an output <code>LineSink</code>. 160 */ 161 public void setOutput(LineSink output) { 162 this.output = output; 163 } 164 165 /** 166 * Sets the parameters of this <code>Stroker</code>. 167 * @param lineWidth the desired line width in pixels, in S15.16 168 * format. 169 * @param capStyle the desired end cap style, one of 170 * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or 171 * <code>CAP_SQUARE</code>. 172 * @param joinStyle the desired line join style, one of 173 * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or 174 * <code>JOIN_BEVEL</code>. 175 * @param miterLimit the desired miter limit, in S15.16 format. 176 * @param transform a <code>Transform4</code> object indicating 177 * the transform that has been previously applied to all incoming 178 * coordinates. This is required in order to produce consistently 179 * shaped end caps and joins. 180 */ 181 public void setParameters(int lineWidth, 182 int capStyle, 183 int joinStyle, 184 int miterLimit, 185 Transform4 transform) { 186 this.lineWidth = lineWidth; 187 this.lineWidth2 = lineWidth >> 1; 188 this.scaledLineWidth2 = ((long)transform.m00*lineWidth2) >> 16; 189 this.capStyle = capStyle; 190 this.joinStyle = joinStyle; 191 this.miterLimit = miterLimit; 192 193 this.transform = transform; 194 this.m00 = transform.m00; 195 this.m01 = transform.m01; 196 this.m10 = transform.m10; 197 this.m11 = transform.m11; 198 199 this.m00_2_m01_2 = (double)m00*m00 + (double)m01*m01; 200 this.m10_2_m11_2 = (double)m10*m10 + (double)m11*m11; 201 this.m00_m10_m01_m11 = (double)m00*m10 + (double)m01*m11; 202 203 double dm00 = m00/65536.0; 204 double dm01 = m01/65536.0; 205 double dm10 = m10/65536.0; 206 double dm11 = m11/65536.0; 207 double determinant = dm00*dm11 - dm01*dm10; 208 209 if (joinStyle == JOIN_MITER) { 210 double limit = 211 (miterLimit/65536.0)*(lineWidth2/65536.0)*determinant; 212 double limitSq = limit*limit; 213 this.miterLimitSq = (long)(limitSq*65536.0*65536.0); 214 } 215 216 this.numPenSegments = (int)(3.14159f*lineWidth/65536.0f); 217 if (pen_dx == null || pen_dx.length < numPenSegments) { 218 this.pen_dx = new int[numPenSegments]; 219 this.pen_dy = new int[numPenSegments]; 220 this.penIncluded = new boolean[numPenSegments]; 221 this.join = new int[2*numPenSegments]; 222 } 223 224 for (int i = 0; i < numPenSegments; i++) { 225 double r = lineWidth/2.0; 226 double theta = (double)i*2.0*Math.PI/numPenSegments; 227 228 double cos = Math.cos(theta); 229 double sin = Math.sin(theta); 230 pen_dx[i] = (int)(r*(dm00*cos + dm01*sin)); 231 pen_dy[i] = (int)(r*(dm10*cos + dm11*sin)); 232 } 233 234 wub = (int)((lineWidth * PiscesMath.hypot((long)m01+m00, (long)m10+m11)) >> 16); 235 wub = (wub > lineWidth) ? wub : lineWidth; 236 237 prev = CLOSE; 238 rindex = 0; 239 started = false; 240 lineToOrigin = false; 241 } 242 243 private void computeOffset(int x0, int y0, int x1, int y1, int[] m) { 244 long lx = (long)x1 - (long)x0; 245 long ly = (long)y1 - (long)y0; 246 247 int dx, dy; 248 if (m00 > 0 && m00 == m11 && m01 == 0 & m10 == 0) { 249 long ilen = PiscesMath.hypot(lx, ly); 250 if (ilen == 0) { 251 dx = dy = 0; 252 } else { 253 dx = (int)( (ly*scaledLineWidth2)/ilen); 254 dy = (int)(-(lx*scaledLineWidth2)/ilen); 255 } 256 } else { 257 double dlx = x1 - x0; 258 double dly = y1 - y0; 259 double det = (double)m00*m11 - (double)m01*m10; 260 int sdet = (det > 0) ? 1 : -1; 261 double a = dly*m00 - dlx*m10; 262 double b = dly*m01 - dlx*m11; 263 double dh = PiscesMath.hypot(a, b); 264 double div = sdet*lineWidth2/(65536.0*dh); 265 double ddx = dly*m00_2_m01_2 - dlx*m00_m10_m01_m11; 266 double ddy = dly*m00_m10_m01_m11 - dlx*m10_2_m11_2; 267 dx = (int)(ddx*div); 268 dy = (int)(ddy*div); 269 } 270 271 m[0] = dx; 272 m[1] = dy; 273 } 274 275 private void ensureCapacity(int newrindex) { 276 if (reverse.length < newrindex) { 277 int[] tmp = new int[Math.max(newrindex, 6*reverse.length/5)]; 278 System.arraycopy(reverse, 0, tmp, 0, rindex); 279 this.reverse = tmp; 280 } 281 } 282 283 private boolean isCCW(int x0, int y0, 284 int x1, int y1, 285 int x2, int y2) { 286 // These need to be longs to avoid overflows. 287 long dx0 = (long)x1 - x0; 288 long dy0 = (long)y1 - y0; 289 long dx1 = (long)x2 - x1; 290 long dy1 = (long)y2 - y1; 291 return dx0*dy1 < dy0*dx1; 292 } 293 294 private boolean side(int x, int y, int x0, int y0, int x1, int y1) { 295 long lx = x; 296 long ly = y; 297 long lx0 = x0; 298 long ly0 = y0; 299 long lx1 = x1; 300 long ly1 = y1; 301 302 return (ly0 - ly1)*lx + (lx1 - lx0)*ly + (lx0*ly1 - lx1*ly0) > 0; 303 } 304 305 private int computeRoundJoin(int cx, int cy, 306 int xa, int ya, 307 int xb, int yb, 308 int side, 309 boolean flip, 310 int[] join) { 311 int px, py; 312 int ncoords = 0; 313 314 boolean centerSide; 315 if (side == 0) { 316 centerSide = side(cx, cy, xa, ya, xb, yb); 317 } else { 318 centerSide = (side == 1) ? true : false; 319 } 320 for (int i = 0; i < numPenSegments; i++) { 321 px = cx + pen_dx[i]; 322 py = cy + pen_dy[i]; 323 324 boolean penSide = side(px, py, xa, ya, xb, yb); 325 if (penSide != centerSide) { 326 penIncluded[i] = true; 327 } else { 328 penIncluded[i] = false; 329 } 330 } 331 332 int start = -1, end = -1; 333 for (int i = 0; i < numPenSegments; i++) { 334 if (penIncluded[i] && 335 !penIncluded[(i + numPenSegments - 1) % numPenSegments]) { 336 start = i; 337 } 338 if (penIncluded[i] && 339 !penIncluded[(i + 1) % numPenSegments]) { 340 end = i; 341 } 342 } 343 344 if (end < start) { 345 end += numPenSegments; 346 } 347 348 if (start != -1 && end != -1) { 349 long dxa = cx + pen_dx[start] - xa; 350 long dya = cy + pen_dy[start] - ya; 351 long dxb = cx + pen_dx[start] - xb; 352 long dyb = cy + pen_dy[start] - yb; 353 354 boolean rev = (dxa*dxa + dya*dya > dxb*dxb + dyb*dyb); 355 int i = rev ? end : start; 356 int incr = rev ? -1 : 1; 357 while (true) { 358 int idx = i % numPenSegments; 359 px = cx + pen_dx[idx]; 360 py = cy + pen_dy[idx]; 361 join[ncoords++] = px; 362 join[ncoords++] = py; 363 if (i == (rev ? start : end)) { 364 break; 365 } 366 i += incr; 367 } 368 } 369 370 return ncoords/2; 371 } 372 373 private static final long ROUND_JOIN_THRESHOLD = 1000L; 374 private static final long ROUND_JOIN_INTERNAL_THRESHOLD = 1000000000L; 375 376 private void drawRoundJoin(int x, int y, 377 int omx, int omy, int mx, int my, 378 int side, 379 boolean flip, 380 boolean rev, 381 long threshold) { 382 if ((omx == 0 && omy == 0) || (mx == 0 && my == 0)) { 383 return; 384 } 385 386 long domx = (long)omx - mx; 387 long domy = (long)omy - my; 388 long len = domx*domx + domy*domy; 389 if (len < threshold) { 390 return; 391 } 392 393 if (rev) { 394 omx = -omx; 395 omy = -omy; 396 mx = -mx; 397 my = -my; 398 } 399 400 int bx0 = x + omx; 401 int by0 = y + omy; 402 int bx1 = x + mx; 403 int by1 = y + my; 404 405 int npoints = computeRoundJoin(x, y, 406 bx0, by0, bx1, by1, side, flip, 407 join); 408 for (int i = 0; i < npoints; i++) { 409 emitLineTo(join[2*i], join[2*i + 1], rev); 410 } 411 } 412 413 // Return the intersection point of the lines (ix0, iy0) -> (ix1, iy1) 414 // and (ix0p, iy0p) -> (ix1p, iy1p) in m[0] and m[1] 415 private void computeMiter(int ix0, int iy0, int ix1, int iy1, 416 int ix0p, int iy0p, int ix1p, int iy1p, 417 int[] m) { 418 long x0 = ix0; 419 long y0 = iy0; 420 long x1 = ix1; 421 long y1 = iy1; 422 423 long x0p = ix0p; 424 long y0p = iy0p; 425 long x1p = ix1p; 426 long y1p = iy1p; 427 428 long x10 = x1 - x0; 429 long y10 = y1 - y0; 430 long x10p = x1p - x0p; 431 long y10p = y1p - y0p; 432 433 long den = (x10*y10p - x10p*y10) >> 16; 434 if (den == 0) { 435 m[0] = ix0; 436 m[1] = iy0; 437 return; 438 } 439 440 long t = (x1p*(y0 - y0p) - x0*y10p + x0p*(y1p - y0)) >> 16; 441 m[0] = (int)(x0 + (t*x10)/den); 442 m[1] = (int)(y0 + (t*y10)/den); 443 } 444 445 private void drawMiter(int px0, int py0, 446 int x0, int y0, 447 int x1, int y1, 448 int omx, int omy, int mx, int my, 449 boolean rev) { 450 if (mx == omx && my == omy) { 451 return; 452 } 453 if (px0 == x0 && py0 == y0) { 454 return; 455 } 456 if (x0 == x1 && y0 == y1) { 457 return; 458 } 459 460 if (rev) { 461 omx = -omx; 462 omy = -omy; 463 mx = -mx; 464 my = -my; 465 } 466 467 computeMiter(px0 + omx, py0 + omy, x0 + omx, y0 + omy, 468 x0 + mx, y0 + my, x1 + mx, y1 + my, 469 miter); 470 471 // Compute miter length in untransformed coordinates 472 long dx = (long)miter[0] - x0; 473 long dy = (long)miter[1] - y0; 474 long a = (dy*m00 - dx*m10) >> 16; 475 long b = (dy*m01 - dx*m11) >> 16; 476 long lenSq = a*a + b*b; 477 478 if (lenSq < miterLimitSq) { 479 emitLineTo(miter[0], miter[1], rev); 480 } 481 } 482 483 private static void makeOverflowSafe(int[] coords, int wub) { 484 int x = coords[0]; 485 int y = coords[1]; 486 if ((long)x + wub != x + wub) { 487 x -= wub; 488 } 489 if ((long)y + wub != y + wub) { 490 y -= wub; 491 } 492 if ((long)x - wub != x - wub) { 493 x += wub; 494 } 495 if ((long)y - wub != y - wub) { 496 y += wub; 497 } 498 coords[0] = x; 499 coords[1] = y; 500 } 501 502 public void moveTo(int x0, int y0) { 503 // System.out.println("Stroker.moveTo(" + x0/65536.0 + ", " + y0/65536.0 + ")"); 504 505 // We can't move too close to the borders set by Integer.MAX_VALUE or 506 // MIN_VALUE, because when we try to draw an endcap, or even just adding 507 // offsets to (x0,y0), overflows might happen. 508 int[] coords = {x0, y0}; 509 makeOverflowSafe(coords, wub); 510 x0 = coords[0]; y0 = coords[1]; 511 512 if (lineToOrigin) { 513 // not closing the path, do the previous lineTo 514 lineToImpl(sx0, sy0, joinToOrigin); 515 lineToOrigin = false; 516 } 517 518 if (prev == LINE_TO) { 519 finish(); 520 } 521 522 this.sx0 = this.x0 = x0; 523 this.sy0 = this.y0 = y0; 524 this.rindex = 0; 525 this.started = false; 526 this.joinSegment = false; 527 this.prev = MOVE_TO; 528 } 529 530 boolean joinSegment = false; 531 532 public void lineJoin() { 533 // System.out.println("Stroker.lineJoin()"); 534 this.joinSegment = true; 535 } 536 537 public void lineTo(int x1, int y1) { 538 // System.out.println("Stroker.lineTo(" + x1/65536.0 + ", " + y1/65536.0 + ")"); 539 540 if (lineToOrigin) { 541 if (x1 == sx0 && y1 == sy0) { 542 // staying in the starting point 543 return; 544 } 545 546 // not closing the path, do the previous lineTo 547 lineToImpl(sx0, sy0, joinToOrigin); 548 lineToOrigin = false; 549 } else if (x1 == x0 && y1 == y0) { 550 return; 551 } else if (x1 == sx0 && y1 == sy0) { 552 lineToOrigin = true; 553 joinToOrigin = joinSegment; 554 joinSegment = false; 555 return; 556 } 557 558 lineToImpl(x1, y1, joinSegment); 559 joinSegment = false; 560 } 561 562 private void lineToImpl(int x1, int y1, boolean joinSegment) { 563 computeOffset(x0, y0, x1, y1, offset); 564 int mx = offset[0]; 565 int my = offset[1]; 566 567 // Now we want to make sure that none of the operations involving line 568 // drawing will overflow. These include adding pen points to (x1,y1), 569 // drawing a square end cap, and adding/subtracting mx and my to x1 570 // and y1 (in the calls to emitLineTo). However, it does not protect 571 // against possible overflows when drawing miters. 572 // TODO: protect against possible overflows when drawing miters. 573 int[] coords = {x1, y1}; 574 makeOverflowSafe(coords, wub); 575 x1 = coords[0]; y1 = coords[1]; 576 577 if (!started) { 578 emitMoveTo(x0 + mx, y0 + my); 579 this.sx1 = x1; 580 this.sy1 = y1; 581 this.mx0 = mx; 582 this.my0 = my; 583 started = true; 584 } else { 585 boolean ccw = isCCW(px0, py0, x0, y0, x1, y1); 586 if (joinSegment) { 587 if (joinStyle == JOIN_MITER) { 588 drawMiter(px0, py0, x0, y0, x1, y1, omx, omy, mx, my, 589 ccw); 590 } else if (joinStyle == JOIN_ROUND) { 591 drawRoundJoin(x0, y0, 592 omx, omy, 593 mx, my, 0, false, ccw, 594 ROUND_JOIN_THRESHOLD); 595 } 596 } else { 597 // Draw internal joins as round 598 drawRoundJoin(x0, y0, 599 omx, omy, 600 mx, my, 0, false, ccw, 601 ROUND_JOIN_INTERNAL_THRESHOLD); 602 } 603 604 emitLineTo(x0, y0, !ccw); 605 } 606 607 emitLineTo(x0 + mx, y0 + my, false); 608 emitLineTo(x1 + mx, y1 + my, false); 609 610 emitLineTo(x0 - mx, y0 - my, true); 611 emitLineTo(x1 - mx, y1 - my, true); 612 613 lx0 = x1 + mx; ly0 = y1 + my; 614 lx0p = x1 - mx; ly0p = y1 - my; 615 lx1 = x1; ly1 = y1; 616 617 this.omx = mx; 618 this.omy = my; 619 this.px0 = x0; 620 this.py0 = y0; 621 this.x0 = x1; 622 this.y0 = y1; 623 this.prev = LINE_TO; 624 } 625 626 public void close() { 627 // System.out.println("Stroker.close()"); 628 629 if (lineToOrigin) { 630 // ignore the previous lineTo 631 lineToOrigin = false; 632 } 633 634 if (!started) { 635 finish(); 636 return; 637 } 638 639 computeOffset(x0, y0, sx0, sy0, offset); 640 int mx = offset[0]; 641 int my = offset[1]; 642 643 // Draw penultimate join 644 boolean ccw = isCCW(px0, py0, x0, y0, sx0, sy0); 645 if (joinSegment) { 646 if (joinStyle == JOIN_MITER) { 647 drawMiter(px0, py0, x0, y0, sx0, sy0, omx, omy, mx, my, ccw); 648 } else if (joinStyle == JOIN_ROUND) { 649 drawRoundJoin(x0, y0, omx, omy, mx, my, 0, false, ccw, 650 ROUND_JOIN_THRESHOLD); 651 } 652 } else { 653 // Draw internal joins as round 654 drawRoundJoin(x0, y0, 655 omx, omy, 656 mx, my, 0, false, ccw, 657 ROUND_JOIN_INTERNAL_THRESHOLD); 658 } 659 660 emitLineTo(x0 + mx, y0 + my); 661 emitLineTo(sx0 + mx, sy0 + my); 662 663 ccw = isCCW(x0, y0, sx0, sy0, sx1, sy1); 664 665 // Draw final join on the outside 666 if (!ccw) { 667 if (joinStyle == JOIN_MITER) { 668 drawMiter(x0, y0, sx0, sy0, sx1, sy1, 669 mx, my, mx0, my0, false); 670 } else if (joinStyle == JOIN_ROUND) { 671 drawRoundJoin(sx0, sy0, mx, my, mx0, my0, 0, false, false, 672 ROUND_JOIN_THRESHOLD); 673 } 674 } 675 676 emitLineTo(sx0 + mx0, sy0 + my0); 677 emitLineTo(sx0 - mx0, sy0 - my0); // same as reverse[0], reverse[1] 678 679 // Draw final join on the inside 680 if (ccw) { 681 if (joinStyle == JOIN_MITER) { 682 drawMiter(x0, y0, sx0, sy0, sx1, sy1, 683 -mx, -my, -mx0, -my0, false); 684 } else if (joinStyle == JOIN_ROUND) { 685 drawRoundJoin(sx0, sy0, -mx, -my, -mx0, -my0, 0, 686 true, false, 687 ROUND_JOIN_THRESHOLD); 688 } 689 } 690 691 emitLineTo(sx0 - mx, sy0 - my); 692 emitLineTo(x0 - mx, y0 - my); 693 for (int i = rindex - 2; i >= 0; i -= 2) { 694 emitLineTo(reverse[i], reverse[i + 1]); 695 } 696 697 this.x0 = this.sx0; 698 this.y0 = this.sy0; 699 this.rindex = 0; 700 this.started = false; 701 this.joinSegment = false; 702 this.prev = CLOSE; 703 emitClose(); 704 } 705 706 public void end() { 707 // System.out.println("Stroker.end()"); 708 709 if (lineToOrigin) { 710 // not closing the path, do the previous lineTo 711 lineToImpl(sx0, sy0, joinToOrigin); 712 lineToOrigin = false; 713 } 714 715 if (prev == LINE_TO) { 716 finish(); 717 } 718 719 output.end(); 720 this.joinSegment = false; 721 this.prev = MOVE_TO; 722 } 723 724 long lineLength(long ldx, long ldy) { 725 long ldet = ((long)m00*m11 - (long)m01*m10) >> 16; 726 long la = ((long)ldy*m00 - (long)ldx*m10)/ldet; 727 long lb = ((long)ldy*m01 - (long)ldx*m11)/ldet; 728 long llen = (int)PiscesMath.hypot(la, lb); 729 return llen; 730 } 731 732 private void finish() { 733 if (capStyle == CAP_ROUND) { 734 drawRoundJoin(x0, y0, 735 omx, omy, -omx, -omy, 1, false, false, 736 ROUND_JOIN_THRESHOLD); 737 } else if (capStyle == CAP_SQUARE) { 738 long ldx = (long)(px0 - x0); 739 long ldy = (long)(py0 - y0); 740 long llen = lineLength(ldx, ldy); 741 long s = (long)lineWidth2*65536/llen; 742 743 int capx = x0 - (int)(ldx*s >> 16); 744 int capy = y0 - (int)(ldy*s >> 16); 745 746 emitLineTo(capx + omx, capy + omy); 747 emitLineTo(capx - omx, capy - omy); 748 } 749 750 for (int i = rindex - 2; i >= 0; i -= 2) { 751 emitLineTo(reverse[i], reverse[i + 1]); 752 } 753 this.rindex = 0; 754 755 if (capStyle == CAP_ROUND) { 756 drawRoundJoin(sx0, sy0, 757 -mx0, -my0, mx0, my0, 1, false, false, 758 ROUND_JOIN_THRESHOLD); 759 } else if (capStyle == CAP_SQUARE) { 760 long ldx = (long)(sx1 - sx0); 761 long ldy = (long)(sy1 - sy0); 762 long llen = lineLength(ldx, ldy); 763 long s = (long)lineWidth2*65536/llen; 764 765 int capx = sx0 - (int)(ldx*s >> 16); 766 int capy = sy0 - (int)(ldy*s >> 16); 767 768 emitLineTo(capx - mx0, capy - my0); 769 emitLineTo(capx + mx0, capy + my0); 770 } 771 772 emitClose(); 773 this.joinSegment = false; 774 } 775 776 private void emitMoveTo(int x0, int y0) { 777 // System.out.println("Stroker.emitMoveTo(" + x0/65536.0 + ", " + y0/65536.0 + ")"); 778 output.moveTo(x0, y0); 779 } 780 781 private void emitLineTo(int x1, int y1) { 782 // System.out.println("Stroker.emitLineTo(" + x0/65536.0 + ", " + y0/65536.0 + ")"); 783 output.lineTo(x1, y1); 784 } 785 786 private void emitLineTo(int x1, int y1, boolean rev) { 787 if (rev) { 788 ensureCapacity(rindex + 2); 789 reverse[rindex++] = x1; 790 reverse[rindex++] = y1; 791 } else { 792 emitLineTo(x1, y1); 793 } 794 } 795 796 private void emitClose() { 797 // System.out.println("Stroker.emitClose()"); 798 output.close(); 799 } 800 }