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 */ Stroker(final RendererContext rdrCtx)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 subdivideCurves true to indicate to subdivide curves, false if dasher does 145 * @return this instance 146 */ init(final PathConsumer2D pc2d, final float lineWidth, final int capStyle, final int joinStyle, final float miterLimit, final boolean subdivideCurves)147 Stroker init(final PathConsumer2D pc2d, 148 final float lineWidth, 149 final int capStyle, 150 final int joinStyle, 151 final float miterLimit, 152 final boolean subdivideCurves) 153 { 154 this.out = pc2d; 155 156 this.lineWidth2 = lineWidth / 2.0f; 157 this.invHalfLineWidth2Sq = 1.0f / (2.0f * lineWidth2 * lineWidth2); 158 this.monotonize = subdivideCurves; 159 160 this.capStyle = capStyle; 161 this.joinStyle = joinStyle; 162 163 final float limit = miterLimit * lineWidth2; 164 this.miterLimitSq = limit * limit; 165 166 this.prev = CLOSE; 167 168 rdrCtx.stroking = 1; 169 170 if (rdrCtx.doClip) { 171 // Adjust the clipping rectangle with the stroker margin (miter limit, width) 172 float margin = lineWidth2; 173 174 if (capStyle == CAP_SQUARE) { 175 margin *= SQRT_2; 176 } 177 if ((joinStyle == JOIN_MITER) && (margin < limit)) { 178 margin = limit; 179 } 180 181 // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY 182 // adjust clip rectangle (ymin, ymax, xmin, xmax): 183 final float[] _clipRect = rdrCtx.clipRect; 184 _clipRect[0] -= margin; 185 _clipRect[1] += margin; 186 _clipRect[2] -= margin; 187 _clipRect[3] += margin; 188 this.clipRect = _clipRect; 189 190 if (MarlinConst.DO_LOG_CLIP) { 191 MarlinUtils.logInfo("clipRect (stroker): " 192 + Arrays.toString(rdrCtx.clipRect)); 193 } 194 195 // initialize curve splitter here for stroker & dasher: 196 if (DO_CLIP_SUBDIVIDER) { 197 subdivide = subdivideCurves; 198 // adjust padded clip rectangle: 199 curveSplitter.init(); 200 } else { 201 subdivide = false; 202 } 203 } else { 204 this.clipRect = null; 205 this.cOutCode = 0; 206 this.sOutCode = 0; 207 } 208 return this; // fluent API 209 } 210 disableClipping()211 void disableClipping() { 212 this.clipRect = null; 213 this.cOutCode = 0; 214 this.sOutCode = 0; 215 } 216 217 /** 218 * Disposes this stroker: 219 * clean up before reusing this instance 220 */ dispose()221 void dispose() { 222 reverse.dispose(); 223 224 opened = false; 225 capStart = false; 226 227 if (DO_CLEAN_DIRTY) { 228 // Force zero-fill dirty arrays: 229 Arrays.fill(offset0, 0.0f); 230 Arrays.fill(offset1, 0.0f); 231 Arrays.fill(offset2, 0.0f); 232 Arrays.fill(miter, 0.0f); 233 Arrays.fill(lp, 0.0f); 234 Arrays.fill(rp, 0.0f); 235 } 236 } 237 computeOffset(final float lx, final float ly, final float w, final float[] m)238 private static void computeOffset(final float lx, final float ly, 239 final float w, final float[] m) 240 { 241 float len = lx*lx + ly*ly; 242 if (len == 0.0f) { 243 m[0] = 0.0f; 244 m[1] = 0.0f; 245 } else { 246 len = (float) Math.sqrt(len); 247 m[0] = (ly * w) / len; 248 m[1] = -(lx * w) / len; 249 } 250 } 251 252 // Returns true if the vectors (dx1, dy1) and (dx2, dy2) are 253 // clockwise (if dx1,dy1 needs to be rotated clockwise to close 254 // the smallest angle between it and dx2,dy2). 255 // This is equivalent to detecting whether a point q is on the right side 256 // of a line passing through points p1, p2 where p2 = p1+(dx1,dy1) and 257 // q = p2+(dx2,dy2), which is the same as saying p1, p2, q are in a 258 // clockwise order. 259 // NOTE: "clockwise" here assumes coordinates with 0,0 at the bottom left. isCW(final float dx1, final float dy1, final float dx2, final float dy2)260 private static boolean isCW(final float dx1, final float dy1, 261 final float dx2, final float dy2) 262 { 263 return dx1 * dy2 <= dy1 * dx2; 264 } 265 mayDrawRoundJoin(float cx, float cy, float omx, float omy, float mx, float my, boolean rev)266 private void mayDrawRoundJoin(float cx, float cy, 267 float omx, float omy, 268 float mx, float my, 269 boolean rev) 270 { 271 if ((omx == 0.0f && omy == 0.0f) || (mx == 0.0f && my == 0.0f)) { 272 return; 273 } 274 275 final float domx = omx - mx; 276 final float domy = omy - my; 277 final float lenSq = domx*domx + domy*domy; 278 279 if (lenSq < ROUND_JOIN_THRESHOLD) { 280 return; 281 } 282 283 if (rev) { 284 omx = -omx; 285 omy = -omy; 286 mx = -mx; 287 my = -my; 288 } 289 drawRoundJoin(cx, cy, omx, omy, mx, my, rev); 290 } 291 drawRoundJoin(float cx, float cy, float omx, float omy, float mx, float my, boolean rev)292 private void drawRoundJoin(float cx, float cy, 293 float omx, float omy, 294 float mx, float my, 295 boolean rev) 296 { 297 // The sign of the dot product of mx,my and omx,omy is equal to the 298 // the sign of the cosine of ext 299 // (ext is the angle between omx,omy and mx,my). 300 final float cosext = omx * mx + omy * my; 301 // If it is >=0, we know that abs(ext) is <= 90 degrees, so we only 302 // need 1 curve to approximate the circle section that joins omx,omy 303 // and mx,my. 304 if (cosext >= 0.0f) { 305 drawBezApproxForArc(cx, cy, omx, omy, mx, my, rev); 306 } else { 307 // we need to split the arc into 2 arcs spanning the same angle. 308 // The point we want will be one of the 2 intersections of the 309 // perpendicular bisector of the chord (omx,omy)->(mx,my) and the 310 // circle. We could find this by scaling the vector 311 // (omx+mx, omy+my)/2 so that it has length=lineWidth2 (and thus lies 312 // on the circle), but that can have numerical problems when the angle 313 // between omx,omy and mx,my is close to 180 degrees. So we compute a 314 // normal of (omx,omy)-(mx,my). This will be the direction of the 315 // perpendicular bisector. To get one of the intersections, we just scale 316 // this vector that its length is lineWidth2 (this works because the 317 // perpendicular bisector goes through the origin). This scaling doesn't 318 // have numerical problems because we know that lineWidth2 divided by 319 // this normal's length is at least 0.5 and at most sqrt(2)/2 (because 320 // we know the angle of the arc is > 90 degrees). 321 float nx = my - omy, ny = omx - mx; 322 float nlen = (float) Math.sqrt(nx*nx + ny*ny); 323 float scale = lineWidth2/nlen; 324 float mmx = nx * scale, mmy = ny * scale; 325 326 // if (isCW(omx, omy, mx, my) != isCW(mmx, mmy, mx, my)) then we've 327 // computed the wrong intersection so we get the other one. 328 // The test above is equivalent to if (rev). 329 if (rev) { 330 mmx = -mmx; 331 mmy = -mmy; 332 } 333 drawBezApproxForArc(cx, cy, omx, omy, mmx, mmy, rev); 334 drawBezApproxForArc(cx, cy, mmx, mmy, mx, my, rev); 335 } 336 } 337 338 // the input arc defined by omx,omy and mx,my must span <= 90 degrees. drawBezApproxForArc(final float cx, final float cy, final float omx, final float omy, final float mx, final float my, boolean rev)339 private void drawBezApproxForArc(final float cx, final float cy, 340 final float omx, final float omy, 341 final float mx, final float my, 342 boolean rev) 343 { 344 final float cosext2 = (omx * mx + omy * my) * invHalfLineWidth2Sq; 345 346 // check round off errors producing cos(ext) > 1 and a NaN below 347 // cos(ext) == 1 implies colinear segments and an empty join anyway 348 if (cosext2 >= 0.5f) { 349 // just return to avoid generating a flat curve: 350 return; 351 } 352 353 // cv is the length of P1-P0 and P2-P3 divided by the radius of the arc 354 // (so, cv assumes the arc has radius 1). P0, P1, P2, P3 are the points that 355 // define the bezier curve we're computing. 356 // It is computed using the constraints that P1-P0 and P3-P2 are parallel 357 // to the arc tangents at the endpoints, and that |P1-P0|=|P3-P2|. 358 float cv = (float) ((4.0d / 3.0d) * Math.sqrt(0.5d - cosext2) / 359 (1.0d + Math.sqrt(cosext2 + 0.5d))); 360 // if clockwise, we need to negate cv. 361 if (rev) { // rev is equivalent to isCW(omx, omy, mx, my) 362 cv = -cv; 363 } 364 final float x1 = cx + omx; 365 final float y1 = cy + omy; 366 final float x2 = x1 - cv * omy; 367 final float y2 = y1 + cv * omx; 368 369 final float x4 = cx + mx; 370 final float y4 = cy + my; 371 final float x3 = x4 + cv * my; 372 final float y3 = y4 - cv * mx; 373 374 emitCurveTo(x1, y1, x2, y2, x3, y3, x4, y4, rev); 375 } 376 drawRoundCap(float cx, float cy, float mx, float my)377 private void drawRoundCap(float cx, float cy, float mx, float my) { 378 final float Cmx = C * mx; 379 final float Cmy = C * my; 380 emitCurveTo(cx + mx - Cmy, cy + my + Cmx, 381 cx - my + Cmx, cy + mx + Cmy, 382 cx - my, cy + mx); 383 emitCurveTo(cx - my - Cmx, cy + mx - Cmy, 384 cx - mx - Cmy, cy - my + Cmx, 385 cx - mx, cy - my); 386 } 387 388 // Return the intersection point of the lines (x0, y0) -> (x1, y1) 389 // and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1] computeMiter(final float x0, final float y0, final float x1, final float y1, final float x0p, final float y0p, final float x1p, final float y1p, final float[] m)390 private static void computeMiter(final float x0, final float y0, 391 final float x1, final float y1, 392 final float x0p, final float y0p, 393 final float x1p, final float y1p, 394 final float[] m) 395 { 396 float x10 = x1 - x0; 397 float y10 = y1 - y0; 398 float x10p = x1p - x0p; 399 float y10p = y1p - y0p; 400 401 // if this is 0, the lines are parallel. If they go in the 402 // same direction, there is no intersection so m[off] and 403 // m[off+1] will contain infinity, so no miter will be drawn. 404 // If they go in the same direction that means that the start of the 405 // current segment and the end of the previous segment have the same 406 // tangent, in which case this method won't even be involved in 407 // miter drawing because it won't be called by drawMiter (because 408 // (mx == omx && my == omy) will be true, and drawMiter will return 409 // immediately). 410 float den = x10*y10p - x10p*y10; 411 float t = x10p*(y0-y0p) - y10p*(x0-x0p); 412 t /= den; 413 m[0] = x0 + t*x10; 414 m[1] = y0 + t*y10; 415 } 416 417 // Return the intersection point of the lines (x0, y0) -> (x1, y1) 418 // and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1] safeComputeMiter(final float x0, final float y0, final float x1, final float y1, final float x0p, final float y0p, final float x1p, final float y1p, final float[] m)419 private static void safeComputeMiter(final float x0, final float y0, 420 final float x1, final float y1, 421 final float x0p, final float y0p, 422 final float x1p, final float y1p, 423 final float[] m) 424 { 425 float x10 = x1 - x0; 426 float y10 = y1 - y0; 427 float x10p = x1p - x0p; 428 float y10p = y1p - y0p; 429 430 // if this is 0, the lines are parallel. If they go in the 431 // same direction, there is no intersection so m[off] and 432 // m[off+1] will contain infinity, so no miter will be drawn. 433 // If they go in the same direction that means that the start of the 434 // current segment and the end of the previous segment have the same 435 // tangent, in which case this method won't even be involved in 436 // miter drawing because it won't be called by drawMiter (because 437 // (mx == omx && my == omy) will be true, and drawMiter will return 438 // immediately). 439 float den = x10*y10p - x10p*y10; 440 if (den == 0.0f) { 441 m[2] = (x0 + x0p) / 2.0f; 442 m[3] = (y0 + y0p) / 2.0f; 443 } else { 444 float t = x10p*(y0-y0p) - y10p*(x0-x0p); 445 t /= den; 446 m[2] = x0 + t*x10; 447 m[3] = y0 + t*y10; 448 } 449 } 450 drawMiter(final float pdx, final float pdy, final float x0, final float y0, final float dx, final float dy, float omx, float omy, float mx, float my, boolean rev)451 private void drawMiter(final float pdx, final float pdy, 452 final float x0, final float y0, 453 final float dx, final float dy, 454 float omx, float omy, 455 float mx, float my, 456 boolean rev) 457 { 458 if ((mx == omx && my == omy) || 459 (pdx == 0.0f && pdy == 0.0f) || 460 (dx == 0.0f && dy == 0.0f)) 461 { 462 return; 463 } 464 465 if (rev) { 466 omx = -omx; 467 omy = -omy; 468 mx = -mx; 469 my = -my; 470 } 471 472 computeMiter((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy, 473 (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my, miter); 474 475 final float miterX = miter[0]; 476 final float miterY = miter[1]; 477 float lenSq = (miterX-x0)*(miterX-x0) + (miterY-y0)*(miterY-y0); 478 479 // If the lines are parallel, lenSq will be either NaN or +inf 480 // (actually, I'm not sure if the latter is possible. The important 481 // thing is that -inf is not possible, because lenSq is a square). 482 // For both of those values, the comparison below will fail and 483 // no miter will be drawn, which is correct. 484 if (lenSq < miterLimitSq) { 485 emitLineTo(miterX, miterY, rev); 486 } 487 } 488 489 @Override moveTo(final float x0, final float y0)490 public void moveTo(final float x0, final float y0) { 491 _moveTo(x0, y0, cOutCode); 492 // update starting point: 493 this.sx0 = x0; 494 this.sy0 = y0; 495 this.sdx = 1.0f; 496 this.sdy = 0.0f; 497 this.opened = false; 498 this.capStart = false; 499 500 if (clipRect != null) { 501 final int outcode = Helpers.outcode(x0, y0, clipRect); 502 this.cOutCode = outcode; 503 this.sOutCode = outcode; 504 } 505 } 506 _moveTo(final float x0, final float y0, final int outcode)507 private void _moveTo(final float x0, final float y0, 508 final int outcode) 509 { 510 if (prev == MOVE_TO) { 511 this.cx0 = x0; 512 this.cy0 = y0; 513 } else { 514 if (prev == DRAWING_OP_TO) { 515 finish(outcode); 516 } 517 this.prev = MOVE_TO; 518 this.cx0 = x0; 519 this.cy0 = y0; 520 this.cdx = 1.0f; 521 this.cdy = 0.0f; 522 } 523 } 524 525 @Override lineTo(final float x1, final float y1)526 public void lineTo(final float x1, final float y1) { 527 lineTo(x1, y1, false); 528 } 529 lineTo(final float x1, final float y1, final boolean force)530 private void lineTo(final float x1, final float y1, 531 final boolean force) 532 { 533 final int outcode0 = this.cOutCode; 534 535 if (!force && clipRect != null) { 536 final int outcode1 = Helpers.outcode(x1, y1, clipRect); 537 538 // Should clip 539 final int orCode = (outcode0 | outcode1); 540 if (orCode != 0) { 541 final int sideCode = outcode0 & outcode1; 542 543 // basic rejection criteria: 544 if (sideCode == 0) { 545 // overlap clip: 546 if (subdivide) { 547 // avoid reentrance 548 subdivide = false; 549 // subdivide curve => callback with subdivided parts: 550 boolean ret = curveSplitter.splitLine(cx0, cy0, x1, y1, 551 orCode, this); 552 // reentrance is done: 553 subdivide = true; 554 if (ret) { 555 return; 556 } 557 } 558 // already subdivided so render it 559 } else { 560 this.cOutCode = outcode1; 561 _moveTo(x1, y1, outcode0); 562 opened = true; 563 return; 564 } 565 } 566 567 this.cOutCode = outcode1; 568 } 569 570 float dx = x1 - cx0; 571 float dy = y1 - cy0; 572 if (dx == 0.0f && dy == 0.0f) { 573 dx = 1.0f; 574 } 575 computeOffset(dx, dy, lineWidth2, offset0); 576 final float mx = offset0[0]; 577 final float my = offset0[1]; 578 579 drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my, outcode0); 580 581 emitLineTo(cx0 + mx, cy0 + my); 582 emitLineTo( x1 + mx, y1 + my); 583 584 emitLineToRev(cx0 - mx, cy0 - my); 585 emitLineToRev( x1 - mx, y1 - my); 586 587 this.prev = DRAWING_OP_TO; 588 this.cx0 = x1; 589 this.cy0 = y1; 590 this.cdx = dx; 591 this.cdy = dy; 592 this.cmx = mx; 593 this.cmy = my; 594 } 595 596 @Override closePath()597 public void closePath() { 598 // distinguish empty path at all vs opened path ? 599 if (prev != DRAWING_OP_TO && !opened) { 600 if (prev == CLOSE) { 601 return; 602 } 603 emitMoveTo(cx0, cy0 - lineWidth2); 604 605 this.sdx = 1.0f; 606 this.sdy = 0.0f; 607 this.cdx = 1.0f; 608 this.cdy = 0.0f; 609 610 this.smx = 0.0f; 611 this.smy = -lineWidth2; 612 this.cmx = 0.0f; 613 this.cmy = -lineWidth2; 614 615 finish(cOutCode); 616 return; 617 } 618 619 // basic acceptance criteria 620 if ((sOutCode & cOutCode) == 0) { 621 if (cx0 != sx0 || cy0 != sy0) { 622 lineTo(sx0, sy0, true); 623 } 624 625 drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy, sOutCode); 626 627 emitLineTo(sx0 + smx, sy0 + smy); 628 629 if (opened) { 630 emitLineTo(sx0 - smx, sy0 - smy); 631 } else { 632 emitMoveTo(sx0 - smx, sy0 - smy); 633 } 634 } 635 // Ignore caps like finish(false) 636 emitReverse(); 637 638 this.prev = CLOSE; 639 this.cx0 = sx0; 640 this.cy0 = sy0; 641 this.cOutCode = sOutCode; 642 643 if (opened) { 644 // do not emit close 645 opened = false; 646 } else { 647 emitClose(); 648 } 649 } 650 emitReverse()651 private void emitReverse() { 652 reverse.popAll(out); 653 } 654 655 @Override pathDone()656 public void pathDone() { 657 if (prev == DRAWING_OP_TO) { 658 finish(cOutCode); 659 } 660 661 out.pathDone(); 662 663 // this shouldn't matter since this object won't be used 664 // after the call to this method. 665 this.prev = CLOSE; 666 667 // Dispose this instance: 668 dispose(); 669 } 670 finish(final int outcode)671 private void finish(final int outcode) { 672 // Problem: impossible to guess if the path will be closed in advance 673 // i.e. if caps must be drawn or not ? 674 // Solution: use the ClosedPathDetector before Stroker to determine 675 // if the path is a closed path or not 676 if (rdrCtx.closedPath) { 677 emitReverse(); 678 } else { 679 if (outcode == 0) { 680 // current point = end's cap: 681 if (capStyle == CAP_ROUND) { 682 drawRoundCap(cx0, cy0, cmx, cmy); 683 } else if (capStyle == CAP_SQUARE) { 684 emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy); 685 emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy); 686 } 687 } 688 emitReverse(); 689 690 if (!capStart) { 691 capStart = true; 692 693 if (sOutCode == 0) { 694 // starting point = initial cap: 695 if (capStyle == CAP_ROUND) { 696 drawRoundCap(sx0, sy0, -smx, -smy); 697 } else if (capStyle == CAP_SQUARE) { 698 emitLineTo(sx0 + smy - smx, sy0 - smx - smy); 699 emitLineTo(sx0 + smy + smx, sy0 - smx + smy); 700 } 701 } 702 } 703 } 704 emitClose(); 705 } 706 emitMoveTo(final float x0, final float y0)707 private void emitMoveTo(final float x0, final float y0) { 708 out.moveTo(x0, y0); 709 } 710 emitLineTo(final float x1, final float y1)711 private void emitLineTo(final float x1, final float y1) { 712 out.lineTo(x1, y1); 713 } 714 emitLineToRev(final float x1, final float y1)715 private void emitLineToRev(final float x1, final float y1) { 716 reverse.pushLine(x1, y1); 717 } 718 emitLineTo(final float x1, final float y1, final boolean rev)719 private void emitLineTo(final float x1, final float y1, 720 final boolean rev) 721 { 722 if (rev) { 723 emitLineToRev(x1, y1); 724 } else { 725 emitLineTo(x1, y1); 726 } 727 } 728 emitQuadTo(final float x1, final float y1, final float x2, final float y2)729 private void emitQuadTo(final float x1, final float y1, 730 final float x2, final float y2) 731 { 732 out.quadTo(x1, y1, x2, y2); 733 } 734 emitQuadToRev(final float x0, final float y0, final float x1, final float y1)735 private void emitQuadToRev(final float x0, final float y0, 736 final float x1, final float y1) 737 { 738 reverse.pushQuad(x0, y0, x1, y1); 739 } 740 emitCurveTo(final float x1, final float y1, final float x2, final float y2, final float x3, final float y3)741 private void emitCurveTo(final float x1, final float y1, 742 final float x2, final float y2, 743 final float x3, final float y3) 744 { 745 out.curveTo(x1, y1, x2, y2, x3, y3); 746 } 747 emitCurveToRev(final float x0, final float y0, final float x1, final float y1, final float x2, final float y2)748 private void emitCurveToRev(final float x0, final float y0, 749 final float x1, final float y1, 750 final float x2, final float y2) 751 { 752 reverse.pushCubic(x0, y0, x1, y1, x2, y2); 753 } 754 emitCurveTo(final float x0, final float y0, final float x1, final float y1, final float x2, final float y2, final float x3, final float y3, final boolean rev)755 private void emitCurveTo(final float x0, final float y0, 756 final float x1, final float y1, 757 final float x2, final float y2, 758 final float x3, final float y3, final boolean rev) 759 { 760 if (rev) { 761 reverse.pushCubic(x0, y0, x1, y1, x2, y2); 762 } else { 763 out.curveTo(x1, y1, x2, y2, x3, y3); 764 } 765 } 766 emitClose()767 private void emitClose() { 768 out.closePath(); 769 } 770 drawJoin(float pdx, float pdy, float x0, float y0, float dx, float dy, float omx, float omy, float mx, float my, final int outcode)771 private void drawJoin(float pdx, float pdy, 772 float x0, float y0, 773 float dx, float dy, 774 float omx, float omy, 775 float mx, float my, 776 final int outcode) 777 { 778 if (prev != DRAWING_OP_TO) { 779 emitMoveTo(x0 + mx, y0 + my); 780 if (!opened) { 781 this.sdx = dx; 782 this.sdy = dy; 783 this.smx = mx; 784 this.smy = my; 785 } 786 } else { 787 final boolean cw = isCW(pdx, pdy, dx, dy); 788 if (outcode == 0) { 789 if (joinStyle == JOIN_MITER) { 790 drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw); 791 } else if (joinStyle == JOIN_ROUND) { 792 mayDrawRoundJoin(x0, y0, omx, omy, mx, my, cw); 793 } 794 } 795 emitLineTo(x0, y0, !cw); 796 } 797 prev = DRAWING_OP_TO; 798 } 799 within(final float x1, final float y1, final float x2, final float y2, final float err)800 private static boolean within(final float x1, final float y1, 801 final float x2, final float y2, 802 final float err) 803 { 804 assert err > 0 : ""; 805 // compare taxicab distance. ERR will always be small, so using 806 // true distance won't give much benefit 807 return (Helpers.within(x1, x2, err) && // we want to avoid calling Math.abs 808 Helpers.within(y1, y2, err)); // this is just as good. 809 } 810 getLineOffsets(final float x1, final float y1, final float x2, final float y2, final float[] left, final float[] right)811 private void getLineOffsets(final float x1, final float y1, 812 final float x2, final float y2, 813 final float[] left, final float[] right) 814 { 815 computeOffset(x2 - x1, y2 - y1, lineWidth2, offset0); 816 final float mx = offset0[0]; 817 final float my = offset0[1]; 818 left[0] = x1 + mx; 819 left[1] = y1 + my; 820 left[2] = x2 + mx; 821 left[3] = y2 + my; 822 823 right[0] = x1 - mx; 824 right[1] = y1 - my; 825 right[2] = x2 - mx; 826 right[3] = y2 - my; 827 } 828 computeOffsetCubic(final float[] pts, final int off, final float[] leftOff, final float[] rightOff)829 private int computeOffsetCubic(final float[] pts, final int off, 830 final float[] leftOff, 831 final float[] rightOff) 832 { 833 // if p1=p2 or p3=p4 it means that the derivative at the endpoint 834 // vanishes, which creates problems with computeOffset. Usually 835 // this happens when this stroker object is trying to widen 836 // a curve with a cusp. What happens is that curveTo splits 837 // the input curve at the cusp, and passes it to this function. 838 // because of inaccuracies in the splitting, we consider points 839 // equal if they're very close to each other. 840 final float x1 = pts[off ], y1 = pts[off + 1]; 841 final float x2 = pts[off + 2], y2 = pts[off + 3]; 842 final float x3 = pts[off + 4], y3 = pts[off + 5]; 843 final float x4 = pts[off + 6], y4 = pts[off + 7]; 844 845 float dx4 = x4 - x3; 846 float dy4 = y4 - y3; 847 float dx1 = x2 - x1; 848 float dy1 = y2 - y1; 849 850 // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4, 851 // in which case ignore if p1 == p2 852 final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0f * Math.ulp(y2)); 853 final boolean p3eqp4 = within(x3, y3, x4, y4, 6.0f * Math.ulp(y4)); 854 855 if (p1eqp2 && p3eqp4) { 856 getLineOffsets(x1, y1, x4, y4, leftOff, rightOff); 857 return 4; 858 } else if (p1eqp2) { 859 dx1 = x3 - x1; 860 dy1 = y3 - y1; 861 } else if (p3eqp4) { 862 dx4 = x4 - x2; 863 dy4 = y4 - y2; 864 } 865 866 // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line 867 float dotsq = (dx1 * dx4 + dy1 * dy4); 868 dotsq *= dotsq; 869 float l1sq = dx1 * dx1 + dy1 * dy1, l4sq = dx4 * dx4 + dy4 * dy4; 870 871 if (Helpers.within(dotsq, l1sq * l4sq, 4.0f * Math.ulp(dotsq))) { 872 getLineOffsets(x1, y1, x4, y4, leftOff, rightOff); 873 return 4; 874 } 875 876 // What we're trying to do in this function is to approximate an ideal 877 // offset curve (call it I) of the input curve B using a bezier curve Bp. 878 // The constraints I use to get the equations are: 879 // 880 // 1. The computed curve Bp should go through I(0) and I(1). These are 881 // x1p, y1p, x4p, y4p, which are p1p and p4p. We still need to find 882 // 4 variables: the x and y components of p2p and p3p (i.e. x2p, y2p, x3p, y3p). 883 // 884 // 2. Bp should have slope equal in absolute value to I at the endpoints. So, 885 // (by the way, the operator || in the comments below means "aligned with". 886 // It is defined on vectors, so when we say I'(0) || Bp'(0) we mean that 887 // vectors I'(0) and Bp'(0) are aligned, which is the same as saying 888 // that the tangent lines of I and Bp at 0 are parallel. Mathematically 889 // this means (I'(t) || Bp'(t)) <==> (I'(t) = c * Bp'(t)) where c is some 890 // nonzero constant.) 891 // I'(0) || Bp'(0) and I'(1) || Bp'(1). Obviously, I'(0) || B'(0) and 892 // I'(1) || B'(1); therefore, Bp'(0) || B'(0) and Bp'(1) || B'(1). 893 // We know that Bp'(0) || (p2p-p1p) and Bp'(1) || (p4p-p3p) and the same 894 // is true for any bezier curve; therefore, we get the equations 895 // (1) p2p = c1 * (p2-p1) + p1p 896 // (2) p3p = c2 * (p4-p3) + p4p 897 // We know p1p, p4p, p2, p1, p3, and p4; therefore, this reduces the number 898 // of unknowns from 4 to 2 (i.e. just c1 and c2). 899 // To eliminate these 2 unknowns we use the following constraint: 900 // 901 // 3. Bp(0.5) == I(0.5). Bp(0.5)=(x,y) and I(0.5)=(xi,yi), and I should note 902 // that I(0.5) is *the only* reason for computing dxm,dym. This gives us 903 // (3) Bp(0.5) = (p1p + 3 * (p2p + p3p) + p4p)/8, which is equivalent to 904 // (4) p2p + p3p = (Bp(0.5)*8 - p1p - p4p) / 3 905 // We can substitute (1) and (2) from above into (4) and we get: 906 // (5) c1*(p2-p1) + c2*(p4-p3) = (Bp(0.5)*8 - p1p - p4p)/3 - p1p - p4p 907 // which is equivalent to 908 // (6) c1*(p2-p1) + c2*(p4-p3) = (4/3) * (Bp(0.5) * 2 - p1p - p4p) 909 // 910 // The right side of this is a 2D vector, and we know I(0.5), which gives us 911 // Bp(0.5), which gives us the value of the right side. 912 // The left side is just a matrix vector multiplication in disguise. It is 913 // 914 // [x2-x1, x4-x3][c1] 915 // [y2-y1, y4-y3][c2] 916 // which, is equal to 917 // [dx1, dx4][c1] 918 // [dy1, dy4][c2] 919 // At this point we are left with a simple linear system and we solve it by 920 // getting the inverse of the matrix above. Then we use [c1,c2] to compute 921 // p2p and p3p. 922 923 float x = (x1 + 3.0f * (x2 + x3) + x4) / 8.0f; 924 float y = (y1 + 3.0f * (y2 + y3) + y4) / 8.0f; 925 // (dxm,dym) is some tangent of B at t=0.5. This means it's equal to 926 // c*B'(0.5) for some constant c. 927 float dxm = x3 + x4 - x1 - x2, dym = y3 + y4 - y1 - y2; 928 929 // this computes the offsets at t=0, 0.5, 1, using the property that 930 // for any bezier curve the vectors p2-p1 and p4-p3 are parallel to 931 // the (dx/dt, dy/dt) vectors at the endpoints. 932 computeOffset(dx1, dy1, lineWidth2, offset0); 933 computeOffset(dxm, dym, lineWidth2, offset1); 934 computeOffset(dx4, dy4, lineWidth2, offset2); 935 float x1p = x1 + offset0[0]; // start 936 float y1p = y1 + offset0[1]; // point 937 float xi = x + offset1[0]; // interpolation 938 float yi = y + offset1[1]; // point 939 float x4p = x4 + offset2[0]; // end 940 float y4p = y4 + offset2[1]; // point 941 942 float invdet43 = 4.0f / (3.0f * (dx1 * dy4 - dy1 * dx4)); 943 944 float two_pi_m_p1_m_p4x = 2.0f * xi - x1p - x4p; 945 float two_pi_m_p1_m_p4y = 2.0f * yi - y1p - y4p; 946 float c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y); 947 float c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x); 948 949 float x2p, y2p, x3p, y3p; 950 x2p = x1p + c1*dx1; 951 y2p = y1p + c1*dy1; 952 x3p = x4p + c2*dx4; 953 y3p = y4p + c2*dy4; 954 955 leftOff[0] = x1p; leftOff[1] = y1p; 956 leftOff[2] = x2p; leftOff[3] = y2p; 957 leftOff[4] = x3p; leftOff[5] = y3p; 958 leftOff[6] = x4p; leftOff[7] = y4p; 959 960 x1p = x1 - offset0[0]; y1p = y1 - offset0[1]; 961 xi = xi - 2.0f * offset1[0]; yi = yi - 2.0f * offset1[1]; 962 x4p = x4 - offset2[0]; y4p = y4 - offset2[1]; 963 964 two_pi_m_p1_m_p4x = 2.0f * xi - x1p - x4p; 965 two_pi_m_p1_m_p4y = 2.0f * yi - y1p - y4p; 966 c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y); 967 c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x); 968 969 x2p = x1p + c1*dx1; 970 y2p = y1p + c1*dy1; 971 x3p = x4p + c2*dx4; 972 y3p = y4p + c2*dy4; 973 974 rightOff[0] = x1p; rightOff[1] = y1p; 975 rightOff[2] = x2p; rightOff[3] = y2p; 976 rightOff[4] = x3p; rightOff[5] = y3p; 977 rightOff[6] = x4p; rightOff[7] = y4p; 978 return 8; 979 } 980 981 // compute offset curves using bezier spline through t=0.5 (i.e. 982 // ComputedCurve(0.5) == IdealParallelCurve(0.5)) 983 // return the kind of curve in the right and left arrays. computeOffsetQuad(final float[] pts, final int off, final float[] leftOff, final float[] rightOff)984 private int computeOffsetQuad(final float[] pts, final int off, 985 final float[] leftOff, 986 final float[] rightOff) 987 { 988 final float x1 = pts[off ], y1 = pts[off + 1]; 989 final float x2 = pts[off + 2], y2 = pts[off + 3]; 990 final float x3 = pts[off + 4], y3 = pts[off + 5]; 991 992 final float dx3 = x3 - x2; 993 final float dy3 = y3 - y2; 994 final float dx1 = x2 - x1; 995 final float dy1 = y2 - y1; 996 997 // if p1=p2 or p3=p4 it means that the derivative at the endpoint 998 // vanishes, which creates problems with computeOffset. Usually 999 // this happens when this stroker object is trying to widen 1000 // a curve with a cusp. What happens is that curveTo splits 1001 // the input curve at the cusp, and passes it to this function. 1002 // because of inaccuracies in the splitting, we consider points 1003 // equal if they're very close to each other. 1004 1005 // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4, 1006 // in which case ignore. 1007 final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0f * Math.ulp(y2)); 1008 final boolean p2eqp3 = within(x2, y2, x3, y3, 6.0f * Math.ulp(y3)); 1009 1010 if (p1eqp2 || p2eqp3) { 1011 getLineOffsets(x1, y1, x3, y3, leftOff, rightOff); 1012 return 4; 1013 } 1014 1015 // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line 1016 float dotsq = (dx1 * dx3 + dy1 * dy3); 1017 dotsq *= dotsq; 1018 float l1sq = dx1 * dx1 + dy1 * dy1, l3sq = dx3 * dx3 + dy3 * dy3; 1019 1020 if (Helpers.within(dotsq, l1sq * l3sq, 4.0f * Math.ulp(dotsq))) { 1021 getLineOffsets(x1, y1, x3, y3, leftOff, rightOff); 1022 return 4; 1023 } 1024 1025 // this computes the offsets at t=0, 0.5, 1, using the property that 1026 // for any bezier curve the vectors p2-p1 and p4-p3 are parallel to 1027 // the (dx/dt, dy/dt) vectors at the endpoints. 1028 computeOffset(dx1, dy1, lineWidth2, offset0); 1029 computeOffset(dx3, dy3, lineWidth2, offset1); 1030 1031 float x1p = x1 + offset0[0]; // start 1032 float y1p = y1 + offset0[1]; // point 1033 float x3p = x3 + offset1[0]; // end 1034 float y3p = y3 + offset1[1]; // point 1035 safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, leftOff); 1036 leftOff[0] = x1p; leftOff[1] = y1p; 1037 leftOff[4] = x3p; leftOff[5] = y3p; 1038 1039 x1p = x1 - offset0[0]; y1p = y1 - offset0[1]; 1040 x3p = x3 - offset1[0]; y3p = y3 - offset1[1]; 1041 safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, rightOff); 1042 rightOff[0] = x1p; rightOff[1] = y1p; 1043 rightOff[4] = x3p; rightOff[5] = y3p; 1044 return 6; 1045 } 1046 1047 @Override curveTo(final float x1, final float y1, final float x2, final float y2, final float x3, final float y3)1048 public void curveTo(final float x1, final float y1, 1049 final float x2, final float y2, 1050 final float x3, final float y3) 1051 { 1052 final int outcode0 = this.cOutCode; 1053 1054 if (clipRect != null) { 1055 final int outcode1 = Helpers.outcode(x1, y1, clipRect); 1056 final int outcode2 = Helpers.outcode(x2, y2, clipRect); 1057 final int outcode3 = Helpers.outcode(x3, y3, clipRect); 1058 1059 // Should clip 1060 final int orCode = (outcode0 | outcode1 | outcode2 | outcode3); 1061 if (orCode != 0) { 1062 final int sideCode = outcode0 & outcode1 & outcode2 & outcode3; 1063 1064 // basic rejection criteria: 1065 if (sideCode == 0) { 1066 // overlap clip: 1067 if (subdivide) { 1068 // avoid reentrance 1069 subdivide = false; 1070 // subdivide curve => callback with subdivided parts: 1071 boolean ret = curveSplitter.splitCurve(cx0, cy0, x1, y1, 1072 x2, y2, x3, y3, 1073 orCode, this); 1074 // reentrance is done: 1075 subdivide = true; 1076 if (ret) { 1077 return; 1078 } 1079 } 1080 // already subdivided so render it 1081 } else { 1082 this.cOutCode = outcode3; 1083 _moveTo(x3, y3, outcode0); 1084 opened = true; 1085 return; 1086 } 1087 } 1088 1089 this.cOutCode = outcode3; 1090 } 1091 _curveTo(x1, y1, x2, y2, x3, y3, outcode0); 1092 } 1093 _curveTo(final float x1, final float y1, final float x2, final float y2, final float x3, final float y3, final int outcode0)1094 private void _curveTo(final float x1, final float y1, 1095 final float x2, final float y2, 1096 final float x3, final float y3, 1097 final int outcode0) 1098 { 1099 // need these so we can update the state at the end of this method 1100 float dxs = x1 - cx0; 1101 float dys = y1 - cy0; 1102 float dxf = x3 - x2; 1103 float dyf = y3 - y2; 1104 1105 if ((dxs == 0.0f) && (dys == 0.0f)) { 1106 dxs = x2 - cx0; 1107 dys = y2 - cy0; 1108 if ((dxs == 0.0f) && (dys == 0.0f)) { 1109 dxs = x3 - cx0; 1110 dys = y3 - cy0; 1111 } 1112 } 1113 if ((dxf == 0.0f) && (dyf == 0.0f)) { 1114 dxf = x3 - x1; 1115 dyf = y3 - y1; 1116 if ((dxf == 0.0f) && (dyf == 0.0f)) { 1117 dxf = x3 - cx0; 1118 dyf = y3 - cy0; 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(cx0, cy0); 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 final 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 final 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 int nSplits = 0; 1148 final float[] mid; 1149 final float[] l = lp; 1150 1151 if (monotonize) { 1152 // monotonize curve: 1153 final CurveBasicMonotonizer monotonizer 1154 = rdrCtx.monotonizer.curve(cx0, cy0, x1, y1, x2, y2, x3, y3); 1155 1156 nSplits = monotonizer.nbSplits; 1157 mid = monotonizer.middle; 1158 } else { 1159 // use left instead: 1160 mid = l; 1161 mid[0] = cx0; mid[1] = cy0; 1162 mid[2] = x1; mid[3] = y1; 1163 mid[4] = x2; mid[5] = y2; 1164 mid[6] = x3; mid[7] = y3; 1165 } 1166 final float[] r = rp; 1167 1168 int kind = 0; 1169 for (int i = 0, off = 0; i <= nSplits; i++, off += 6) { 1170 kind = computeOffsetCubic(mid, off, l, r); 1171 1172 emitLineTo(l[0], l[1]); 1173 1174 switch(kind) { 1175 case 8: 1176 emitCurveTo(l[2], l[3], l[4], l[5], l[6], l[7]); 1177 emitCurveToRev(r[0], r[1], r[2], r[3], r[4], r[5]); 1178 break; 1179 case 4: 1180 emitLineTo(l[2], l[3]); 1181 emitLineToRev(r[0], r[1]); 1182 break; 1183 default: 1184 } 1185 emitLineToRev(r[kind - 2], r[kind - 1]); 1186 } 1187 1188 this.prev = DRAWING_OP_TO; 1189 this.cx0 = x3; 1190 this.cy0 = y3; 1191 this.cdx = dxf; 1192 this.cdy = dyf; 1193 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f; 1194 this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f; 1195 } 1196 1197 @Override quadTo(final float x1, final float y1, final float x2, final float y2)1198 public void quadTo(final float x1, final float y1, 1199 final float x2, final float y2) 1200 { 1201 final int outcode0 = this.cOutCode; 1202 1203 if (clipRect != null) { 1204 final int outcode1 = Helpers.outcode(x1, y1, clipRect); 1205 final int outcode2 = Helpers.outcode(x2, y2, clipRect); 1206 1207 // Should clip 1208 final int orCode = (outcode0 | outcode1 | outcode2); 1209 if (orCode != 0) { 1210 final int sideCode = outcode0 & outcode1 & outcode2; 1211 1212 // basic rejection criteria: 1213 if (sideCode == 0) { 1214 // overlap clip: 1215 if (subdivide) { 1216 // avoid reentrance 1217 subdivide = false; 1218 // subdivide curve => call lineTo() with subdivided curves: 1219 boolean ret = curveSplitter.splitQuad(cx0, cy0, x1, y1, 1220 x2, y2, orCode, this); 1221 // reentrance is done: 1222 subdivide = true; 1223 if (ret) { 1224 return; 1225 } 1226 } 1227 // already subdivided so render it 1228 } else { 1229 this.cOutCode = outcode2; 1230 _moveTo(x2, y2, outcode0); 1231 opened = true; 1232 return; 1233 } 1234 } 1235 1236 this.cOutCode = outcode2; 1237 } 1238 _quadTo(x1, y1, x2, y2, outcode0); 1239 } 1240 _quadTo(final float x1, final float y1, final float x2, final float y2, final int outcode0)1241 private void _quadTo(final float x1, final float y1, 1242 final float x2, final float y2, 1243 final int outcode0) 1244 { 1245 // need these so we can update the state at the end of this method 1246 float dxs = x1 - cx0; 1247 float dys = y1 - cy0; 1248 float dxf = x2 - x1; 1249 float dyf = y2 - y1; 1250 1251 if (((dxs == 0.0f) && (dys == 0.0f)) || ((dxf == 0.0f) && (dyf == 0.0f))) { 1252 dxs = dxf = x2 - cx0; 1253 dys = dyf = y2 - cy0; 1254 } 1255 if ((dxs == 0.0f) && (dys == 0.0f)) { 1256 // this happens if the "curve" is just a point 1257 // fix outcode0 for lineTo() call: 1258 if (clipRect != null) { 1259 this.cOutCode = outcode0; 1260 } 1261 lineTo(cx0, cy0); 1262 return; 1263 } 1264 // if these vectors are too small, normalize them, to avoid future 1265 // precision problems. 1266 if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) { 1267 final float len = (float)Math.sqrt(dxs * dxs + dys * dys); 1268 dxs /= len; 1269 dys /= len; 1270 } 1271 if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) { 1272 final float len = (float)Math.sqrt(dxf * dxf + dyf * dyf); 1273 dxf /= len; 1274 dyf /= len; 1275 } 1276 computeOffset(dxs, dys, lineWidth2, offset0); 1277 drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0); 1278 1279 int nSplits = 0; 1280 final float[] mid; 1281 final float[] l = lp; 1282 1283 if (monotonize) { 1284 // monotonize quad: 1285 final CurveBasicMonotonizer monotonizer 1286 = rdrCtx.monotonizer.quad(cx0, cy0, x1, y1, x2, y2); 1287 1288 nSplits = monotonizer.nbSplits; 1289 mid = monotonizer.middle; 1290 } else { 1291 // use left instead: 1292 mid = l; 1293 mid[0] = cx0; mid[1] = cy0; 1294 mid[2] = x1; mid[3] = y1; 1295 mid[4] = x2; mid[5] = y2; 1296 } 1297 final float[] r = rp; 1298 1299 int kind = 0; 1300 for (int i = 0, off = 0; i <= nSplits; i++, off += 4) { 1301 kind = computeOffsetQuad(mid, off, l, r); 1302 1303 emitLineTo(l[0], l[1]); 1304 1305 switch(kind) { 1306 case 6: 1307 emitQuadTo(l[2], l[3], l[4], l[5]); 1308 emitQuadToRev(r[0], r[1], r[2], r[3]); 1309 break; 1310 case 4: 1311 emitLineTo(l[2], l[3]); 1312 emitLineToRev(r[0], r[1]); 1313 break; 1314 default: 1315 } 1316 emitLineToRev(r[kind - 2], r[kind - 1]); 1317 } 1318 1319 this.prev = DRAWING_OP_TO; 1320 this.cx0 = x2; 1321 this.cy0 = y2; 1322 this.cdx = dxf; 1323 this.cdy = dyf; 1324 this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f; 1325 this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f; 1326 } 1327 getNativeConsumer()1328 @Override public long getNativeConsumer() { 1329 throw new InternalError("Stroker doesn't use a native consumer"); 1330 } 1331 } 1332