1 /* 2 * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.marlin; 27 28 import java.util.Arrays; 29 30 import com.sun.javafx.geom.PathConsumer2D; 31 import com.sun.marlin.Helpers.PolyStack; 32 33 // TODO: some of the arithmetic here is too verbose and prone to hard to 34 // debug typos. We should consider making a small Point/Vector class that 35 // has methods like plus(Point), minus(Point), dot(Point), cross(Point)and such 36 public final class Stroker implements PathConsumer2D, MarlinConst { 37 38 private static final int MOVE_TO = 0; 39 private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad 40 private static final int CLOSE = 2; 41 42 // pisces used to use fixed point arithmetic with 16 decimal digits. I 43 // didn't want to change the values of the constant below when I converted 44 // it to floating point, so that's why the divisions by 2^16 are there. 45 private static final float ROUND_JOIN_THRESHOLD = 1000.0f/65536.0f; 46 47 // kappa = (4/3) * (SQRT(2) - 1) 48 private static final float C = (float)(4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d); 49 50 // SQRT(2) 51 private static final float SQRT_2 = (float)Math.sqrt(2.0d); 52 53 private static final int MAX_N_CURVES = 11; 54 55 private PathConsumer2D out; 56 57 private int capStyle; 58 private int joinStyle; 59 60 private float lineWidth2; 61 private float invHalfLineWidth2Sq; 62 63 private final float[] offset0 = new float[2]; 64 private final float[] offset1 = new float[2]; 65 private final float[] offset2 = new float[2]; 66 private final float[] miter = new float[2]; 67 private float miterLimitSq; 68 69 private int prev; 70 71 // The starting point of the path, and the slope there. 72 private float sx0, sy0, sdx, sdy; 73 // the current point and the slope there. 74 private float cx0, cy0, cdx, cdy; // c stands for current 75 // vectors that when added to (sx0,sy0) and (cx0,cy0) respectively yield the 76 // first and last points on the left parallel path. Since this path is 77 // parallel, it's slope at any point is parallel to the slope of the 78 // original path (thought they may have different directions), so these 79 // could be computed from sdx,sdy and cdx,cdy (and vice versa), but that 80 // would be error prone and hard to read, so we keep these anyway. 81 private float smx, smy, cmx, cmy; 82 83 private final PolyStack reverse; 84 85 // This is where the curve to be processed is put. We give it 86 // enough room to store all curves. 87 private final float[] middle = new float[MAX_N_CURVES * 6 + 2]; 88 private final float[] lp = new float[8]; 89 private final float[] rp = new float[8]; 90 private final float[] subdivTs = new float[MAX_N_CURVES - 1]; 91 92 // per-thread renderer context 93 final RendererContext rdrCtx; 94 95 // dirty curve 96 final Curve curve; 97 98 // Bounds of the drawing region, at pixel precision. 99 private float[] clipRect; 100 101 // the outcode of the current point 102 private int cOutCode = 0; 103 104 // the outcode of the starting point 105 private int sOutCode = 0; 106 107 // flag indicating if the path is opened (clipped) 108 private boolean opened = false; 109 // flag indicating if the starting point's cap is done 110 private boolean capStart = false; 111 112 /** 113 * Constructs a <code>Stroker</code>. 114 * @param rdrCtx per-thread renderer context 115 */ 116 Stroker(final RendererContext rdrCtx) { 117 this.rdrCtx = rdrCtx; 118 119 this.reverse = (rdrCtx.stats != null) ? 120 new PolyStack(rdrCtx, 121 rdrCtx.stats.stat_str_polystack_types, 122 rdrCtx.stats.stat_str_polystack_curves, 123 rdrCtx.stats.hist_str_polystack_curves, 124 rdrCtx.stats.stat_array_str_polystack_curves, 125 rdrCtx.stats.stat_array_str_polystack_types) 126 : new PolyStack(rdrCtx); 127 128 this.curve = rdrCtx.curve; 129 } 130 131 /** 132 * Inits the <code>Stroker</code>. 133 * 134 * @param pc2d an output <code>PathConsumer2D</code>. 135 * @param lineWidth the desired line width in pixels 136 * @param capStyle the desired end cap style, one of 137 * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or 138 * <code>CAP_SQUARE</code>. 139 * @param joinStyle the desired line join style, one of 140 * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or 141 * <code>JOIN_BEVEL</code>. 142 * @param miterLimit the desired miter limit 143 * @param scale scaling factor applied to clip boundaries 144 * @param rdrOffX renderer's coordinate offset on X axis 145 * @param rdrOffY renderer's coordinate offset on Y axis 146 * @return this instance 147 */ 148 public Stroker init(final PathConsumer2D pc2d, 149 final float lineWidth, 150 final int capStyle, 151 final int joinStyle, 152 final float miterLimit, 153 final float scale, 154 double rdrOffX, 155 double rdrOffY) 156 { 157 this.out = pc2d; 158 159 this.lineWidth2 = lineWidth / 2.0f; 160 this.invHalfLineWidth2Sq = 1.0f / (2.0f * lineWidth2 * lineWidth2); 161 this.capStyle = capStyle; 162 this.joinStyle = joinStyle; 163 164 final float limit = miterLimit * lineWidth2; 165 this.miterLimitSq = limit * limit; 166 167 this.prev = CLOSE; 168 169 rdrCtx.stroking = 1; 170 171 if (rdrCtx.doClip) { 172 // Adjust the clipping rectangle with the stroker margin (miter limit, width) 173 float margin = lineWidth2; 174 175 if (capStyle == CAP_SQUARE) { 176 margin *= SQRT_2; 177 } 178 if ((joinStyle == JOIN_MITER) && (margin < limit)) { 179 margin = limit; 180 } 181 if (scale != 1.0f) { 182 margin *= scale; 183 rdrOffX *= scale; 184 rdrOffY *= scale; 185 } 186 // add a small rounding error: 187 margin += 1e-3f; 188 189 // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY 190 // adjust clip rectangle (ymin, ymax, xmin, xmax): 191 final float[] _clipRect = rdrCtx.clipRect; 192 _clipRect[0] -= margin - rdrOffY; 193 _clipRect[1] += margin + rdrOffY; 194 _clipRect[2] -= margin - rdrOffX; 195 _clipRect[3] += margin + rdrOffX; 196 this.clipRect = _clipRect; 197 } else { 198 this.clipRect = null; 199 this.cOutCode = 0; 200 this.sOutCode = 0; 201 } 202 return this; // fluent API 203 } 204 205 /** 206 * Disposes this stroker: 207 * clean up before reusing this instance 208 */ 209 void dispose() { 210 reverse.dispose(); 211 212 opened = false; 213 capStart = false; 214 215 if (DO_CLEAN_DIRTY) { 216 // Force zero-fill dirty arrays: 217 Arrays.fill(offset0, 0.0f); 218 Arrays.fill(offset1, 0.0f); 219 Arrays.fill(offset2, 0.0f); 220 Arrays.fill(miter, 0.0f); 221 Arrays.fill(middle, 0.0f); 222 Arrays.fill(lp, 0.0f); 223 Arrays.fill(rp, 0.0f); 224 Arrays.fill(subdivTs, 0.0f); 225 } 226 } 227 228 private static void computeOffset(final float lx, final float ly, 229 final float w, final float[] m) 230 { 231 float len = lx*lx + ly*ly; 232 if (len == 0.0f) { 233 m[0] = 0.0f; 234 m[1] = 0.0f; 235 } else { 236 len = (float) Math.sqrt(len); 237 m[0] = (ly * w) / len; 238 m[1] = -(lx * w) / len; 239 } 240 } 241 242 // Returns true if the vectors (dx1, dy1) and (dx2, dy2) are 243 // clockwise (if dx1,dy1 needs to be rotated clockwise to close 244 // the smallest angle between it and dx2,dy2). 245 // This is equivalent to detecting whether a point q is on the right side 246 // of a line passing through points p1, p2 where p2 = p1+(dx1,dy1) and 247 // q = p2+(dx2,dy2), which is the same as saying p1, p2, q are in a 248 // clockwise order. 249 // NOTE: "clockwise" here assumes coordinates with 0,0 at the bottom left. 250 private static boolean isCW(final float dx1, final float dy1, 251 final float dx2, final float dy2) 252 { 253 return dx1 * dy2 <= dy1 * dx2; 254 } 255 256 private void drawRoundJoin(float x, float y, 257 float omx, float omy, float mx, float my, 258 boolean rev, 259 float threshold) 260 { 261 if ((omx == 0.0f && omy == 0.0f) || (mx == 0.0f && my == 0.0f)) { 262 return; 263 } 264 265 float domx = omx - mx; 266 float domy = omy - my; 267 float len = domx*domx + domy*domy; 268 if (len < threshold) { 269 return; 270 } 271 272 if (rev) { 273 omx = -omx; 274 omy = -omy; 275 mx = -mx; 276 my = -my; 277 } 278 drawRoundJoin(x, y, omx, omy, mx, my, rev); 279 } 280 281 private void drawRoundJoin(float cx, float cy, 282 float omx, float omy, 283 float mx, float my, 284 boolean rev) 285 { 286 // The sign of the dot product of mx,my and omx,omy is equal to the 287 // the sign of the cosine of ext 288 // (ext is the angle between omx,omy and mx,my). 289 final float cosext = omx * mx + omy * my; 290 // If it is >=0, we know that abs(ext) is <= 90 degrees, so we only 291 // need 1 curve to approximate the circle section that joins omx,omy 292 // and mx,my. 293 final int numCurves = (cosext >= 0.0f) ? 1 : 2; 294 295 switch (numCurves) { 296 case 1: 297 drawBezApproxForArc(cx, cy, omx, omy, mx, my, rev); 298 break; 299 case 2: 300 // we need to split the arc into 2 arcs spanning the same angle. 301 // The point we want will be one of the 2 intersections of the 302 // perpendicular bisector of the chord (omx,omy)->(mx,my) and the 303 // circle. We could find this by scaling the vector 304 // (omx+mx, omy+my)/2 so that it has length=lineWidth2 (and thus lies 305 // on the circle), but that can have numerical problems when the angle 306 // between omx,omy and mx,my is close to 180 degrees. So we compute a 307 // normal of (omx,omy)-(mx,my). This will be the direction of the 308 // perpendicular bisector. To get one of the intersections, we just scale 309 // this vector that its length is lineWidth2 (this works because the 310 // perpendicular bisector goes through the origin). This scaling doesn't 311 // have numerical problems because we know that lineWidth2 divided by 312 // this normal's length is at least 0.5 and at most sqrt(2)/2 (because 313 // we know the angle of the arc is > 90 degrees). 314 float nx = my - omy, ny = omx - mx; 315 float nlen = (float) Math.sqrt(nx*nx + ny*ny); 316 float scale = lineWidth2/nlen; 317 float mmx = nx * scale, mmy = ny * scale; 318 319 // if (isCW(omx, omy, mx, my) != isCW(mmx, mmy, mx, my)) then we've 320 // computed the wrong intersection so we get the other one. 321 // The test above is equivalent to if (rev). 322 if (rev) { 323 mmx = -mmx; 324 mmy = -mmy; 325 } 326 drawBezApproxForArc(cx, cy, omx, omy, mmx, mmy, rev); 327 drawBezApproxForArc(cx, cy, mmx, mmy, mx, my, rev); 328 break; 329 default: 330 } 331 } 332 333 // the input arc defined by omx,omy and mx,my must span <= 90 degrees. 334 private void drawBezApproxForArc(final float cx, final float cy, 335 final float omx, final float omy, 336 final float mx, final float my, 337 boolean rev) 338 { 339 final float cosext2 = (omx * mx + omy * my) * invHalfLineWidth2Sq; 340 341 // check round off errors producing cos(ext) > 1 and a NaN below 342 // cos(ext) == 1 implies colinear segments and an empty join anyway 343 if (cosext2 >= 0.5f) { 344 // just return to avoid generating a flat curve: 345 return; 346 } 347 348 // cv is the length of P1-P0 and P2-P3 divided by the radius of the arc 349 // (so, cv assumes the arc has radius 1). P0, P1, P2, P3 are the points that 369 emitCurveTo(x1, y1, x2, y2, x3, y3, x4, y4, rev); 370 } 371 372 private void drawRoundCap(float cx, float cy, float mx, float my) { 373 final float Cmx = C * mx; 374 final float Cmy = C * my; 375 emitCurveTo(cx + mx - Cmy, cy + my + Cmx, 376 cx - my + Cmx, cy + mx + Cmy, 377 cx - my, cy + mx); 378 emitCurveTo(cx - my - Cmx, cy + mx - Cmy, 379 cx - mx - Cmy, cy - my + Cmx, 380 cx - mx, cy - my); 381 } 382 383 // Return the intersection point of the lines (x0, y0) -> (x1, y1) 384 // and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1] 385 private static void computeMiter(final float x0, final float y0, 386 final float x1, final float y1, 387 final float x0p, final float y0p, 388 final float x1p, final float y1p, 389 final float[] m, int off) 390 { 391 float x10 = x1 - x0; 392 float y10 = y1 - y0; 393 float x10p = x1p - x0p; 394 float y10p = y1p - y0p; 395 396 // if this is 0, the lines are parallel. If they go in the 397 // same direction, there is no intersection so m[off] and 398 // m[off+1] will contain infinity, so no miter will be drawn. 399 // If they go in the same direction that means that the start of the 400 // current segment and the end of the previous segment have the same 401 // tangent, in which case this method won't even be involved in 402 // miter drawing because it won't be called by drawMiter (because 403 // (mx == omx && my == omy) will be true, and drawMiter will return 404 // immediately). 405 float den = x10*y10p - x10p*y10; 406 float t = x10p*(y0-y0p) - y10p*(x0-x0p); 407 t /= den; 408 m[off++] = x0 + t*x10; 409 m[off] = y0 + t*y10; 410 } 411 412 // Return the intersection point of the lines (x0, y0) -> (x1, y1) 413 // and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1] 414 private static void safeComputeMiter(final float x0, final float y0, 415 final float x1, final float y1, 416 final float x0p, final float y0p, 417 final float x1p, final float y1p, 418 final float[] m, int off) 419 { 420 float x10 = x1 - x0; 421 float y10 = y1 - y0; 422 float x10p = x1p - x0p; 423 float y10p = y1p - y0p; 424 425 // if this is 0, the lines are parallel. If they go in the 426 // same direction, there is no intersection so m[off] and 427 // m[off+1] will contain infinity, so no miter will be drawn. 428 // If they go in the same direction that means that the start of the 429 // current segment and the end of the previous segment have the same 430 // tangent, in which case this method won't even be involved in 431 // miter drawing because it won't be called by drawMiter (because 432 // (mx == omx && my == omy) will be true, and drawMiter will return 433 // immediately). 434 float den = x10*y10p - x10p*y10; 435 if (den == 0.0f) { 436 m[off++] = (x0 + x0p) / 2.0f; 437 m[off] = (y0 + y0p) / 2.0f; 438 return; 439 } 440 float t = x10p*(y0-y0p) - y10p*(x0-x0p); 441 t /= den; 442 m[off++] = x0 + t*x10; 443 m[off] = y0 + t*y10; 444 } 445 446 private void drawMiter(final float pdx, final float pdy, 447 final float x0, final float y0, 448 final float dx, final float dy, 449 float omx, float omy, float mx, float my, 450 boolean rev) 451 { 452 if ((mx == omx && my == omy) || 453 (pdx == 0.0f && pdy == 0.0f) || 454 (dx == 0.0f && dy == 0.0f)) 455 { 456 return; 457 } 458 459 if (rev) { 460 omx = -omx; 461 omy = -omy; 462 mx = -mx; 463 my = -my; 464 } 465 466 computeMiter((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy, 467 (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my, 468 miter, 0); 469 470 final float miterX = miter[0]; 471 final float miterY = miter[1]; 472 float lenSq = (miterX-x0)*(miterX-x0) + (miterY-y0)*(miterY-y0); 473 474 // If the lines are parallel, lenSq will be either NaN or +inf 475 // (actually, I'm not sure if the latter is possible. The important 476 // thing is that -inf is not possible, because lenSq is a square). 477 // For both of those values, the comparison below will fail and 478 // no miter will be drawn, which is correct. 479 if (lenSq < miterLimitSq) { 480 emitLineTo(miterX, miterY, rev); 481 } 482 } 483 484 @Override 485 public void moveTo(final float x0, final float y0) { 486 moveTo(x0, y0, cOutCode); 487 // update starting point: 488 this.sx0 = x0; 489 this.sy0 = y0; 490 this.sdx = 1.0f; 491 this.sdy = 0.0f; 492 this.opened = false; 493 this.capStart = false; 494 495 if (clipRect != null) { 496 final int outcode = Helpers.outcode(x0, y0, clipRect); 497 this.cOutCode = outcode; 498 this.sOutCode = outcode; 499 } 500 } 501 502 private void moveTo(final float x0, final float y0, 503 final int outcode) 504 { 505 if (prev == MOVE_TO) { 506 this.cx0 = x0; 507 this.cy0 = y0; 508 } else { 509 if (prev == DRAWING_OP_TO) { 510 finish(outcode); 511 } 512 this.prev = MOVE_TO; 513 this.cx0 = x0; 514 this.cy0 = y0; 515 this.cdx = 1.0f; 516 this.cdy = 0.0f; 517 } 518 } 519 520 @Override 521 public void lineTo(final float x1, final float y1) { 522 lineTo(x1, y1, false); 523 } 524 525 private void lineTo(final float x1, final float y1, 526 final boolean force) 527 { 528 final int outcode0 = this.cOutCode; 529 if (!force && clipRect != null) { 530 final int outcode1 = Helpers.outcode(x1, y1, clipRect); 531 this.cOutCode = outcode1; 532 533 // basic rejection criteria 534 if ((outcode0 & outcode1) != 0) { 535 moveTo(x1, y1, outcode0); 536 opened = true; 537 return; 538 } 539 } 540 541 float dx = x1 - cx0; 542 float dy = y1 - cy0; 543 if (dx == 0.0f && dy == 0.0f) { 544 dx = 1.0f; 545 } 546 computeOffset(dx, dy, lineWidth2, offset0); 547 final float mx = offset0[0]; 548 final float my = offset0[1]; 549 550 drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my, outcode0); 551 552 emitLineTo(cx0 + mx, cy0 + my); 553 emitLineTo( x1 + mx, y1 + my); 554 555 emitLineToRev(cx0 - mx, cy0 - my); 556 emitLineToRev( x1 - mx, y1 - my); 557 558 this.prev = DRAWING_OP_TO; 740 float x0, float y0, 741 float dx, float dy, 742 float omx, float omy, 743 float mx, float my, 744 final int outcode) 745 { 746 if (prev != DRAWING_OP_TO) { 747 emitMoveTo(x0 + mx, y0 + my); 748 if (!opened) { 749 this.sdx = dx; 750 this.sdy = dy; 751 this.smx = mx; 752 this.smy = my; 753 } 754 } else { 755 final boolean cw = isCW(pdx, pdy, dx, dy); 756 if (outcode == 0) { 757 if (joinStyle == JOIN_MITER) { 758 drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw); 759 } else if (joinStyle == JOIN_ROUND) { 760 drawRoundJoin(x0, y0, 761 omx, omy, 762 mx, my, cw, 763 ROUND_JOIN_THRESHOLD); 764 } 765 } 766 emitLineTo(x0, y0, !cw); 767 } 768 prev = DRAWING_OP_TO; 769 } 770 771 private static boolean within(final float x1, final float y1, 772 final float x2, final float y2, 773 final float ERR) 774 { 775 assert ERR > 0 : ""; 776 // compare taxicab distance. ERR will always be small, so using 777 // true distance won't give much benefit 778 return (Helpers.within(x1, x2, ERR) && // we want to avoid calling Math.abs 779 Helpers.within(y1, y2, ERR)); // this is just as good. 780 } 781 782 private void getLineOffsets(float x1, float y1, 783 float x2, float y2, 784 float[] left, float[] right) { 785 computeOffset(x2 - x1, y2 - y1, lineWidth2, offset0); 786 final float mx = offset0[0]; 787 final float my = offset0[1]; 788 left[0] = x1 + mx; 789 left[1] = y1 + my; 790 left[2] = x2 + mx; 791 left[3] = y2 + my; 792 right[0] = x1 - mx; 793 right[1] = y1 - my; 794 right[2] = x2 - mx; 795 right[3] = y2 - my; 796 } 797 798 private int computeOffsetCubic(float[] pts, final int off, 799 float[] leftOff, float[] rightOff) 800 { 801 // if p1=p2 or p3=p4 it means that the derivative at the endpoint 802 // vanishes, which creates problems with computeOffset. Usually 803 // this happens when this stroker object is trying to widen 804 // a curve with a cusp. What happens is that curveTo splits 805 // the input curve at the cusp, and passes it to this function. 806 // because of inaccuracies in the splitting, we consider points 807 // equal if they're very close to each other. 808 final float x1 = pts[off + 0], y1 = pts[off + 1]; 809 final float x2 = pts[off + 2], y2 = pts[off + 3]; 810 final float x3 = pts[off + 4], y3 = pts[off + 5]; 811 final float x4 = pts[off + 6], y4 = pts[off + 7]; 812 813 float dx4 = x4 - x3; 814 float dy4 = y4 - y3; 815 float dx1 = x2 - x1; 816 float dy1 = y2 - y1; 817 818 // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4, 819 // in which case ignore if p1 == p2 820 final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0f * Math.ulp(y2)); 821 final boolean p3eqp4 = within(x3, y3, x4, y4, 6.0f * Math.ulp(y4)); 822 if (p1eqp2 && p3eqp4) { 823 getLineOffsets(x1, y1, x4, y4, leftOff, rightOff); 824 return 4; 825 } else if (p1eqp2) { 826 dx1 = x3 - x1; 827 dy1 = y3 - y1; 828 } else if (p3eqp4) { 829 dx4 = x4 - x2; 830 dy4 = y4 - y2; 831 } 832 833 // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line 834 float dotsq = (dx1 * dx4 + dy1 * dy4); 835 dotsq *= dotsq; 836 float l1sq = dx1 * dx1 + dy1 * dy1, l4sq = dx4 * dx4 + dy4 * dy4; 837 if (Helpers.within(dotsq, l1sq * l4sq, 4.0f * Math.ulp(dotsq))) { 838 getLineOffsets(x1, y1, x4, y4, leftOff, rightOff); 839 return 4; 840 } 841 842 // What we're trying to do in this function is to approximate an ideal 843 // offset curve (call it I) of the input curve B using a bezier curve Bp. 844 // The constraints I use to get the equations are: 845 // 846 // 1. The computed curve Bp should go through I(0) and I(1). These are 847 // x1p, y1p, x4p, y4p, which are p1p and p4p. We still need to find 848 // 4 variables: the x and y components of p2p and p3p (i.e. x2p, y2p, x3p, y3p). 849 // 850 // 2. Bp should have slope equal in absolute value to I at the endpoints. So, 851 // (by the way, the operator || in the comments below means "aligned with". 852 // It is defined on vectors, so when we say I'(0) || Bp'(0) we mean that 853 // vectors I'(0) and Bp'(0) are aligned, which is the same as saying 854 // that the tangent lines of I and Bp at 0 are parallel. Mathematically 855 // this means (I'(t) || Bp'(t)) <==> (I'(t) = c * Bp'(t)) where c is some 856 // nonzero constant.) 930 two_pi_m_p1_m_p4x = 2.0f * xi - x1p - x4p; 931 two_pi_m_p1_m_p4y = 2.0f * yi - y1p - y4p; 932 c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y); 933 c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x); 934 935 x2p = x1p + c1*dx1; 936 y2p = y1p + c1*dy1; 937 x3p = x4p + c2*dx4; 938 y3p = y4p + c2*dy4; 939 940 rightOff[0] = x1p; rightOff[1] = y1p; 941 rightOff[2] = x2p; rightOff[3] = y2p; 942 rightOff[4] = x3p; rightOff[5] = y3p; 943 rightOff[6] = x4p; rightOff[7] = y4p; 944 return 8; 945 } 946 947 // compute offset curves using bezier spline through t=0.5 (i.e. 948 // ComputedCurve(0.5) == IdealParallelCurve(0.5)) 949 // return the kind of curve in the right and left arrays. 950 private int computeOffsetQuad(float[] pts, final int off, 951 float[] leftOff, float[] rightOff) 952 { 953 final float x1 = pts[off + 0], y1 = pts[off + 1]; 954 final float x2 = pts[off + 2], y2 = pts[off + 3]; 955 final float x3 = pts[off + 4], y3 = pts[off + 5]; 956 957 final float dx3 = x3 - x2; 958 final float dy3 = y3 - y2; 959 final float dx1 = x2 - x1; 960 final float dy1 = y2 - y1; 961 962 // if p1=p2 or p3=p4 it means that the derivative at the endpoint 963 // vanishes, which creates problems with computeOffset. Usually 964 // this happens when this stroker object is trying to widen 965 // a curve with a cusp. What happens is that curveTo splits 966 // the input curve at the cusp, and passes it to this function. 967 // because of inaccuracies in the splitting, we consider points 968 // equal if they're very close to each other. 969 970 // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4, 971 // in which case ignore. 972 final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0f * Math.ulp(y2)); 973 final boolean p2eqp3 = within(x2, y2, x3, y3, 6.0f * Math.ulp(y3)); 974 if (p1eqp2 || p2eqp3) { 975 getLineOffsets(x1, y1, x3, y3, leftOff, rightOff); 976 return 4; 977 } 978 979 // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line 980 float dotsq = (dx1 * dx3 + dy1 * dy3); 981 dotsq *= dotsq; 982 float l1sq = dx1 * dx1 + dy1 * dy1, l3sq = dx3 * dx3 + dy3 * dy3; 983 if (Helpers.within(dotsq, l1sq * l3sq, 4.0f * Math.ulp(dotsq))) { 984 getLineOffsets(x1, y1, x3, y3, leftOff, rightOff); 985 return 4; 986 } 987 988 // this computes the offsets at t=0, 0.5, 1, using the property that 989 // for any bezier curve the vectors p2-p1 and p4-p3 are parallel to 990 // the (dx/dt, dy/dt) vectors at the endpoints. 991 computeOffset(dx1, dy1, lineWidth2, offset0); 992 computeOffset(dx3, dy3, lineWidth2, offset1); 993 994 float x1p = x1 + offset0[0]; // start 995 float y1p = y1 + offset0[1]; // point 996 float x3p = x3 + offset1[0]; // end 997 float y3p = y3 + offset1[1]; // point 998 safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, leftOff, 2); 999 leftOff[0] = x1p; leftOff[1] = y1p; 1000 leftOff[4] = x3p; leftOff[5] = y3p; 1001 1002 x1p = x1 - offset0[0]; y1p = y1 - offset0[1]; 1003 x3p = x3 - offset1[0]; y3p = y3 - offset1[1]; 1004 safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, rightOff, 2); 1005 rightOff[0] = x1p; rightOff[1] = y1p; 1006 rightOff[4] = x3p; rightOff[5] = y3p; 1007 return 6; 1008 } 1009 1010 // finds values of t where the curve in pts should be subdivided in order 1011 // to get good offset curves a distance of w away from the middle curve. 1012 // Stores the points in ts, and returns how many of them there were. 1013 private static int findSubdivPoints(final Curve c, float[] pts, float[] ts, 1014 final int type, final float w) 1015 { 1016 final float x12 = pts[2] - pts[0]; 1017 final float y12 = pts[3] - pts[1]; 1018 // if the curve is already parallel to either axis we gain nothing 1019 // from rotating it. 1020 if (y12 != 0.0f && x12 != 0.0f) { 1021 // we rotate it so that the first vector in the control polygon is 1022 // parallel to the x-axis. This will ensure that rotated quarter 1023 // circles won't be subdivided. 1024 final float hypot = (float) Math.sqrt(x12 * x12 + y12 * y12); 1025 final float cos = x12 / hypot; 1026 final float sin = y12 / hypot; 1027 final float x1 = cos * pts[0] + sin * pts[1]; 1028 final float y1 = cos * pts[1] - sin * pts[0]; 1029 final float x2 = cos * pts[2] + sin * pts[3]; 1030 final float y2 = cos * pts[3] - sin * pts[2]; 1031 final float x3 = cos * pts[4] + sin * pts[5]; 1032 final float y3 = cos * pts[5] - sin * pts[4]; 1033 1034 switch(type) { 1035 case 8: 1036 final float x4 = cos * pts[6] + sin * pts[7]; 1037 final float y4 = cos * pts[7] - sin * pts[6]; 1038 c.set(x1, y1, x2, y2, x3, y3, x4, y4); 1039 break; 1040 case 6: 1041 c.set(x1, y1, x2, y2, x3, y3); 1042 break; 1043 default: 1044 } 1045 } else { 1046 c.set(pts, type); 1047 } 1048 1049 int ret = 0; 1050 // we subdivide at values of t such that the remaining rotated 1051 // curves are monotonic in x and y. 1052 ret += c.dxRoots(ts, ret); 1053 ret += c.dyRoots(ts, ret); 1054 // subdivide at inflection points. 1055 if (type == 8) { 1056 // quadratic curves can't have inflection points 1057 ret += c.infPoints(ts, ret); 1058 } 1059 1060 // now we must subdivide at points where one of the offset curves will have 1061 // a cusp. This happens at ts where the radius of curvature is equal to w. 1062 ret += c.rootsOfROCMinusW(ts, ret, w, 0.0001f); 1063 1064 ret = Helpers.filterOutNotInAB(ts, 0, ret, 0.0001f, 0.9999f); 1065 Helpers.isort(ts, 0, ret); 1066 return ret; 1067 } 1068 1069 @Override 1070 public void curveTo(final float x1, final float y1, 1071 final float x2, final float y2, 1072 final float x3, final float y3) 1073 { 1074 final int outcode0 = this.cOutCode; 1075 if (clipRect != null) { 1076 final int outcode3 = Helpers.outcode(x3, y3, clipRect); 1077 this.cOutCode = outcode3; 1078 1079 if ((outcode0 & outcode3) != 0) { 1080 final int outcode1 = Helpers.outcode(x1, y1, clipRect); 1081 final int outcode2 = Helpers.outcode(x2, y2, clipRect); 1082 1083 // basic rejection criteria 1084 if ((outcode0 & outcode1 & outcode2 & outcode3) != 0) { 1085 moveTo(x3, y3, outcode0); 1086 opened = true; 1087 return; 1088 } 1089 } 1090 } 1091 1092 final float[] mid = middle; 1093 1094 mid[0] = cx0; mid[1] = cy0; 1095 mid[2] = x1; mid[3] = y1; 1096 mid[4] = x2; mid[5] = y2; 1097 mid[6] = x3; mid[7] = y3; 1098 1099 // need these so we can update the state at the end of this method 1100 final float xf = x3, yf = y3; 1101 float dxs = mid[2] - mid[0]; 1102 float dys = mid[3] - mid[1]; 1103 float dxf = mid[6] - mid[4]; 1104 float dyf = mid[7] - mid[5]; 1105 1106 boolean p1eqp2 = (dxs == 0.0f && dys == 0.0f); 1107 boolean p3eqp4 = (dxf == 0.0f && dyf == 0.0f); 1108 if (p1eqp2) { 1109 dxs = mid[4] - mid[0]; 1110 dys = mid[5] - mid[1]; 1111 if (dxs == 0.0f && dys == 0.0f) { 1112 dxs = mid[6] - mid[0]; 1113 dys = mid[7] - mid[1]; 1114 } 1115 } 1116 if (p3eqp4) { 1117 dxf = mid[6] - mid[2]; 1118 dyf = mid[7] - mid[3]; 1119 if (dxf == 0.0f && dyf == 0.0f) { 1120 dxf = mid[6] - mid[0]; 1121 dyf = mid[7] - mid[1]; 1122 } 1123 } 1124 if (dxs == 0.0f && dys == 0.0f) { 1125 // this happens if the "curve" is just a point 1126 // fix outcode0 for lineTo() call: 1127 if (clipRect != null) { 1128 this.cOutCode = outcode0; 1129 } 1130 lineTo(mid[0], mid[1]); 1131 return; 1132 } 1133 1134 // if these vectors are too small, normalize them, to avoid future 1135 // precision problems. 1136 if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) { 1137 float len = (float) Math.sqrt(dxs*dxs + dys*dys); 1138 dxs /= len; 1139 dys /= len; 1140 } 1141 if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) { 1142 float len = (float) Math.sqrt(dxf*dxf + dyf*dyf); 1143 dxf /= len; 1144 dyf /= len; 1145 } 1146 1147 computeOffset(dxs, dys, lineWidth2, offset0); 1148 drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0); 1149 1150 final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2); 1151 1152 float prevT = 0.0f; 1153 for (int i = 0, off = 0; i < nSplits; i++, off += 6) { 1154 final float t = subdivTs[i]; 1155 Helpers.subdivideCubicAt((t - prevT) / (1.0f - prevT), 1156 mid, off, mid, off, mid, off + 6); 1157 prevT = t; 1158 } 1159 1160 final float[] l = lp; 1161 final float[] r = rp; 1162 1163 int kind = 0; 1164 for (int i = 0, off = 0; i <= nSplits; i++, off += 6) { 1165 kind = computeOffsetCubic(mid, off, l, r); 1166 1167 emitLineTo(l[0], l[1]); 1168 1169 switch(kind) { 1170 case 8: 1171 emitCurveTo(l[2], l[3], l[4], l[5], l[6], l[7]); 1172 emitCurveToRev(r[0], r[1], r[2], r[3], r[4], r[5]); 1173 break; 1174 case 4: 1175 emitLineTo(l[2], l[3]); 1176 emitLineToRev(r[0], r[1]); 1177 break; 1178 default: 1179 } 1180 emitLineToRev(r[kind - 2], r[kind - 1]); 1181 } 1182 1183 this.prev = DRAWING_OP_TO; 1184 this.cx0 = xf; 1185 this.cy0 = yf; 1186 this.cdx = dxf; 1187 this.cdy = dyf; 1188 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f; 1189 this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f; 1190 } 1191 1192 @Override 1193 public void quadTo(final float x1, final float y1, 1194 final float x2, final float y2) 1195 { 1196 final int outcode0 = this.cOutCode; 1197 if (clipRect != null) { 1198 final int outcode2 = Helpers.outcode(x2, y2, clipRect); 1199 this.cOutCode = outcode2; 1200 1201 if ((outcode0 & outcode2) != 0) { 1202 final int outcode1 = Helpers.outcode(x1, y1, clipRect); 1203 1204 // basic rejection criteria 1205 if ((outcode0 & outcode1 & outcode2) != 0) { 1206 moveTo(x2, y2, outcode0); 1207 opened = true; 1208 return; 1209 } 1210 } 1211 } 1212 1213 final float[] mid = middle; 1214 1215 mid[0] = cx0; mid[1] = cy0; 1216 mid[2] = x1; mid[3] = y1; 1217 mid[4] = x2; mid[5] = y2; 1218 1219 // need these so we can update the state at the end of this method 1220 final float xf = x2, yf = y2; 1221 float dxs = mid[2] - mid[0]; 1222 float dys = mid[3] - mid[1]; 1223 float dxf = mid[4] - mid[2]; 1224 float dyf = mid[5] - mid[3]; 1225 if ((dxs == 0.0f && dys == 0.0f) || (dxf == 0.0f && dyf == 0.0f)) { 1226 dxs = dxf = mid[4] - mid[0]; 1227 dys = dyf = mid[5] - mid[1]; 1228 } 1229 if (dxs == 0.0f && dys == 0.0f) { 1230 // this happens if the "curve" is just a point 1231 // fix outcode0 for lineTo() call: 1232 if (clipRect != null) { 1233 this.cOutCode = outcode0; 1234 } 1235 lineTo(mid[0], mid[1]); 1236 return; 1237 } 1238 // if these vectors are too small, normalize them, to avoid future 1239 // precision problems. 1240 if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) { 1241 float len = (float) Math.sqrt(dxs*dxs + dys*dys); 1242 dxs /= len; 1243 dys /= len; 1244 } 1245 if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) { 1246 float len = (float) Math.sqrt(dxf*dxf + dyf*dyf); 1247 dxf /= len; 1248 dyf /= len; 1249 } 1250 1251 computeOffset(dxs, dys, lineWidth2, offset0); 1252 drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0); 1253 1254 int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2); 1255 1256 float prevt = 0.0f; 1257 for (int i = 0, off = 0; i < nSplits; i++, off += 4) { 1258 final float t = subdivTs[i]; 1259 Helpers.subdivideQuadAt((t - prevt) / (1.0f - prevt), 1260 mid, off, mid, off, mid, off + 4); 1261 prevt = t; 1262 } 1263 1264 final float[] l = lp; 1265 final float[] r = rp; 1266 1267 int kind = 0; 1268 for (int i = 0, off = 0; i <= nSplits; i++, off += 4) { 1269 kind = computeOffsetQuad(mid, off, l, r); 1270 1271 emitLineTo(l[0], l[1]); 1272 1273 switch(kind) { 1274 case 6: 1275 emitQuadTo(l[2], l[3], l[4], l[5]); 1276 emitQuadToRev(r[0], r[1], r[2], r[3]); 1277 break; 1278 case 4: 1279 emitLineTo(l[2], l[3]); 1280 emitLineToRev(r[0], r[1]); 1281 break; 1282 default: 1283 } 1284 emitLineToRev(r[kind - 2], r[kind - 1]); 1285 } 1286 1287 this.prev = DRAWING_OP_TO; 1288 this.cx0 = xf; 1289 this.cy0 = yf; 1290 this.cdx = dxf; 1291 this.cdy = dyf; 1292 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f; 1293 this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f; 1294 } 1295 1296 } | 1 /* 2 * Copyright (c) 2007, 2018, 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 com.sun.marlin; 27 28 import java.util.Arrays; 29 30 import com.sun.javafx.geom.PathConsumer2D; 31 import com.sun.marlin.Helpers.PolyStack; 32 import com.sun.marlin.TransformingPathConsumer2D.CurveBasicMonotonizer; 33 import com.sun.marlin.TransformingPathConsumer2D.CurveClipSplitter; 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 public final class Stroker implements PathConsumer2D, MarlinConst { 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 // round join threshold = 1 subpixel 45 private static final float ERR_JOIN = (1.0f / MIN_SUBPIXELS); 46 private static final float ROUND_JOIN_THRESHOLD = ERR_JOIN * ERR_JOIN; 47 48 // kappa = (4/3) * (SQRT(2) - 1) 49 private static final float C = (float)(4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d); 50 51 // SQRT(2) 52 private static final float SQRT_2 = (float)Math.sqrt(2.0d); 53 54 private PathConsumer2D out; 55 56 private int capStyle; 57 private int joinStyle; 58 59 private float lineWidth2; 60 private float invHalfLineWidth2Sq; 61 62 private final float[] offset0 = new float[2]; 63 private final float[] offset1 = new float[2]; 64 private final float[] offset2 = new float[2]; 65 private final float[] miter = new float[2]; 66 private float miterLimitSq; 67 68 private int prev; 69 70 // The starting point of the path, and the slope there. 71 private float sx0, sy0, sdx, sdy; 72 // the current point and the slope there. 73 private float cx0, cy0, cdx, cdy; // c stands for current 74 // vectors that when added to (sx0,sy0) and (cx0,cy0) respectively yield the 75 // first and last points on the left parallel path. Since this path is 76 // parallel, it's slope at any point is parallel to the slope of the 77 // original path (thought they may have different directions), so these 78 // could be computed from sdx,sdy and cdx,cdy (and vice versa), but that 79 // would be error prone and hard to read, so we keep these anyway. 80 private float smx, smy, cmx, cmy; 81 82 private final PolyStack reverse; 83 84 private final float[] lp = new float[8]; 85 private final float[] rp = new float[8]; 86 87 // per-thread renderer context 88 final RendererContext rdrCtx; 89 90 // dirty curve 91 final Curve curve; 92 93 // Bounds of the drawing region, at pixel precision. 94 private float[] clipRect; 95 96 // the outcode of the current point 97 private int cOutCode = 0; 98 99 // the outcode of the starting point 100 private int sOutCode = 0; 101 102 // flag indicating if the path is opened (clipped) 103 private boolean opened = false; 104 // flag indicating if the starting point's cap is done 105 private boolean capStart = false; 106 // flag indicating to monotonize curves 107 private boolean monotonize; 108 109 private boolean subdivide = DO_CLIP_SUBDIVIDER; 110 private final CurveClipSplitter curveSplitter; 111 112 /** 113 * Constructs a <code>Stroker</code>. 114 * @param rdrCtx per-thread renderer context 115 */ 116 Stroker(final RendererContext rdrCtx) { 117 this.rdrCtx = rdrCtx; 118 119 this.reverse = (rdrCtx.stats != null) ? 120 new PolyStack(rdrCtx, 121 rdrCtx.stats.stat_str_polystack_types, 122 rdrCtx.stats.stat_str_polystack_curves, 123 rdrCtx.stats.hist_str_polystack_curves, 124 rdrCtx.stats.stat_array_str_polystack_curves, 125 rdrCtx.stats.stat_array_str_polystack_types) 126 : new PolyStack(rdrCtx); 127 128 this.curve = rdrCtx.curve; 129 this.curveSplitter = rdrCtx.curveClipSplitter; 130 } 131 132 /** 133 * Inits the <code>Stroker</code>. 134 * 135 * @param pc2d an output <code>PathConsumer2D</code>. 136 * @param lineWidth the desired line width in pixels 137 * @param capStyle the desired end cap style, one of 138 * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or 139 * <code>CAP_SQUARE</code>. 140 * @param joinStyle the desired line join style, one of 141 * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or 142 * <code>JOIN_BEVEL</code>. 143 * @param miterLimit the desired miter limit 144 * @param scale scaling factor applied to clip boundaries 145 * @param rdrOffX renderer's coordinate offset on X axis 146 * @param rdrOffY renderer's coordinate offset on Y axis 147 * @param subdivideCurves true to indicate to subdivide curves, false if dasher does 148 * @return this instance 149 */ 150 public Stroker init(final PathConsumer2D pc2d, 151 final float lineWidth, 152 final int capStyle, 153 final int joinStyle, 154 final float miterLimit, 155 final float scale, 156 double rdrOffX, 157 double rdrOffY, 158 final boolean subdivideCurves) 159 { 160 this.out = pc2d; 161 162 this.lineWidth2 = lineWidth / 2.0f; 163 this.invHalfLineWidth2Sq = 1.0f / (2.0f * lineWidth2 * lineWidth2); 164 this.monotonize = subdivideCurves; 165 166 this.capStyle = capStyle; 167 this.joinStyle = joinStyle; 168 169 final float limit = miterLimit * lineWidth2; 170 this.miterLimitSq = limit * limit; 171 172 this.prev = CLOSE; 173 174 rdrCtx.stroking = 1; 175 176 if (rdrCtx.doClip) { 177 // Adjust the clipping rectangle with the stroker margin (miter limit, width) 178 float margin = lineWidth2; 179 180 if (capStyle == CAP_SQUARE) { 181 margin *= SQRT_2; 182 } 183 if ((joinStyle == JOIN_MITER) && (margin < limit)) { 184 margin = limit; 185 } 186 if (scale != 1.0f) { 187 margin *= scale; 188 rdrOffX *= scale; 189 rdrOffY *= scale; 190 } 191 // add a small rounding error: 192 margin += 1e-3f; 193 194 // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY 195 // adjust clip rectangle (ymin, ymax, xmin, xmax): 196 final float[] _clipRect = rdrCtx.clipRect; 197 _clipRect[0] -= margin - rdrOffY; 198 _clipRect[1] += margin + rdrOffY; 199 _clipRect[2] -= margin - rdrOffX; 200 _clipRect[3] += margin + rdrOffX; 201 this.clipRect = _clipRect; 202 203 // initialize curve splitter here for stroker & dasher: 204 if (DO_CLIP_SUBDIVIDER) { 205 subdivide = subdivideCurves; 206 // adjust padded clip rectangle: 207 curveSplitter.init(); 208 } else { 209 subdivide = false; 210 } 211 } else { 212 this.clipRect = null; 213 this.cOutCode = 0; 214 this.sOutCode = 0; 215 } 216 return this; // fluent API 217 } 218 219 public void disableClipping() { 220 this.clipRect = null; 221 this.cOutCode = 0; 222 this.sOutCode = 0; 223 } 224 225 /** 226 * Disposes this stroker: 227 * clean up before reusing this instance 228 */ 229 void dispose() { 230 reverse.dispose(); 231 232 opened = false; 233 capStart = false; 234 235 if (DO_CLEAN_DIRTY) { 236 // Force zero-fill dirty arrays: 237 Arrays.fill(offset0, 0.0f); 238 Arrays.fill(offset1, 0.0f); 239 Arrays.fill(offset2, 0.0f); 240 Arrays.fill(miter, 0.0f); 241 Arrays.fill(lp, 0.0f); 242 Arrays.fill(rp, 0.0f); 243 } 244 } 245 246 private static void computeOffset(final float lx, final float ly, 247 final float w, final float[] m) 248 { 249 float len = lx*lx + ly*ly; 250 if (len == 0.0f) { 251 m[0] = 0.0f; 252 m[1] = 0.0f; 253 } else { 254 len = (float) Math.sqrt(len); 255 m[0] = (ly * w) / len; 256 m[1] = -(lx * w) / len; 257 } 258 } 259 260 // Returns true if the vectors (dx1, dy1) and (dx2, dy2) are 261 // clockwise (if dx1,dy1 needs to be rotated clockwise to close 262 // the smallest angle between it and dx2,dy2). 263 // This is equivalent to detecting whether a point q is on the right side 264 // of a line passing through points p1, p2 where p2 = p1+(dx1,dy1) and 265 // q = p2+(dx2,dy2), which is the same as saying p1, p2, q are in a 266 // clockwise order. 267 // NOTE: "clockwise" here assumes coordinates with 0,0 at the bottom left. 268 private static boolean isCW(final float dx1, final float dy1, 269 final float dx2, final float dy2) 270 { 271 return dx1 * dy2 <= dy1 * dx2; 272 } 273 274 private void mayDrawRoundJoin(float cx, float cy, 275 float omx, float omy, 276 float mx, float my, 277 boolean rev) 278 { 279 if ((omx == 0.0f && omy == 0.0f) || (mx == 0.0f && my == 0.0f)) { 280 return; 281 } 282 283 final float domx = omx - mx; 284 final float domy = omy - my; 285 final float lenSq = domx*domx + domy*domy; 286 287 if (lenSq < ROUND_JOIN_THRESHOLD) { 288 return; 289 } 290 291 if (rev) { 292 omx = -omx; 293 omy = -omy; 294 mx = -mx; 295 my = -my; 296 } 297 drawRoundJoin(cx, cy, omx, omy, mx, my, rev); 298 } 299 300 private void drawRoundJoin(float cx, float cy, 301 float omx, float omy, 302 float mx, float my, 303 boolean rev) 304 { 305 // The sign of the dot product of mx,my and omx,omy is equal to the 306 // the sign of the cosine of ext 307 // (ext is the angle between omx,omy and mx,my). 308 final float cosext = omx * mx + omy * my; 309 // If it is >=0, we know that abs(ext) is <= 90 degrees, so we only 310 // need 1 curve to approximate the circle section that joins omx,omy 311 // and mx,my. 312 if (cosext >= 0.0f) { 313 drawBezApproxForArc(cx, cy, omx, omy, mx, my, rev); 314 } else { 315 // we need to split the arc into 2 arcs spanning the same angle. 316 // The point we want will be one of the 2 intersections of the 317 // perpendicular bisector of the chord (omx,omy)->(mx,my) and the 318 // circle. We could find this by scaling the vector 319 // (omx+mx, omy+my)/2 so that it has length=lineWidth2 (and thus lies 320 // on the circle), but that can have numerical problems when the angle 321 // between omx,omy and mx,my is close to 180 degrees. So we compute a 322 // normal of (omx,omy)-(mx,my). This will be the direction of the 323 // perpendicular bisector. To get one of the intersections, we just scale 324 // this vector that its length is lineWidth2 (this works because the 325 // perpendicular bisector goes through the origin). This scaling doesn't 326 // have numerical problems because we know that lineWidth2 divided by 327 // this normal's length is at least 0.5 and at most sqrt(2)/2 (because 328 // we know the angle of the arc is > 90 degrees). 329 float nx = my - omy, ny = omx - mx; 330 float nlen = (float) Math.sqrt(nx*nx + ny*ny); 331 float scale = lineWidth2/nlen; 332 float mmx = nx * scale, mmy = ny * scale; 333 334 // if (isCW(omx, omy, mx, my) != isCW(mmx, mmy, mx, my)) then we've 335 // computed the wrong intersection so we get the other one. 336 // The test above is equivalent to if (rev). 337 if (rev) { 338 mmx = -mmx; 339 mmy = -mmy; 340 } 341 drawBezApproxForArc(cx, cy, omx, omy, mmx, mmy, rev); 342 drawBezApproxForArc(cx, cy, mmx, mmy, mx, my, rev); 343 } 344 } 345 346 // the input arc defined by omx,omy and mx,my must span <= 90 degrees. 347 private void drawBezApproxForArc(final float cx, final float cy, 348 final float omx, final float omy, 349 final float mx, final float my, 350 boolean rev) 351 { 352 final float cosext2 = (omx * mx + omy * my) * invHalfLineWidth2Sq; 353 354 // check round off errors producing cos(ext) > 1 and a NaN below 355 // cos(ext) == 1 implies colinear segments and an empty join anyway 356 if (cosext2 >= 0.5f) { 357 // just return to avoid generating a flat curve: 358 return; 359 } 360 361 // cv is the length of P1-P0 and P2-P3 divided by the radius of the arc 362 // (so, cv assumes the arc has radius 1). P0, P1, P2, P3 are the points that 382 emitCurveTo(x1, y1, x2, y2, x3, y3, x4, y4, rev); 383 } 384 385 private void drawRoundCap(float cx, float cy, float mx, float my) { 386 final float Cmx = C * mx; 387 final float Cmy = C * my; 388 emitCurveTo(cx + mx - Cmy, cy + my + Cmx, 389 cx - my + Cmx, cy + mx + Cmy, 390 cx - my, cy + mx); 391 emitCurveTo(cx - my - Cmx, cy + mx - Cmy, 392 cx - mx - Cmy, cy - my + Cmx, 393 cx - mx, cy - my); 394 } 395 396 // Return the intersection point of the lines (x0, y0) -> (x1, y1) 397 // and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1] 398 private static void computeMiter(final float x0, final float y0, 399 final float x1, final float y1, 400 final float x0p, final float y0p, 401 final float x1p, final float y1p, 402 final float[] m) 403 { 404 float x10 = x1 - x0; 405 float y10 = y1 - y0; 406 float x10p = x1p - x0p; 407 float y10p = y1p - y0p; 408 409 // if this is 0, the lines are parallel. If they go in the 410 // same direction, there is no intersection so m[off] and 411 // m[off+1] will contain infinity, so no miter will be drawn. 412 // If they go in the same direction that means that the start of the 413 // current segment and the end of the previous segment have the same 414 // tangent, in which case this method won't even be involved in 415 // miter drawing because it won't be called by drawMiter (because 416 // (mx == omx && my == omy) will be true, and drawMiter will return 417 // immediately). 418 float den = x10*y10p - x10p*y10; 419 float t = x10p*(y0-y0p) - y10p*(x0-x0p); 420 t /= den; 421 m[0] = x0 + t*x10; 422 m[1] = y0 + t*y10; 423 } 424 425 // Return the intersection point of the lines (x0, y0) -> (x1, y1) 426 // and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1] 427 private static void safeComputeMiter(final float x0, final float y0, 428 final float x1, final float y1, 429 final float x0p, final float y0p, 430 final float x1p, final float y1p, 431 final float[] m) 432 { 433 float x10 = x1 - x0; 434 float y10 = y1 - y0; 435 float x10p = x1p - x0p; 436 float y10p = y1p - y0p; 437 438 // if this is 0, the lines are parallel. If they go in the 439 // same direction, there is no intersection so m[off] and 440 // m[off+1] will contain infinity, so no miter will be drawn. 441 // If they go in the same direction that means that the start of the 442 // current segment and the end of the previous segment have the same 443 // tangent, in which case this method won't even be involved in 444 // miter drawing because it won't be called by drawMiter (because 445 // (mx == omx && my == omy) will be true, and drawMiter will return 446 // immediately). 447 float den = x10*y10p - x10p*y10; 448 if (den == 0.0f) { 449 m[2] = (x0 + x0p) / 2.0f; 450 m[3] = (y0 + y0p) / 2.0f; 451 } else { 452 float t = x10p*(y0-y0p) - y10p*(x0-x0p); 453 t /= den; 454 m[2] = x0 + t*x10; 455 m[3] = y0 + t*y10; 456 } 457 } 458 459 private void drawMiter(final float pdx, final float pdy, 460 final float x0, final float y0, 461 final float dx, final float dy, 462 float omx, float omy, 463 float mx, float my, 464 boolean rev) 465 { 466 if ((mx == omx && my == omy) || 467 (pdx == 0.0f && pdy == 0.0f) || 468 (dx == 0.0f && dy == 0.0f)) 469 { 470 return; 471 } 472 473 if (rev) { 474 omx = -omx; 475 omy = -omy; 476 mx = -mx; 477 my = -my; 478 } 479 480 computeMiter((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy, 481 (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my, miter); 482 483 final float miterX = miter[0]; 484 final float miterY = miter[1]; 485 float lenSq = (miterX-x0)*(miterX-x0) + (miterY-y0)*(miterY-y0); 486 487 // If the lines are parallel, lenSq will be either NaN or +inf 488 // (actually, I'm not sure if the latter is possible. The important 489 // thing is that -inf is not possible, because lenSq is a square). 490 // For both of those values, the comparison below will fail and 491 // no miter will be drawn, which is correct. 492 if (lenSq < miterLimitSq) { 493 emitLineTo(miterX, miterY, rev); 494 } 495 } 496 497 @Override 498 public void moveTo(final float x0, final float y0) { 499 _moveTo(x0, y0, cOutCode); 500 // update starting point: 501 this.sx0 = x0; 502 this.sy0 = y0; 503 this.sdx = 1.0f; 504 this.sdy = 0.0f; 505 this.opened = false; 506 this.capStart = false; 507 508 if (clipRect != null) { 509 final int outcode = Helpers.outcode(x0, y0, clipRect); 510 this.cOutCode = outcode; 511 this.sOutCode = outcode; 512 } 513 } 514 515 private void _moveTo(final float x0, final float y0, 516 final int outcode) 517 { 518 if (prev == MOVE_TO) { 519 this.cx0 = x0; 520 this.cy0 = y0; 521 } else { 522 if (prev == DRAWING_OP_TO) { 523 finish(outcode); 524 } 525 this.prev = MOVE_TO; 526 this.cx0 = x0; 527 this.cy0 = y0; 528 this.cdx = 1.0f; 529 this.cdy = 0.0f; 530 } 531 } 532 533 @Override 534 public void lineTo(final float x1, final float y1) { 535 lineTo(x1, y1, false); 536 } 537 538 private void lineTo(final float x1, final float y1, 539 final boolean force) 540 { 541 final int outcode0 = this.cOutCode; 542 543 if (!force && clipRect != null) { 544 final int outcode1 = Helpers.outcode(x1, y1, clipRect); 545 546 // Should clip 547 final int orCode = (outcode0 | outcode1); 548 if (orCode != 0) { 549 final int sideCode = outcode0 & outcode1; 550 551 // basic rejection criteria: 552 if (sideCode == 0) { 553 // ovelap clip: 554 if (subdivide) { 555 // avoid reentrance 556 subdivide = false; 557 // subdivide curve => callback with subdivided parts: 558 boolean ret = curveSplitter.splitLine(cx0, cy0, x1, y1, 559 orCode, this); 560 // reentrance is done: 561 subdivide = true; 562 if (ret) { 563 return; 564 } 565 } 566 // already subdivided so render it 567 } else { 568 this.cOutCode = outcode1; 569 _moveTo(x1, y1, outcode0); 570 opened = true; 571 return; 572 } 573 } 574 575 this.cOutCode = outcode1; 576 } 577 578 float dx = x1 - cx0; 579 float dy = y1 - cy0; 580 if (dx == 0.0f && dy == 0.0f) { 581 dx = 1.0f; 582 } 583 computeOffset(dx, dy, lineWidth2, offset0); 584 final float mx = offset0[0]; 585 final float my = offset0[1]; 586 587 drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my, outcode0); 588 589 emitLineTo(cx0 + mx, cy0 + my); 590 emitLineTo( x1 + mx, y1 + my); 591 592 emitLineToRev(cx0 - mx, cy0 - my); 593 emitLineToRev( x1 - mx, y1 - my); 594 595 this.prev = DRAWING_OP_TO; 777 float x0, float y0, 778 float dx, float dy, 779 float omx, float omy, 780 float mx, float my, 781 final int outcode) 782 { 783 if (prev != DRAWING_OP_TO) { 784 emitMoveTo(x0 + mx, y0 + my); 785 if (!opened) { 786 this.sdx = dx; 787 this.sdy = dy; 788 this.smx = mx; 789 this.smy = my; 790 } 791 } else { 792 final boolean cw = isCW(pdx, pdy, dx, dy); 793 if (outcode == 0) { 794 if (joinStyle == JOIN_MITER) { 795 drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw); 796 } else if (joinStyle == JOIN_ROUND) { 797 mayDrawRoundJoin(x0, y0, omx, omy, mx, my, cw); 798 } 799 } 800 emitLineTo(x0, y0, !cw); 801 } 802 prev = DRAWING_OP_TO; 803 } 804 805 private static boolean within(final float x1, final float y1, 806 final float x2, final float y2, 807 final float err) 808 { 809 assert err > 0 : ""; 810 // compare taxicab distance. ERR will always be small, so using 811 // true distance won't give much benefit 812 return (Helpers.within(x1, x2, err) && // we want to avoid calling Math.abs 813 Helpers.within(y1, y2, err)); // this is just as good. 814 } 815 816 private void getLineOffsets(final float x1, final float y1, 817 final float x2, final float y2, 818 final float[] left, final float[] right) 819 { 820 computeOffset(x2 - x1, y2 - y1, lineWidth2, offset0); 821 final float mx = offset0[0]; 822 final float my = offset0[1]; 823 left[0] = x1 + mx; 824 left[1] = y1 + my; 825 left[2] = x2 + mx; 826 left[3] = y2 + my; 827 828 right[0] = x1 - mx; 829 right[1] = y1 - my; 830 right[2] = x2 - mx; 831 right[3] = y2 - my; 832 } 833 834 private int computeOffsetCubic(final float[] pts, final int off, 835 final float[] leftOff, 836 final float[] rightOff) 837 { 838 // if p1=p2 or p3=p4 it means that the derivative at the endpoint 839 // vanishes, which creates problems with computeOffset. Usually 840 // this happens when this stroker object is trying to widen 841 // a curve with a cusp. What happens is that curveTo splits 842 // the input curve at the cusp, and passes it to this function. 843 // because of inaccuracies in the splitting, we consider points 844 // equal if they're very close to each other. 845 final float x1 = pts[off ], y1 = pts[off + 1]; 846 final float x2 = pts[off + 2], y2 = pts[off + 3]; 847 final float x3 = pts[off + 4], y3 = pts[off + 5]; 848 final float x4 = pts[off + 6], y4 = pts[off + 7]; 849 850 float dx4 = x4 - x3; 851 float dy4 = y4 - y3; 852 float dx1 = x2 - x1; 853 float dy1 = y2 - y1; 854 855 // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4, 856 // in which case ignore if p1 == p2 857 final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0f * Math.ulp(y2)); 858 final boolean p3eqp4 = within(x3, y3, x4, y4, 6.0f * Math.ulp(y4)); 859 860 if (p1eqp2 && p3eqp4) { 861 getLineOffsets(x1, y1, x4, y4, leftOff, rightOff); 862 return 4; 863 } else if (p1eqp2) { 864 dx1 = x3 - x1; 865 dy1 = y3 - y1; 866 } else if (p3eqp4) { 867 dx4 = x4 - x2; 868 dy4 = y4 - y2; 869 } 870 871 // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line 872 float dotsq = (dx1 * dx4 + dy1 * dy4); 873 dotsq *= dotsq; 874 float l1sq = dx1 * dx1 + dy1 * dy1, l4sq = dx4 * dx4 + dy4 * dy4; 875 876 if (Helpers.within(dotsq, l1sq * l4sq, 4.0f * Math.ulp(dotsq))) { 877 getLineOffsets(x1, y1, x4, y4, leftOff, rightOff); 878 return 4; 879 } 880 881 // What we're trying to do in this function is to approximate an ideal 882 // offset curve (call it I) of the input curve B using a bezier curve Bp. 883 // The constraints I use to get the equations are: 884 // 885 // 1. The computed curve Bp should go through I(0) and I(1). These are 886 // x1p, y1p, x4p, y4p, which are p1p and p4p. We still need to find 887 // 4 variables: the x and y components of p2p and p3p (i.e. x2p, y2p, x3p, y3p). 888 // 889 // 2. Bp should have slope equal in absolute value to I at the endpoints. So, 890 // (by the way, the operator || in the comments below means "aligned with". 891 // It is defined on vectors, so when we say I'(0) || Bp'(0) we mean that 892 // vectors I'(0) and Bp'(0) are aligned, which is the same as saying 893 // that the tangent lines of I and Bp at 0 are parallel. Mathematically 894 // this means (I'(t) || Bp'(t)) <==> (I'(t) = c * Bp'(t)) where c is some 895 // nonzero constant.) 969 two_pi_m_p1_m_p4x = 2.0f * xi - x1p - x4p; 970 two_pi_m_p1_m_p4y = 2.0f * yi - y1p - y4p; 971 c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y); 972 c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x); 973 974 x2p = x1p + c1*dx1; 975 y2p = y1p + c1*dy1; 976 x3p = x4p + c2*dx4; 977 y3p = y4p + c2*dy4; 978 979 rightOff[0] = x1p; rightOff[1] = y1p; 980 rightOff[2] = x2p; rightOff[3] = y2p; 981 rightOff[4] = x3p; rightOff[5] = y3p; 982 rightOff[6] = x4p; rightOff[7] = y4p; 983 return 8; 984 } 985 986 // compute offset curves using bezier spline through t=0.5 (i.e. 987 // ComputedCurve(0.5) == IdealParallelCurve(0.5)) 988 // return the kind of curve in the right and left arrays. 989 private int computeOffsetQuad(final float[] pts, final int off, 990 final float[] leftOff, 991 final float[] rightOff) 992 { 993 final float x1 = pts[off ], y1 = pts[off + 1]; 994 final float x2 = pts[off + 2], y2 = pts[off + 3]; 995 final float x3 = pts[off + 4], y3 = pts[off + 5]; 996 997 final float dx3 = x3 - x2; 998 final float dy3 = y3 - y2; 999 final float dx1 = x2 - x1; 1000 final float dy1 = y2 - y1; 1001 1002 // if p1=p2 or p3=p4 it means that the derivative at the endpoint 1003 // vanishes, which creates problems with computeOffset. Usually 1004 // this happens when this stroker object is trying to widen 1005 // a curve with a cusp. What happens is that curveTo splits 1006 // the input curve at the cusp, and passes it to this function. 1007 // because of inaccuracies in the splitting, we consider points 1008 // equal if they're very close to each other. 1009 1010 // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4, 1011 // in which case ignore. 1012 final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0f * Math.ulp(y2)); 1013 final boolean p2eqp3 = within(x2, y2, x3, y3, 6.0f * Math.ulp(y3)); 1014 1015 if (p1eqp2 || p2eqp3) { 1016 getLineOffsets(x1, y1, x3, y3, leftOff, rightOff); 1017 return 4; 1018 } 1019 1020 // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line 1021 float dotsq = (dx1 * dx3 + dy1 * dy3); 1022 dotsq *= dotsq; 1023 float l1sq = dx1 * dx1 + dy1 * dy1, l3sq = dx3 * dx3 + dy3 * dy3; 1024 1025 if (Helpers.within(dotsq, l1sq * l3sq, 4.0f * Math.ulp(dotsq))) { 1026 getLineOffsets(x1, y1, x3, y3, leftOff, rightOff); 1027 return 4; 1028 } 1029 1030 // this computes the offsets at t=0, 0.5, 1, using the property that 1031 // for any bezier curve the vectors p2-p1 and p4-p3 are parallel to 1032 // the (dx/dt, dy/dt) vectors at the endpoints. 1033 computeOffset(dx1, dy1, lineWidth2, offset0); 1034 computeOffset(dx3, dy3, lineWidth2, offset1); 1035 1036 float x1p = x1 + offset0[0]; // start 1037 float y1p = y1 + offset0[1]; // point 1038 float x3p = x3 + offset1[0]; // end 1039 float y3p = y3 + offset1[1]; // point 1040 safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, leftOff); 1041 leftOff[0] = x1p; leftOff[1] = y1p; 1042 leftOff[4] = x3p; leftOff[5] = y3p; 1043 1044 x1p = x1 - offset0[0]; y1p = y1 - offset0[1]; 1045 x3p = x3 - offset1[0]; y3p = y3 - offset1[1]; 1046 safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, rightOff); 1047 rightOff[0] = x1p; rightOff[1] = y1p; 1048 rightOff[4] = x3p; rightOff[5] = y3p; 1049 return 6; 1050 } 1051 1052 @Override 1053 public void curveTo(final float x1, final float y1, 1054 final float x2, final float y2, 1055 final float x3, final float y3) 1056 { 1057 final int outcode0 = this.cOutCode; 1058 1059 if (clipRect != null) { 1060 final int outcode1 = Helpers.outcode(x1, y1, clipRect); 1061 final int outcode2 = Helpers.outcode(x2, y2, clipRect); 1062 final int outcode3 = Helpers.outcode(x3, y3, clipRect); 1063 1064 // Should clip 1065 final int orCode = (outcode0 | outcode1 | outcode2 | outcode3); 1066 if (orCode != 0) { 1067 final int sideCode = outcode0 & outcode1 & outcode2 & outcode3; 1068 1069 // basic rejection criteria: 1070 if (sideCode == 0) { 1071 // ovelap clip: 1072 if (subdivide) { 1073 // avoid reentrance 1074 subdivide = false; 1075 // subdivide curve => callback with subdivided parts: 1076 boolean ret = curveSplitter.splitCurve(cx0, cy0, x1, y1, 1077 x2, y2, x3, y3, 1078 orCode, this); 1079 // reentrance is done: 1080 subdivide = true; 1081 if (ret) { 1082 return; 1083 } 1084 } 1085 // already subdivided so render it 1086 } else { 1087 this.cOutCode = outcode3; 1088 _moveTo(x3, y3, outcode0); 1089 opened = true; 1090 return; 1091 } 1092 } 1093 1094 this.cOutCode = outcode3; 1095 } 1096 _curveTo(x1, y1, x2, y2, x3, y3, outcode0); 1097 } 1098 1099 private void _curveTo(final float x1, final float y1, 1100 final float x2, final float y2, 1101 final float x3, final float y3, 1102 final int outcode0) 1103 { 1104 // need these so we can update the state at the end of this method 1105 float dxs = x1 - cx0; 1106 float dys = y1 - cy0; 1107 float dxf = x3 - x2; 1108 float dyf = y3 - y2; 1109 1110 if ((dxs == 0.0f) && (dys == 0.0f)) { 1111 dxs = x2 - cx0; 1112 dys = y2 - cy0; 1113 if ((dxs == 0.0f) && (dys == 0.0f)) { 1114 dxs = x3 - cx0; 1115 dys = y3 - cy0; 1116 } 1117 } 1118 if ((dxf == 0.0f) && (dyf == 0.0f)) { 1119 dxf = x3 - x1; 1120 dyf = y3 - y1; 1121 if ((dxf == 0.0f) && (dyf == 0.0f)) { 1122 dxf = x3 - cx0; 1123 dyf = y3 - cy0; 1124 } 1125 } 1126 if ((dxs == 0.0f) && (dys == 0.0f)) { 1127 // this happens if the "curve" is just a point 1128 // fix outcode0 for lineTo() call: 1129 if (clipRect != null) { 1130 this.cOutCode = outcode0; 1131 } 1132 lineTo(cx0, cy0); 1133 return; 1134 } 1135 1136 // if these vectors are too small, normalize them, to avoid future 1137 // precision problems. 1138 if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) { 1139 final float len = (float)Math.sqrt(dxs * dxs + dys * dys); 1140 dxs /= len; 1141 dys /= len; 1142 } 1143 if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) { 1144 final float len = (float)Math.sqrt(dxf * dxf + dyf * dyf); 1145 dxf /= len; 1146 dyf /= len; 1147 } 1148 1149 computeOffset(dxs, dys, lineWidth2, offset0); 1150 drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0); 1151 1152 int nSplits = 0; 1153 final float[] mid; 1154 final float[] l = lp; 1155 1156 if (monotonize) { 1157 // monotonize curve: 1158 final CurveBasicMonotonizer monotonizer 1159 = rdrCtx.monotonizer.curve(cx0, cy0, x1, y1, x2, y2, x3, y3); 1160 1161 nSplits = monotonizer.nbSplits; 1162 mid = monotonizer.middle; 1163 } else { 1164 // use left instead: 1165 mid = l; 1166 mid[0] = cx0; mid[1] = cy0; 1167 mid[2] = x1; mid[3] = y1; 1168 mid[4] = x2; mid[5] = y2; 1169 mid[6] = x3; mid[7] = y3; 1170 } 1171 final float[] r = rp; 1172 1173 int kind = 0; 1174 for (int i = 0, off = 0; i <= nSplits; i++, off += 6) { 1175 kind = computeOffsetCubic(mid, off, l, r); 1176 1177 emitLineTo(l[0], l[1]); 1178 1179 switch(kind) { 1180 case 8: 1181 emitCurveTo(l[2], l[3], l[4], l[5], l[6], l[7]); 1182 emitCurveToRev(r[0], r[1], r[2], r[3], r[4], r[5]); 1183 break; 1184 case 4: 1185 emitLineTo(l[2], l[3]); 1186 emitLineToRev(r[0], r[1]); 1187 break; 1188 default: 1189 } 1190 emitLineToRev(r[kind - 2], r[kind - 1]); 1191 } 1192 1193 this.prev = DRAWING_OP_TO; 1194 this.cx0 = x3; 1195 this.cy0 = y3; 1196 this.cdx = dxf; 1197 this.cdy = dyf; 1198 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f; 1199 this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f; 1200 } 1201 1202 @Override 1203 public void quadTo(final float x1, final float y1, 1204 final float x2, final float y2) 1205 { 1206 final int outcode0 = this.cOutCode; 1207 1208 if (clipRect != null) { 1209 final int outcode1 = Helpers.outcode(x1, y1, clipRect); 1210 final int outcode2 = Helpers.outcode(x2, y2, clipRect); 1211 1212 // Should clip 1213 final int orCode = (outcode0 | outcode1 | outcode2); 1214 if (orCode != 0) { 1215 final int sideCode = outcode0 & outcode1 & outcode2; 1216 1217 // basic rejection criteria: 1218 if (sideCode == 0) { 1219 // ovelap clip: 1220 if (subdivide) { 1221 // avoid reentrance 1222 subdivide = false; 1223 // subdivide curve => call lineTo() with subdivided curves: 1224 boolean ret = curveSplitter.splitQuad(cx0, cy0, x1, y1, 1225 x2, y2, orCode, this); 1226 // reentrance is done: 1227 subdivide = true; 1228 if (ret) { 1229 return; 1230 } 1231 } 1232 // already subdivided so render it 1233 } else { 1234 this.cOutCode = outcode2; 1235 _moveTo(x2, y2, outcode0); 1236 opened = true; 1237 return; 1238 } 1239 } 1240 1241 this.cOutCode = outcode2; 1242 } 1243 _quadTo(x1, y1, x2, y2, outcode0); 1244 } 1245 1246 private void _quadTo(final float x1, final float y1, 1247 final float x2, final float y2, 1248 final int outcode0) 1249 { 1250 // need these so we can update the state at the end of this method 1251 float dxs = x1 - cx0; 1252 float dys = y1 - cy0; 1253 float dxf = x2 - x1; 1254 float dyf = y2 - y1; 1255 1256 if (((dxs == 0.0f) && (dys == 0.0f)) || ((dxf == 0.0f) && (dyf == 0.0f))) { 1257 dxs = dxf = x2 - cx0; 1258 dys = dyf = y2 - cy0; 1259 } 1260 if ((dxs == 0.0f) && (dys == 0.0f)) { 1261 // this happens if the "curve" is just a point 1262 // fix outcode0 for lineTo() call: 1263 if (clipRect != null) { 1264 this.cOutCode = outcode0; 1265 } 1266 lineTo(cx0, cy0); 1267 return; 1268 } 1269 // if these vectors are too small, normalize them, to avoid future 1270 // precision problems. 1271 if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) { 1272 final float len = (float)Math.sqrt(dxs * dxs + dys * dys); 1273 dxs /= len; 1274 dys /= len; 1275 } 1276 if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) { 1277 final float len = (float)Math.sqrt(dxf * dxf + dyf * dyf); 1278 dxf /= len; 1279 dyf /= len; 1280 } 1281 computeOffset(dxs, dys, lineWidth2, offset0); 1282 drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0); 1283 1284 int nSplits = 0; 1285 final float[] mid; 1286 final float[] l = lp; 1287 1288 if (monotonize) { 1289 // monotonize quad: 1290 final CurveBasicMonotonizer monotonizer 1291 = rdrCtx.monotonizer.quad(cx0, cy0, x1, y1, x2, y2); 1292 1293 nSplits = monotonizer.nbSplits; 1294 mid = monotonizer.middle; 1295 } else { 1296 // use left instead: 1297 mid = l; 1298 mid[0] = cx0; mid[1] = cy0; 1299 mid[2] = x1; mid[3] = y1; 1300 mid[4] = x2; mid[5] = y2; 1301 } 1302 final float[] r = rp; 1303 1304 int kind = 0; 1305 for (int i = 0, off = 0; i <= nSplits; i++, off += 4) { 1306 kind = computeOffsetQuad(mid, off, l, r); 1307 1308 emitLineTo(l[0], l[1]); 1309 1310 switch(kind) { 1311 case 6: 1312 emitQuadTo(l[2], l[3], l[4], l[5]); 1313 emitQuadToRev(r[0], r[1], r[2], r[3]); 1314 break; 1315 case 4: 1316 emitLineTo(l[2], l[3]); 1317 emitLineToRev(r[0], r[1]); 1318 break; 1319 default: 1320 } 1321 emitLineToRev(r[kind - 2], r[kind - 1]); 1322 } 1323 1324 this.prev = DRAWING_OP_TO; 1325 this.cx0 = x2; 1326 this.cy0 = y2; 1327 this.cdx = dxf; 1328 this.cdy = dyf; 1329 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f; 1330 this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f; 1331 } 1332 } |