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