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