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