J. Duke | 319a3b9 | 2007-12-01 00:00:00 +0000 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright 2007 Sun Microsystems, Inc. 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. Sun designates this |
| 8 | * particular file as subject to the "Classpath" exception as provided |
| 9 | * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
| 22 | * CA 95054 USA or visit www.sun.com if you need additional information or |
| 23 | * have any 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 | /** |
| 117 | * Empty constructor. <code>setOutput</code> and |
| 118 | * <code>setParameters</code> must be called prior to calling any |
| 119 | * other methods. |
| 120 | */ |
| 121 | public Stroker() {} |
| 122 | |
| 123 | /** |
| 124 | * Constructs a <code>Stroker</code>. |
| 125 | * |
| 126 | * @param output an output <code>LineSink</code>. |
| 127 | * @param lineWidth the desired line width in pixels, in S15.16 |
| 128 | * format. |
| 129 | * @param capStyle the desired end cap style, one of |
| 130 | * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or |
| 131 | * <code>CAP_SQUARE</code>. |
| 132 | * @param joinStyle the desired line join style, one of |
| 133 | * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or |
| 134 | * <code>JOIN_BEVEL</code>. |
| 135 | * @param miterLimit the desired miter limit, in S15.16 format. |
| 136 | * @param transform a <code>Transform4</code> object indicating |
| 137 | * the transform that has been previously applied to all incoming |
| 138 | * coordinates. This is required in order to produce consistently |
| 139 | * shaped end caps and joins. |
| 140 | */ |
| 141 | public Stroker(LineSink output, |
| 142 | int lineWidth, |
| 143 | int capStyle, |
| 144 | int joinStyle, |
| 145 | int miterLimit, |
| 146 | Transform4 transform) { |
| 147 | setOutput(output); |
| 148 | setParameters(lineWidth, capStyle, joinStyle, miterLimit, transform); |
| 149 | } |
| 150 | |
| 151 | /** |
| 152 | * Sets the output <code>LineSink</code> of this |
| 153 | * <code>Stroker</code>. |
| 154 | * |
| 155 | * @param output an output <code>LineSink</code>. |
| 156 | */ |
| 157 | public void setOutput(LineSink output) { |
| 158 | this.output = output; |
| 159 | } |
| 160 | |
| 161 | /** |
| 162 | * Sets the parameters of this <code>Stroker</code>. |
| 163 | * @param lineWidth the desired line width in pixels, in S15.16 |
| 164 | * format. |
| 165 | * @param capStyle the desired end cap style, one of |
| 166 | * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or |
| 167 | * <code>CAP_SQUARE</code>. |
| 168 | * @param joinStyle the desired line join style, one of |
| 169 | * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or |
| 170 | * <code>JOIN_BEVEL</code>. |
| 171 | * @param miterLimit the desired miter limit, in S15.16 format. |
| 172 | * @param transform a <code>Transform4</code> object indicating |
| 173 | * the transform that has been previously applied to all incoming |
| 174 | * coordinates. This is required in order to produce consistently |
| 175 | * shaped end caps and joins. |
| 176 | */ |
| 177 | public void setParameters(int lineWidth, |
| 178 | int capStyle, |
| 179 | int joinStyle, |
| 180 | int miterLimit, |
| 181 | Transform4 transform) { |
| 182 | this.lineWidth = lineWidth; |
| 183 | this.lineWidth2 = lineWidth >> 1; |
| 184 | this.scaledLineWidth2 = (long)transform.m00*lineWidth2; |
| 185 | this.capStyle = capStyle; |
| 186 | this.joinStyle = joinStyle; |
| 187 | this.miterLimit = miterLimit; |
| 188 | |
| 189 | this.transform = transform; |
| 190 | this.m00 = transform.m00; |
| 191 | this.m01 = transform.m01; |
| 192 | this.m10 = transform.m10; |
| 193 | this.m11 = transform.m11; |
| 194 | |
| 195 | this.m00_2_m01_2 = (double)m00*m00 + (double)m01*m01; |
| 196 | this.m10_2_m11_2 = (double)m10*m10 + (double)m11*m11; |
| 197 | this.m00_m10_m01_m11 = (double)m00*m10 + (double)m01*m11; |
| 198 | |
| 199 | double dm00 = m00/65536.0; |
| 200 | double dm01 = m01/65536.0; |
| 201 | double dm10 = m10/65536.0; |
| 202 | double dm11 = m11/65536.0; |
| 203 | double determinant = dm00*dm11 - dm01*dm10; |
| 204 | |
| 205 | if (joinStyle == JOIN_MITER) { |
| 206 | double limit = |
| 207 | (miterLimit/65536.0)*(lineWidth2/65536.0)*determinant; |
| 208 | double limitSq = limit*limit; |
| 209 | this.miterLimitSq = (long)(limitSq*65536.0*65536.0); |
| 210 | } |
| 211 | |
| 212 | this.numPenSegments = (int)(3.14159f*lineWidth/65536.0f); |
| 213 | if (pen_dx == null || pen_dx.length < numPenSegments) { |
| 214 | this.pen_dx = new int[numPenSegments]; |
| 215 | this.pen_dy = new int[numPenSegments]; |
| 216 | this.penIncluded = new boolean[numPenSegments]; |
| 217 | this.join = new int[2*numPenSegments]; |
| 218 | } |
| 219 | |
| 220 | for (int i = 0; i < numPenSegments; i++) { |
| 221 | double r = lineWidth/2.0; |
| 222 | double theta = (double)i*2.0*Math.PI/numPenSegments; |
| 223 | |
| 224 | double cos = Math.cos(theta); |
| 225 | double sin = Math.sin(theta); |
| 226 | pen_dx[i] = (int)(r*(dm00*cos + dm01*sin)); |
| 227 | pen_dy[i] = (int)(r*(dm10*cos + dm11*sin)); |
| 228 | } |
| 229 | |
| 230 | prev = CLOSE; |
| 231 | rindex = 0; |
| 232 | started = false; |
| 233 | lineToOrigin = false; |
| 234 | } |
| 235 | |
| 236 | private void computeOffset(int x0, int y0, int x1, int y1, int[] m) { |
| 237 | long lx = (long)x1 - (long)x0; |
| 238 | long ly = (long)y1 - (long)y0; |
| 239 | |
| 240 | int dx, dy; |
| 241 | if (m00 > 0 && m00 == m11 && m01 == 0 & m10 == 0) { |
| 242 | long ilen = PiscesMath.hypot(lx, ly); |
| 243 | if (ilen == 0) { |
| 244 | dx = dy = 0; |
| 245 | } else { |
| 246 | dx = (int)( (ly*scaledLineWidth2)/ilen >> 16); |
| 247 | dy = (int)(-(lx*scaledLineWidth2)/ilen >> 16); |
| 248 | } |
| 249 | } else { |
| 250 | double dlx = x1 - x0; |
| 251 | double dly = y1 - y0; |
| 252 | double det = (double)m00*m11 - (double)m01*m10; |
| 253 | int sdet = (det > 0) ? 1 : -1; |
| 254 | double a = dly*m00 - dlx*m10; |
| 255 | double b = dly*m01 - dlx*m11; |
| 256 | double dh = PiscesMath.hypot(a, b); |
| 257 | double div = sdet*lineWidth2/(65536.0*dh); |
| 258 | double ddx = dly*m00_2_m01_2 - dlx*m00_m10_m01_m11; |
| 259 | double ddy = dly*m00_m10_m01_m11 - dlx*m10_2_m11_2; |
| 260 | dx = (int)(ddx*div); |
| 261 | dy = (int)(ddy*div); |
| 262 | } |
| 263 | |
| 264 | m[0] = dx; |
| 265 | m[1] = dy; |
| 266 | } |
| 267 | |
| 268 | private void ensureCapacity(int newrindex) { |
| 269 | if (reverse.length < newrindex) { |
| 270 | int[] tmp = new int[Math.max(newrindex, 6*reverse.length/5)]; |
| 271 | System.arraycopy(reverse, 0, tmp, 0, rindex); |
| 272 | this.reverse = tmp; |
| 273 | } |
| 274 | } |
| 275 | |
| 276 | private boolean isCCW(int x0, int y0, |
| 277 | int x1, int y1, |
| 278 | int x2, int y2) { |
| 279 | int dx0 = x1 - x0; |
| 280 | int dy0 = y1 - y0; |
| 281 | int dx1 = x2 - x1; |
| 282 | int dy1 = y2 - y1; |
| 283 | return (long)dx0*dy1 < (long)dy0*dx1; |
| 284 | } |
| 285 | |
| 286 | private boolean side(int x, int y, int x0, int y0, int x1, int y1) { |
| 287 | long lx = x; |
| 288 | long ly = y; |
| 289 | long lx0 = x0; |
| 290 | long ly0 = y0; |
| 291 | long lx1 = x1; |
| 292 | long ly1 = y1; |
| 293 | |
| 294 | return (ly0 - ly1)*lx + (lx1 - lx0)*ly + (lx0*ly1 - lx1*ly0) > 0; |
| 295 | } |
| 296 | |
| 297 | private int computeRoundJoin(int cx, int cy, |
| 298 | int xa, int ya, |
| 299 | int xb, int yb, |
| 300 | int side, |
| 301 | boolean flip, |
| 302 | int[] join) { |
| 303 | int px, py; |
| 304 | int ncoords = 0; |
| 305 | |
| 306 | boolean centerSide; |
| 307 | if (side == 0) { |
| 308 | centerSide = side(cx, cy, xa, ya, xb, yb); |
| 309 | } else { |
| 310 | centerSide = (side == 1) ? true : false; |
| 311 | } |
| 312 | for (int i = 0; i < numPenSegments; i++) { |
| 313 | px = cx + pen_dx[i]; |
| 314 | py = cy + pen_dy[i]; |
| 315 | |
| 316 | boolean penSide = side(px, py, xa, ya, xb, yb); |
| 317 | if (penSide != centerSide) { |
| 318 | penIncluded[i] = true; |
| 319 | } else { |
| 320 | penIncluded[i] = false; |
| 321 | } |
| 322 | } |
| 323 | |
| 324 | int start = -1, end = -1; |
| 325 | for (int i = 0; i < numPenSegments; i++) { |
| 326 | if (penIncluded[i] && |
| 327 | !penIncluded[(i + numPenSegments - 1) % numPenSegments]) { |
| 328 | start = i; |
| 329 | } |
| 330 | if (penIncluded[i] && |
| 331 | !penIncluded[(i + 1) % numPenSegments]) { |
| 332 | end = i; |
| 333 | } |
| 334 | } |
| 335 | |
| 336 | if (end < start) { |
| 337 | end += numPenSegments; |
| 338 | } |
| 339 | |
| 340 | if (start != -1 && end != -1) { |
| 341 | long dxa = cx + pen_dx[start] - xa; |
| 342 | long dya = cy + pen_dy[start] - ya; |
| 343 | long dxb = cx + pen_dx[start] - xb; |
| 344 | long dyb = cy + pen_dy[start] - yb; |
| 345 | |
| 346 | boolean rev = (dxa*dxa + dya*dya > dxb*dxb + dyb*dyb); |
| 347 | int i = rev ? end : start; |
| 348 | int incr = rev ? -1 : 1; |
| 349 | while (true) { |
| 350 | int idx = i % numPenSegments; |
| 351 | px = cx + pen_dx[idx]; |
| 352 | py = cy + pen_dy[idx]; |
| 353 | join[ncoords++] = px; |
| 354 | join[ncoords++] = py; |
| 355 | if (i == (rev ? start : end)) { |
| 356 | break; |
| 357 | } |
| 358 | i += incr; |
| 359 | } |
| 360 | } |
| 361 | |
| 362 | return ncoords/2; |
| 363 | } |
| 364 | |
| 365 | private static final long ROUND_JOIN_THRESHOLD = 1000L; |
| 366 | private static final long ROUND_JOIN_INTERNAL_THRESHOLD = 1000000000L; |
| 367 | |
| 368 | private void drawRoundJoin(int x, int y, |
| 369 | int omx, int omy, int mx, int my, |
| 370 | int side, |
| 371 | boolean flip, |
| 372 | boolean rev, |
| 373 | long threshold) { |
| 374 | if ((omx == 0 && omy == 0) || (mx == 0 && my == 0)) { |
| 375 | return; |
| 376 | } |
| 377 | |
| 378 | long domx = (long)omx - mx; |
| 379 | long domy = (long)omy - my; |
| 380 | long len = domx*domx + domy*domy; |
| 381 | if (len < threshold) { |
| 382 | return; |
| 383 | } |
| 384 | |
| 385 | if (rev) { |
| 386 | omx = -omx; |
| 387 | omy = -omy; |
| 388 | mx = -mx; |
| 389 | my = -my; |
| 390 | } |
| 391 | |
| 392 | int bx0 = x + omx; |
| 393 | int by0 = y + omy; |
| 394 | int bx1 = x + mx; |
| 395 | int by1 = y + my; |
| 396 | |
| 397 | int npoints = computeRoundJoin(x, y, |
| 398 | bx0, by0, bx1, by1, side, flip, |
| 399 | join); |
| 400 | for (int i = 0; i < npoints; i++) { |
| 401 | emitLineTo(join[2*i], join[2*i + 1], rev); |
| 402 | } |
| 403 | } |
| 404 | |
| 405 | // Return the intersection point of the lines (ix0, iy0) -> (ix1, iy1) |
| 406 | // and (ix0p, iy0p) -> (ix1p, iy1p) in m[0] and m[1] |
| 407 | private void computeMiter(int ix0, int iy0, int ix1, int iy1, |
| 408 | int ix0p, int iy0p, int ix1p, int iy1p, |
| 409 | int[] m) { |
| 410 | long x0 = ix0; |
| 411 | long y0 = iy0; |
| 412 | long x1 = ix1; |
| 413 | long y1 = iy1; |
| 414 | |
| 415 | long x0p = ix0p; |
| 416 | long y0p = iy0p; |
| 417 | long x1p = ix1p; |
| 418 | long y1p = iy1p; |
| 419 | |
| 420 | long x10 = x1 - x0; |
| 421 | long y10 = y1 - y0; |
| 422 | long x10p = x1p - x0p; |
| 423 | long y10p = y1p - y0p; |
| 424 | |
| 425 | long den = (x10*y10p - x10p*y10) >> 16; |
| 426 | if (den == 0) { |
| 427 | m[0] = ix0; |
| 428 | m[1] = iy0; |
| 429 | return; |
| 430 | } |
| 431 | |
| 432 | long t = (x1p*(y0 - y0p) - x0*y10p + x0p*(y1p - y0)) >> 16; |
| 433 | m[0] = (int)(x0 + (t*x10)/den); |
| 434 | m[1] = (int)(y0 + (t*y10)/den); |
| 435 | } |
| 436 | |
| 437 | private void drawMiter(int px0, int py0, |
| 438 | int x0, int y0, |
| 439 | int x1, int y1, |
| 440 | int omx, int omy, int mx, int my, |
| 441 | boolean rev) { |
| 442 | if (mx == omx && my == omy) { |
| 443 | return; |
| 444 | } |
| 445 | if (px0 == x0 && py0 == y0) { |
| 446 | return; |
| 447 | } |
| 448 | if (x0 == x1 && y0 == y1) { |
| 449 | return; |
| 450 | } |
| 451 | |
| 452 | if (rev) { |
| 453 | omx = -omx; |
| 454 | omy = -omy; |
| 455 | mx = -mx; |
| 456 | my = -my; |
| 457 | } |
| 458 | |
| 459 | computeMiter(px0 + omx, py0 + omy, x0 + omx, y0 + omy, |
| 460 | x0 + mx, y0 + my, x1 + mx, y1 + my, |
| 461 | miter); |
| 462 | |
| 463 | // Compute miter length in untransformed coordinates |
| 464 | long dx = (long)miter[0] - x0; |
| 465 | long dy = (long)miter[1] - y0; |
| 466 | long a = (dy*m00 - dx*m10) >> 16; |
| 467 | long b = (dy*m01 - dx*m11) >> 16; |
| 468 | long lenSq = a*a + b*b; |
| 469 | |
| 470 | if (lenSq < miterLimitSq) { |
| 471 | emitLineTo(miter[0], miter[1], rev); |
| 472 | } |
| 473 | } |
| 474 | |
| 475 | |
| 476 | public void moveTo(int x0, int y0) { |
| 477 | // System.out.println("Stroker.moveTo(" + x0/65536.0 + ", " + y0/65536.0 + ")"); |
| 478 | |
| 479 | if (lineToOrigin) { |
| 480 | // not closing the path, do the previous lineTo |
| 481 | lineToImpl(sx0, sy0, joinToOrigin); |
| 482 | lineToOrigin = false; |
| 483 | } |
| 484 | |
| 485 | if (prev == LINE_TO) { |
| 486 | finish(); |
| 487 | } |
| 488 | |
| 489 | this.sx0 = this.x0 = x0; |
| 490 | this.sy0 = this.y0 = y0; |
| 491 | this.rindex = 0; |
| 492 | this.started = false; |
| 493 | this.joinSegment = false; |
| 494 | this.prev = MOVE_TO; |
| 495 | } |
| 496 | |
| 497 | boolean joinSegment = false; |
| 498 | |
| 499 | public void lineJoin() { |
| 500 | // System.out.println("Stroker.lineJoin()"); |
| 501 | this.joinSegment = true; |
| 502 | } |
| 503 | |
| 504 | public void lineTo(int x1, int y1) { |
| 505 | // System.out.println("Stroker.lineTo(" + x1/65536.0 + ", " + y1/65536.0 + ")"); |
| 506 | |
| 507 | if (lineToOrigin) { |
| 508 | if (x1 == sx0 && y1 == sy0) { |
| 509 | // staying in the starting point |
| 510 | return; |
| 511 | } |
| 512 | |
| 513 | // not closing the path, do the previous lineTo |
| 514 | lineToImpl(sx0, sy0, joinToOrigin); |
| 515 | lineToOrigin = false; |
| 516 | } else if (x1 == x0 && y1 == y0) { |
| 517 | return; |
| 518 | } else if (x1 == sx0 && y1 == sy0) { |
| 519 | lineToOrigin = true; |
| 520 | joinToOrigin = joinSegment; |
| 521 | joinSegment = false; |
| 522 | return; |
| 523 | } |
| 524 | |
| 525 | lineToImpl(x1, y1, joinSegment); |
| 526 | joinSegment = false; |
| 527 | } |
| 528 | |
| 529 | private void lineToImpl(int x1, int y1, boolean joinSegment) { |
| 530 | computeOffset(x0, y0, x1, y1, offset); |
| 531 | int mx = offset[0]; |
| 532 | int my = offset[1]; |
| 533 | |
| 534 | if (!started) { |
| 535 | emitMoveTo(x0 + mx, y0 + my); |
| 536 | this.sx1 = x1; |
| 537 | this.sy1 = y1; |
| 538 | this.mx0 = mx; |
| 539 | this.my0 = my; |
| 540 | started = true; |
| 541 | } else { |
| 542 | boolean ccw = isCCW(px0, py0, x0, y0, x1, y1); |
| 543 | if (joinSegment) { |
| 544 | if (joinStyle == JOIN_MITER) { |
| 545 | drawMiter(px0, py0, x0, y0, x1, y1, omx, omy, mx, my, |
| 546 | ccw); |
| 547 | } else if (joinStyle == JOIN_ROUND) { |
| 548 | drawRoundJoin(x0, y0, |
| 549 | omx, omy, |
| 550 | mx, my, 0, false, ccw, |
| 551 | ROUND_JOIN_THRESHOLD); |
| 552 | } |
| 553 | } else { |
| 554 | // Draw internal joins as round |
| 555 | drawRoundJoin(x0, y0, |
| 556 | omx, omy, |
| 557 | mx, my, 0, false, ccw, |
| 558 | ROUND_JOIN_INTERNAL_THRESHOLD); |
| 559 | } |
| 560 | |
| 561 | emitLineTo(x0, y0, !ccw); |
| 562 | } |
| 563 | |
| 564 | emitLineTo(x0 + mx, y0 + my, false); |
| 565 | emitLineTo(x1 + mx, y1 + my, false); |
| 566 | |
| 567 | emitLineTo(x0 - mx, y0 - my, true); |
| 568 | emitLineTo(x1 - mx, y1 - my, true); |
| 569 | |
| 570 | lx0 = x1 + mx; ly0 = y1 + my; |
| 571 | lx0p = x1 - mx; ly0p = y1 - my; |
| 572 | lx1 = x1; ly1 = y1; |
| 573 | |
| 574 | this.omx = mx; |
| 575 | this.omy = my; |
| 576 | this.px0 = x0; |
| 577 | this.py0 = y0; |
| 578 | this.x0 = x1; |
| 579 | this.y0 = y1; |
| 580 | this.prev = LINE_TO; |
| 581 | } |
| 582 | |
| 583 | public void close() { |
| 584 | // System.out.println("Stroker.close()"); |
| 585 | |
| 586 | if (lineToOrigin) { |
| 587 | // ignore the previous lineTo |
| 588 | lineToOrigin = false; |
| 589 | } |
| 590 | |
| 591 | if (!started) { |
| 592 | finish(); |
| 593 | return; |
| 594 | } |
| 595 | |
| 596 | computeOffset(x0, y0, sx0, sy0, offset); |
| 597 | int mx = offset[0]; |
| 598 | int my = offset[1]; |
| 599 | |
| 600 | // Draw penultimate join |
| 601 | boolean ccw = isCCW(px0, py0, x0, y0, sx0, sy0); |
| 602 | if (joinSegment) { |
| 603 | if (joinStyle == JOIN_MITER) { |
| 604 | drawMiter(px0, py0, x0, y0, sx0, sy0, omx, omy, mx, my, ccw); |
| 605 | } else if (joinStyle == JOIN_ROUND) { |
| 606 | drawRoundJoin(x0, y0, omx, omy, mx, my, 0, false, ccw, |
| 607 | ROUND_JOIN_THRESHOLD); |
| 608 | } |
| 609 | } else { |
| 610 | // Draw internal joins as round |
| 611 | drawRoundJoin(x0, y0, |
| 612 | omx, omy, |
| 613 | mx, my, 0, false, ccw, |
| 614 | ROUND_JOIN_INTERNAL_THRESHOLD); |
| 615 | } |
| 616 | |
| 617 | emitLineTo(x0 + mx, y0 + my); |
| 618 | emitLineTo(sx0 + mx, sy0 + my); |
| 619 | |
| 620 | ccw = isCCW(x0, y0, sx0, sy0, sx1, sy1); |
| 621 | |
| 622 | // Draw final join on the outside |
| 623 | if (!ccw) { |
| 624 | if (joinStyle == JOIN_MITER) { |
| 625 | drawMiter(x0, y0, sx0, sy0, sx1, sy1, |
| 626 | mx, my, mx0, my0, false); |
| 627 | } else if (joinStyle == JOIN_ROUND) { |
| 628 | drawRoundJoin(sx0, sy0, mx, my, mx0, my0, 0, false, false, |
| 629 | ROUND_JOIN_THRESHOLD); |
| 630 | } |
| 631 | } |
| 632 | |
| 633 | emitLineTo(sx0 + mx0, sy0 + my0); |
| 634 | emitLineTo(sx0 - mx0, sy0 - my0); // same as reverse[0], reverse[1] |
| 635 | |
| 636 | // Draw final join on the inside |
| 637 | if (ccw) { |
| 638 | if (joinStyle == JOIN_MITER) { |
| 639 | drawMiter(x0, y0, sx0, sy0, sx1, sy1, |
| 640 | -mx, -my, -mx0, -my0, false); |
| 641 | } else if (joinStyle == JOIN_ROUND) { |
| 642 | drawRoundJoin(sx0, sy0, -mx, -my, -mx0, -my0, 0, |
| 643 | true, false, |
| 644 | ROUND_JOIN_THRESHOLD); |
| 645 | } |
| 646 | } |
| 647 | |
| 648 | emitLineTo(sx0 - mx, sy0 - my); |
| 649 | emitLineTo(x0 - mx, y0 - my); |
| 650 | for (int i = rindex - 2; i >= 0; i -= 2) { |
| 651 | emitLineTo(reverse[i], reverse[i + 1]); |
| 652 | } |
| 653 | |
| 654 | this.x0 = this.sx0; |
| 655 | this.y0 = this.sy0; |
| 656 | this.rindex = 0; |
| 657 | this.started = false; |
| 658 | this.joinSegment = false; |
| 659 | this.prev = CLOSE; |
| 660 | emitClose(); |
| 661 | } |
| 662 | |
| 663 | public void end() { |
| 664 | // System.out.println("Stroker.end()"); |
| 665 | |
| 666 | if (lineToOrigin) { |
| 667 | // not closing the path, do the previous lineTo |
| 668 | lineToImpl(sx0, sy0, joinToOrigin); |
| 669 | lineToOrigin = false; |
| 670 | } |
| 671 | |
| 672 | if (prev == LINE_TO) { |
| 673 | finish(); |
| 674 | } |
| 675 | |
| 676 | output.end(); |
| 677 | this.joinSegment = false; |
| 678 | this.prev = MOVE_TO; |
| 679 | } |
| 680 | |
| 681 | long lineLength(long ldx, long ldy) { |
| 682 | long ldet = ((long)m00*m11 - (long)m01*m10) >> 16; |
| 683 | long la = ((long)ldy*m00 - (long)ldx*m10)/ldet; |
| 684 | long lb = ((long)ldy*m01 - (long)ldx*m11)/ldet; |
| 685 | long llen = (int)PiscesMath.hypot(la, lb); |
| 686 | return llen; |
| 687 | } |
| 688 | |
| 689 | private void finish() { |
| 690 | if (capStyle == CAP_ROUND) { |
| 691 | drawRoundJoin(x0, y0, |
| 692 | omx, omy, -omx, -omy, 1, false, false, |
| 693 | ROUND_JOIN_THRESHOLD); |
| 694 | } else if (capStyle == CAP_SQUARE) { |
| 695 | long ldx = (long)(px0 - x0); |
| 696 | long ldy = (long)(py0 - y0); |
| 697 | long llen = lineLength(ldx, ldy); |
| 698 | long s = (long)lineWidth2*65536/llen; |
| 699 | |
| 700 | int capx = x0 - (int)(ldx*s >> 16); |
| 701 | int capy = y0 - (int)(ldy*s >> 16); |
| 702 | |
| 703 | emitLineTo(capx + omx, capy + omy); |
| 704 | emitLineTo(capx - omx, capy - omy); |
| 705 | } |
| 706 | |
| 707 | for (int i = rindex - 2; i >= 0; i -= 2) { |
| 708 | emitLineTo(reverse[i], reverse[i + 1]); |
| 709 | } |
| 710 | this.rindex = 0; |
| 711 | |
| 712 | if (capStyle == CAP_ROUND) { |
| 713 | drawRoundJoin(sx0, sy0, |
| 714 | -mx0, -my0, mx0, my0, 1, false, false, |
| 715 | ROUND_JOIN_THRESHOLD); |
| 716 | } else if (capStyle == CAP_SQUARE) { |
| 717 | long ldx = (long)(sx1 - sx0); |
| 718 | long ldy = (long)(sy1 - sy0); |
| 719 | long llen = lineLength(ldx, ldy); |
| 720 | long s = (long)lineWidth2*65536/llen; |
| 721 | |
| 722 | int capx = sx0 - (int)(ldx*s >> 16); |
| 723 | int capy = sy0 - (int)(ldy*s >> 16); |
| 724 | |
| 725 | emitLineTo(capx - mx0, capy - my0); |
| 726 | emitLineTo(capx + mx0, capy + my0); |
| 727 | } |
| 728 | |
| 729 | emitClose(); |
| 730 | this.joinSegment = false; |
| 731 | } |
| 732 | |
| 733 | private void emitMoveTo(int x0, int y0) { |
| 734 | // System.out.println("Stroker.emitMoveTo(" + x0/65536.0 + ", " + y0/65536.0 + ")"); |
| 735 | output.moveTo(x0, y0); |
| 736 | } |
| 737 | |
| 738 | private void emitLineTo(int x1, int y1) { |
| 739 | // System.out.println("Stroker.emitLineTo(" + x0/65536.0 + ", " + y0/65536.0 + ")"); |
| 740 | output.lineTo(x1, y1); |
| 741 | } |
| 742 | |
| 743 | private void emitLineTo(int x1, int y1, boolean rev) { |
| 744 | if (rev) { |
| 745 | ensureCapacity(rindex + 2); |
| 746 | reverse[rindex++] = x1; |
| 747 | reverse[rindex++] = y1; |
| 748 | } else { |
| 749 | emitLineTo(x1, y1); |
| 750 | } |
| 751 | } |
| 752 | |
| 753 | private void emitClose() { |
| 754 | // System.out.println("Stroker.emitClose()"); |
| 755 | output.close(); |
| 756 | } |
| 757 | } |