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 | +} | |