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 sun.awt.geom.PathConsumer2D; 29 import java.awt.geom.AffineTransform; 30 import java.awt.geom.Path2D; 31 import java.util.Arrays; 32 import sun.java2d.marlin.Helpers.IndexStack; 33 import sun.java2d.marlin.Helpers.PolyStack; 34 35 final class TransformingPathConsumer2D { 36 37 // higher uncertainty in float variant for huge shapes > 10^7 38 static final float CLIP_RECT_PADDING = 1.0f; 39 40 private final RendererContext rdrCtx; 41 42 // recycled ClosedPathDetector instance from detectClosedPath() 43 private final ClosedPathDetector cpDetector; 44 45 // recycled PathClipFilter instance from pathClipper() 46 private final PathClipFilter pathClipper; 47 48 // recycled PathConsumer2D instance from wrapPath2D() 49 private final Path2DWrapper wp_Path2DWrapper = new Path2DWrapper(); 50 51 // recycled PathConsumer2D instances from deltaTransformConsumer() 52 private final DeltaScaleFilter dt_DeltaScaleFilter = new DeltaScaleFilter(); 53 private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter(); 54 55 // recycled PathConsumer2D instances from inverseDeltaTransformConsumer() 56 private final DeltaScaleFilter iv_DeltaScaleFilter = new DeltaScaleFilter(); 57 private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter(); 58 59 // recycled PathTracer instances from tracer...() methods 60 private final PathTracer tracerInput = new PathTracer("[Input]"); 61 private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector"); 62 private final PathTracer tracerFiller = new PathTracer("Filler"); 63 private final PathTracer tracerStroker = new PathTracer("Stroker"); 64 private final PathTracer tracerDasher = new PathTracer("Dasher"); 65 66 TransformingPathConsumer2D(final RendererContext rdrCtx) { 67 // used by RendererContext 68 this.rdrCtx = rdrCtx; 69 this.cpDetector = new ClosedPathDetector(rdrCtx); 70 this.pathClipper = new PathClipFilter(rdrCtx); 71 } 72 73 PathConsumer2D wrapPath2D(Path2D.Float p2d) { 74 return wp_Path2DWrapper.init(p2d); 75 } 76 77 PathConsumer2D traceInput(PathConsumer2D out) { 78 return tracerInput.init(out); 79 } 80 81 PathConsumer2D traceClosedPathDetector(PathConsumer2D out) { 82 return tracerCPDetector.init(out); 83 } 84 85 PathConsumer2D traceFiller(PathConsumer2D out) { 86 return tracerFiller.init(out); 87 } 88 89 PathConsumer2D traceStroker(PathConsumer2D out) { 90 return tracerStroker.init(out); 91 } 92 93 PathConsumer2D traceDasher(PathConsumer2D out) { 94 return tracerDasher.init(out); 95 } 96 97 PathConsumer2D detectClosedPath(PathConsumer2D out) { 98 return cpDetector.init(out); 99 } 100 101 PathConsumer2D pathClipper(PathConsumer2D out) { 102 return pathClipper.init(out); 103 } 104 105 PathConsumer2D deltaTransformConsumer(PathConsumer2D out, 106 AffineTransform at) 107 { 108 if (at == null) { 109 return out; 110 } 111 final float mxx = (float) at.getScaleX(); 112 final float mxy = (float) at.getShearX(); 113 final float myx = (float) at.getShearY(); 114 final float myy = (float) at.getScaleY(); 115 116 if (mxy == 0.0f && myx == 0.0f) { 117 if (mxx == 1.0f && myy == 1.0f) { 118 return out; 119 } else { 120 // Scale only 121 if (rdrCtx.doClip) { 122 // adjust clip rectangle (ymin, ymax, xmin, xmax): 123 adjustClipScale(rdrCtx.clipRect, mxx, myy); 124 } 125 return dt_DeltaScaleFilter.init(out, mxx, myy); 126 } 127 } else { 128 if (rdrCtx.doClip) { 129 // adjust clip rectangle (ymin, ymax, xmin, xmax): 130 adjustClipInverseDelta(rdrCtx.clipRect, mxx, mxy, myx, myy); 131 } 132 return dt_DeltaTransformFilter.init(out, mxx, mxy, myx, myy); 133 } 134 } 135 136 private static void adjustClipOffset(final float[] clipRect) { 137 clipRect[0] += Renderer.RDR_OFFSET_Y; 138 clipRect[1] += Renderer.RDR_OFFSET_Y; 139 clipRect[2] += Renderer.RDR_OFFSET_X; 140 clipRect[3] += Renderer.RDR_OFFSET_X; 141 } 142 143 private static void adjustClipScale(final float[] clipRect, 144 final float mxx, final float myy) 145 { 146 adjustClipOffset(clipRect); 147 148 // Adjust the clipping rectangle (iv_DeltaScaleFilter): 149 clipRect[0] /= myy; 150 clipRect[1] /= myy; 151 clipRect[2] /= mxx; 152 clipRect[3] /= mxx; 153 } 154 155 private static void adjustClipInverseDelta(final float[] clipRect, 156 final float mxx, final float mxy, 157 final float myx, final float myy) 158 { 159 adjustClipOffset(clipRect); 160 161 // Adjust the clipping rectangle (iv_DeltaTransformFilter): 162 final float det = mxx * myy - mxy * myx; 163 final float imxx = myy / det; 164 final float imxy = -mxy / det; 165 final float imyx = -myx / det; 166 final float imyy = mxx / det; 167 168 float xmin, xmax, ymin, ymax; 169 float x, y; 170 // xmin, ymin: 171 x = clipRect[2] * imxx + clipRect[0] * imxy; 172 y = clipRect[2] * imyx + clipRect[0] * imyy; 173 174 xmin = xmax = x; 175 ymin = ymax = y; 176 177 // xmax, ymin: 178 x = clipRect[3] * imxx + clipRect[0] * imxy; 179 y = clipRect[3] * imyx + clipRect[0] * imyy; 180 181 if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; } 182 if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; } 183 184 // xmin, ymax: 185 x = clipRect[2] * imxx + clipRect[1] * imxy; 186 y = clipRect[2] * imyx + clipRect[1] * imyy; 187 188 if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; } 189 if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; } 190 191 // xmax, ymax: 192 x = clipRect[3] * imxx + clipRect[1] * imxy; 193 y = clipRect[3] * imyx + clipRect[1] * imyy; 194 195 if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; } 196 if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; } 197 198 clipRect[0] = ymin; 199 clipRect[1] = ymax; 200 clipRect[2] = xmin; 201 clipRect[3] = xmax; 202 } 203 204 PathConsumer2D inverseDeltaTransformConsumer(PathConsumer2D out, 205 AffineTransform at) 206 { 207 if (at == null) { 208 return out; 209 } 210 float mxx = (float) at.getScaleX(); 211 float mxy = (float) at.getShearX(); 212 float myx = (float) at.getShearY(); 213 float myy = (float) at.getScaleY(); 214 215 if (mxy == 0.0f && myx == 0.0f) { 216 if (mxx == 1.0f && myy == 1.0f) { 217 return out; 218 } else { 219 return iv_DeltaScaleFilter.init(out, 1.0f/mxx, 1.0f/myy); 220 } 221 } else { 222 final float det = mxx * myy - mxy * myx; 223 return iv_DeltaTransformFilter.init(out, 224 myy / det, 225 -mxy / det, 226 -myx / det, 227 mxx / det); 228 } 229 } 230 231 static final class DeltaScaleFilter implements PathConsumer2D { 232 private PathConsumer2D out; 233 private float sx, sy; 234 235 DeltaScaleFilter() {} 236 237 DeltaScaleFilter init(PathConsumer2D out, 238 float mxx, float myy) 239 { 240 this.out = out; 241 sx = mxx; 242 sy = myy; 243 return this; // fluent API 244 } 245 246 @Override 247 public void moveTo(float x0, float y0) { 248 out.moveTo(x0 * sx, y0 * sy); 249 } 250 251 @Override 252 public void lineTo(float x1, float y1) { 253 out.lineTo(x1 * sx, y1 * sy); 254 } 255 256 @Override 257 public void quadTo(float x1, float y1, 258 float x2, float y2) 259 { 260 out.quadTo(x1 * sx, y1 * sy, 261 x2 * sx, y2 * sy); 262 } 263 264 @Override 265 public void curveTo(float x1, float y1, 266 float x2, float y2, 267 float x3, float y3) 268 { 269 out.curveTo(x1 * sx, y1 * sy, 270 x2 * sx, y2 * sy, 271 x3 * sx, y3 * sy); 272 } 273 274 @Override 275 public void closePath() { 276 out.closePath(); 277 } 278 279 @Override 280 public void pathDone() { 281 out.pathDone(); 282 } 283 284 @Override 285 public long getNativeConsumer() { 286 return 0; 287 } 288 } 289 290 static final class DeltaTransformFilter implements PathConsumer2D { 291 private PathConsumer2D out; 292 private float mxx, mxy, myx, myy; 293 294 DeltaTransformFilter() {} 295 296 DeltaTransformFilter init(PathConsumer2D out, 297 float mxx, float mxy, 298 float myx, float myy) 299 { 300 this.out = out; 301 this.mxx = mxx; 302 this.mxy = mxy; 303 this.myx = myx; 304 this.myy = myy; 305 return this; // fluent API 306 } 307 308 @Override 309 public void moveTo(float x0, float y0) { 310 out.moveTo(x0 * mxx + y0 * mxy, 311 x0 * myx + y0 * myy); 312 } 313 314 @Override 315 public void lineTo(float x1, float y1) { 316 out.lineTo(x1 * mxx + y1 * mxy, 317 x1 * myx + y1 * myy); 318 } 319 320 @Override 321 public void quadTo(float x1, float y1, 322 float x2, float y2) 323 { 324 out.quadTo(x1 * mxx + y1 * mxy, 325 x1 * myx + y1 * myy, 326 x2 * mxx + y2 * mxy, 327 x2 * myx + y2 * myy); 328 } 329 330 @Override 331 public void curveTo(float x1, float y1, 332 float x2, float y2, 333 float x3, float y3) 334 { 335 out.curveTo(x1 * mxx + y1 * mxy, 336 x1 * myx + y1 * myy, 337 x2 * mxx + y2 * mxy, 338 x2 * myx + y2 * myy, 339 x3 * mxx + y3 * mxy, 340 x3 * myx + y3 * myy); 341 } 342 343 @Override 344 public void closePath() { 345 out.closePath(); 346 } 347 348 @Override 349 public void pathDone() { 350 out.pathDone(); 351 } 352 353 @Override 354 public long getNativeConsumer() { 355 return 0; 356 } 357 } 358 359 static final class Path2DWrapper implements PathConsumer2D { 360 private Path2D.Float p2d; 361 362 Path2DWrapper() {} 363 364 Path2DWrapper init(Path2D.Float p2d) { 365 this.p2d = p2d; 366 return this; 367 } 368 369 @Override 370 public void moveTo(float x0, float y0) { 371 p2d.moveTo(x0, y0); 372 } 373 374 @Override 375 public void lineTo(float x1, float y1) { 376 p2d.lineTo(x1, y1); 377 } 378 379 @Override 380 public void closePath() { 381 p2d.closePath(); 382 } 383 384 @Override 385 public void pathDone() {} 386 387 @Override 388 public void curveTo(float x1, float y1, 389 float x2, float y2, 390 float x3, float y3) 391 { 392 p2d.curveTo(x1, y1, x2, y2, x3, y3); 393 } 394 395 @Override 396 public void quadTo(float x1, float y1, float x2, float y2) { 397 p2d.quadTo(x1, y1, x2, y2); 398 } 399 400 @Override 401 public long getNativeConsumer() { 402 throw new InternalError("Not using a native peer"); 403 } 404 } 405 406 static final class ClosedPathDetector implements PathConsumer2D { 407 408 private final RendererContext rdrCtx; 409 private final PolyStack stack; 410 411 private PathConsumer2D out; 412 413 ClosedPathDetector(final RendererContext rdrCtx) { 414 this.rdrCtx = rdrCtx; 415 this.stack = (rdrCtx.stats != null) ? 416 new PolyStack(rdrCtx, 417 rdrCtx.stats.stat_cpd_polystack_types, 418 rdrCtx.stats.stat_cpd_polystack_curves, 419 rdrCtx.stats.hist_cpd_polystack_curves, 420 rdrCtx.stats.stat_array_cpd_polystack_curves, 421 rdrCtx.stats.stat_array_cpd_polystack_types) 422 : new PolyStack(rdrCtx); 423 } 424 425 ClosedPathDetector init(PathConsumer2D out) { 426 this.out = out; 427 return this; // fluent API 428 } 429 430 /** 431 * Disposes this instance: 432 * clean up before reusing this instance 433 */ 434 void dispose() { 435 stack.dispose(); 436 } 437 438 @Override 439 public void pathDone() { 440 // previous path is not closed: 441 finish(false); 442 out.pathDone(); 443 444 // TODO: fix possible leak if exception happened 445 // Dispose this instance: 446 dispose(); 447 } 448 449 @Override 450 public void closePath() { 451 // path is closed 452 finish(true); 453 out.closePath(); 454 } 455 456 @Override 457 public void moveTo(float x0, float y0) { 458 // previous path is not closed: 459 finish(false); 460 out.moveTo(x0, y0); 461 } 462 463 private void finish(final boolean closed) { 464 rdrCtx.closedPath = closed; 465 stack.pullAll(out); 466 } 467 468 @Override 469 public void lineTo(float x1, float y1) { 470 stack.pushLine(x1, y1); 471 } 472 473 @Override 474 public void curveTo(float x3, float y3, 475 float x2, float y2, 476 float x1, float y1) 477 { 478 stack.pushCubic(x1, y1, x2, y2, x3, y3); 479 } 480 481 @Override 482 public void quadTo(float x2, float y2, float x1, float y1) { 483 stack.pushQuad(x1, y1, x2, y2); 484 } 485 486 @Override 487 public long getNativeConsumer() { 488 throw new InternalError("Not using a native peer"); 489 } 490 } 491 492 static final class PathClipFilter implements PathConsumer2D { 493 494 private PathConsumer2D out; 495 496 // Bounds of the drawing region, at pixel precision. 497 private final float[] clipRect; 498 499 private final float[] corners = new float[8]; 500 private boolean init_corners = false; 501 502 private final IndexStack stack; 503 504 // the current outcode of the current sub path 505 private int cOutCode = 0; 506 507 // the cumulated (and) outcode of the complete path 508 private int gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R; 509 510 private boolean outside = false; 511 512 // The current point (TODO stupid repeated info) 513 private float cx0, cy0; 514 515 // The current point OUTSIDE 516 private float cox0, coy0; 517 518 private boolean subdivide = MarlinConst.DO_CLIP_SUBDIVIDER; 519 private final CurveClipSplitter curveSplitter; 520 521 PathClipFilter(final RendererContext rdrCtx) { 522 this.clipRect = rdrCtx.clipRect; 523 this.curveSplitter = rdrCtx.curveClipSplitter; 524 525 this.stack = (rdrCtx.stats != null) ? 526 new IndexStack(rdrCtx, 527 rdrCtx.stats.stat_pcf_idxstack_indices, 528 rdrCtx.stats.hist_pcf_idxstack_indices, 529 rdrCtx.stats.stat_array_pcf_idxstack_indices) 530 : new IndexStack(rdrCtx); 531 } 532 533 PathClipFilter init(final PathConsumer2D out) { 534 this.out = out; 535 536 // Adjust the clipping rectangle with the renderer offsets 537 final float rdrOffX = Renderer.RDR_OFFSET_X; 538 final float rdrOffY = Renderer.RDR_OFFSET_Y; 539 540 // add a small rounding error: 541 final float margin = 1e-3f; 542 543 final float[] _clipRect = this.clipRect; 544 _clipRect[0] -= margin - rdrOffY; 545 _clipRect[1] += margin + rdrOffY; 546 _clipRect[2] -= margin - rdrOffX; 547 _clipRect[3] += margin + rdrOffX; 548 549 if (MarlinConst.DO_CLIP_SUBDIVIDER) { 550 // adjust padded clip rectangle: 551 curveSplitter.init(); 552 } 553 554 this.init_corners = true; 555 this.gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R; 556 557 return this; // fluent API 558 } 559 560 /** 561 * Disposes this instance: 562 * clean up before reusing this instance 563 */ 564 void dispose() { 565 stack.dispose(); 566 } 567 568 private void finishPath() { 569 if (outside) { 570 // criteria: inside or totally outside ? 571 if (gOutCode == 0) { 572 finish(); 573 } else { 574 this.outside = false; 575 stack.reset(); 576 } 577 } 578 } 579 580 private void finish() { 581 this.outside = false; 582 583 if (!stack.isEmpty()) { 584 if (init_corners) { 585 init_corners = false; 586 587 final float[] _corners = corners; 588 final float[] _clipRect = clipRect; 589 // Top Left (0): 590 _corners[0] = _clipRect[2]; 591 _corners[1] = _clipRect[0]; 592 // Bottom Left (1): 593 _corners[2] = _clipRect[2]; 594 _corners[3] = _clipRect[1]; 595 // Top right (2): 596 _corners[4] = _clipRect[3]; 597 _corners[5] = _clipRect[0]; 598 // Bottom Right (3): 599 _corners[6] = _clipRect[3]; 600 _corners[7] = _clipRect[1]; 601 } 602 stack.pullAll(corners, out); 603 } 604 out.lineTo(cox0, coy0); 605 this.cx0 = cox0; 606 this.cy0 = coy0; 607 } 608 609 @Override 610 public void pathDone() { 611 finishPath(); 612 613 out.pathDone(); 614 615 // TODO: fix possible leak if exception happened 616 // Dispose this instance: 617 dispose(); 618 } 619 620 @Override 621 public void closePath() { 622 finishPath(); 623 624 out.closePath(); 625 } 626 627 @Override 628 public void moveTo(final float x0, final float y0) { 629 finishPath(); 630 631 this.cOutCode = Helpers.outcode(x0, y0, clipRect); 632 this.outside = false; 633 out.moveTo(x0, y0); 634 this.cx0 = x0; 635 this.cy0 = y0; 636 } 637 638 @Override 639 public void lineTo(final float xe, final float ye) { 640 final int outcode0 = this.cOutCode; 641 final int outcode1 = Helpers.outcode(xe, ye, clipRect); 642 643 // Should clip 644 final int orCode = (outcode0 | outcode1); 645 if (orCode != 0) { 646 final int sideCode = (outcode0 & outcode1); 647 648 // basic rejection criteria: 649 if (sideCode == 0) { 650 // ovelap clip: 651 if (subdivide) { 652 // avoid reentrance 653 subdivide = false; 654 boolean ret; 655 // subdivide curve => callback with subdivided parts: 656 if (outside) { 657 ret = curveSplitter.splitLine(cox0, coy0, xe, ye, 658 orCode, this); 659 } else { 660 ret = curveSplitter.splitLine(cx0, cy0, xe, ye, 661 orCode, this); 662 } 663 // reentrance is done: 664 subdivide = true; 665 if (ret) { 666 return; 667 } 668 } 669 // already subdivided so render it 670 } else { 671 this.cOutCode = outcode1; 672 this.gOutCode &= sideCode; 673 // keep last point coordinate before entering the clip again: 674 this.outside = true; 675 this.cox0 = xe; 676 this.coy0 = ye; 677 678 clip(sideCode, outcode0, outcode1); 679 return; 680 } 681 } 682 683 this.cOutCode = outcode1; 684 this.gOutCode = 0; 685 686 if (outside) { 687 finish(); 688 } 689 // clipping disabled: 690 out.lineTo(xe, ye); 691 this.cx0 = xe; 692 this.cy0 = ye; 693 } 694 695 private void clip(final int sideCode, 696 final int outcode0, 697 final int outcode1) 698 { 699 // corner or cross-boundary on left or right side: 700 if ((outcode0 != outcode1) 701 && ((sideCode & MarlinConst.OUTCODE_MASK_L_R) != 0)) 702 { 703 // combine outcodes: 704 final int mergeCode = (outcode0 | outcode1); 705 final int tbCode = mergeCode & MarlinConst.OUTCODE_MASK_T_B; 706 final int lrCode = mergeCode & MarlinConst.OUTCODE_MASK_L_R; 707 final int off = (lrCode == MarlinConst.OUTCODE_LEFT) ? 0 : 2; 708 709 // add corners to outside stack: 710 switch (tbCode) { 711 case MarlinConst.OUTCODE_TOP: 712 stack.push(off); // top 713 return; 714 case MarlinConst.OUTCODE_BOTTOM: 715 stack.push(off + 1); // bottom 716 return; 717 default: 718 // both TOP / BOTTOM: 719 if ((outcode0 & MarlinConst.OUTCODE_TOP) != 0) { 720 // top to bottom 721 stack.push(off); // top 722 stack.push(off + 1); // bottom 723 } else { 724 // bottom to top 725 stack.push(off + 1); // bottom 726 stack.push(off); // top 727 } 728 } 729 } 730 } 731 732 @Override 733 public void curveTo(final float x1, final float y1, 734 final float x2, final float y2, 735 final float xe, final float ye) 736 { 737 final int outcode0 = this.cOutCode; 738 final int outcode1 = Helpers.outcode(x1, y1, clipRect); 739 final int outcode2 = Helpers.outcode(x2, y2, clipRect); 740 final int outcode3 = Helpers.outcode(xe, ye, clipRect); 741 742 // Should clip 743 final int orCode = (outcode0 | outcode1 | outcode2 | outcode3); 744 if (orCode != 0) { 745 final int sideCode = outcode0 & outcode1 & outcode2 & outcode3; 746 747 // basic rejection criteria: 748 if (sideCode == 0) { 749 // ovelap clip: 750 if (subdivide) { 751 // avoid reentrance 752 subdivide = false; 753 // subdivide curve => callback with subdivided parts: 754 boolean ret; 755 if (outside) { 756 ret = curveSplitter.splitCurve(cox0, coy0, x1, y1, 757 x2, y2, xe, ye, 758 orCode, this); 759 } else { 760 ret = curveSplitter.splitCurve(cx0, cy0, x1, y1, 761 x2, y2, xe, ye, 762 orCode, this); 763 } 764 // reentrance is done: 765 subdivide = true; 766 if (ret) { 767 return; 768 } 769 } 770 // already subdivided so render it 771 } else { 772 this.cOutCode = outcode3; 773 this.gOutCode &= sideCode; 774 // keep last point coordinate before entering the clip again: 775 this.outside = true; 776 this.cox0 = xe; 777 this.coy0 = ye; 778 779 clip(sideCode, outcode0, outcode3); 780 return; 781 } 782 } 783 784 this.cOutCode = outcode3; 785 this.gOutCode = 0; 786 787 if (outside) { 788 finish(); 789 } 790 // clipping disabled: 791 out.curveTo(x1, y1, x2, y2, xe, ye); 792 this.cx0 = xe; 793 this.cy0 = ye; 794 } 795 796 @Override 797 public void quadTo(final float x1, final float y1, 798 final float xe, final float ye) 799 { 800 final int outcode0 = this.cOutCode; 801 final int outcode1 = Helpers.outcode(x1, y1, clipRect); 802 final int outcode2 = Helpers.outcode(xe, ye, clipRect); 803 804 // Should clip 805 final int orCode = (outcode0 | outcode1 | outcode2); 806 if (orCode != 0) { 807 final int sideCode = outcode0 & outcode1 & outcode2; 808 809 // basic rejection criteria: 810 if (sideCode == 0) { 811 // ovelap clip: 812 if (subdivide) { 813 // avoid reentrance 814 subdivide = false; 815 // subdivide curve => callback with subdivided parts: 816 boolean ret; 817 if (outside) { 818 ret = curveSplitter.splitQuad(cox0, coy0, x1, y1, 819 xe, ye, orCode, this); 820 } else { 821 ret = curveSplitter.splitQuad(cx0, cy0, x1, y1, 822 xe, ye, orCode, this); 823 } 824 // reentrance is done: 825 subdivide = true; 826 if (ret) { 827 return; 828 } 829 } 830 // already subdivided so render it 831 } else { 832 this.cOutCode = outcode2; 833 this.gOutCode &= sideCode; 834 // keep last point coordinate before entering the clip again: 835 this.outside = true; 836 this.cox0 = xe; 837 this.coy0 = ye; 838 839 clip(sideCode, outcode0, outcode2); 840 return; 841 } 842 } 843 844 this.cOutCode = outcode2; 845 this.gOutCode = 0; 846 847 if (outside) { 848 finish(); 849 } 850 // clipping disabled: 851 out.quadTo(x1, y1, xe, ye); 852 this.cx0 = xe; 853 this.cy0 = ye; 854 } 855 856 @Override 857 public long getNativeConsumer() { 858 throw new InternalError("Not using a native peer"); 859 } 860 } 861 862 static final class CurveClipSplitter { 863 864 static final float LEN_TH = MarlinProperties.getSubdividerMinLength(); 865 static final boolean DO_CHECK_LENGTH = (LEN_TH > 0.0f); 866 867 private static final boolean TRACE = false; 868 869 private static final int MAX_N_CURVES = 3 * 4; 870 871 // clip rectangle (ymin, ymax, xmin, xmax): 872 final float[] clipRect; 873 874 // clip rectangle (ymin, ymax, xmin, xmax) including padding: 875 final float[] clipRectPad = new float[4]; 876 private boolean init_clipRectPad = false; 877 878 // This is where the curve to be processed is put. We give it 879 // enough room to store all curves. 880 final float[] middle = new float[MAX_N_CURVES * 8 + 2]; 881 // t values at subdivision points 882 private final float[] subdivTs = new float[MAX_N_CURVES]; 883 884 // dirty curve 885 private final Curve curve; 886 887 CurveClipSplitter(final RendererContext rdrCtx) { 888 this.clipRect = rdrCtx.clipRect; 889 this.curve = rdrCtx.curve; 890 } 891 892 void init() { 893 this.init_clipRectPad = true; 894 } 895 896 private void initPaddedClip() { 897 // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY 898 // adjust padded clip rectangle (ymin, ymax, xmin, xmax): 899 // add a rounding error (curve subdivision ~ 0.1px): 900 final float[] _clipRect = clipRect; 901 final float[] _clipRectPad = clipRectPad; 902 903 _clipRectPad[0] = _clipRect[0] - CLIP_RECT_PADDING; 904 _clipRectPad[1] = _clipRect[1] + CLIP_RECT_PADDING; 905 _clipRectPad[2] = _clipRect[2] - CLIP_RECT_PADDING; 906 _clipRectPad[3] = _clipRect[3] + CLIP_RECT_PADDING; 907 908 if (TRACE) { 909 MarlinUtils.logInfo("clip: X [" + _clipRectPad[2] + " .. " + _clipRectPad[3] +"] " 910 + "Y ["+ _clipRectPad[0] + " .. " + _clipRectPad[1] +"]"); 911 } 912 } 913 914 boolean splitLine(final float x0, final float y0, 915 final float x1, final float y1, 916 final int outCodeOR, 917 final PathConsumer2D out) 918 { 919 if (TRACE) { 920 MarlinUtils.logInfo("divLine P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ")"); 921 } 922 923 if (DO_CHECK_LENGTH && Helpers.fastLineLen(x0, y0, x1, y1) <= LEN_TH) { 924 return false; 925 } 926 927 final float[] mid = middle; 928 mid[0] = x0; mid[1] = y0; 929 mid[2] = x1; mid[3] = y1; 930 931 return subdivideAtIntersections(4, outCodeOR, out); 932 } 933 934 boolean splitQuad(final float x0, final float y0, 935 final float x1, final float y1, 936 final float x2, final float y2, 937 final int outCodeOR, 938 final PathConsumer2D out) 939 { 940 if (TRACE) { 941 MarlinUtils.logInfo("divQuad P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ")"); 942 } 943 944 if (DO_CHECK_LENGTH && Helpers.fastQuadLen(x0, y0, x1, y1, x2, y2) <= LEN_TH) { 945 return false; 946 } 947 948 final float[] mid = middle; 949 mid[0] = x0; mid[1] = y0; 950 mid[2] = x1; mid[3] = y1; 951 mid[4] = x2; mid[5] = y2; 952 953 return subdivideAtIntersections(6, outCodeOR, out); 954 } 955 956 boolean splitCurve(final float x0, final float y0, 957 final float x1, final float y1, 958 final float x2, final float y2, 959 final float x3, final float y3, 960 final int outCodeOR, 961 final PathConsumer2D out) 962 { 963 if (TRACE) { 964 MarlinUtils.logInfo("divCurve P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ")"); 965 } 966 967 if (DO_CHECK_LENGTH && Helpers.fastCurvelen(x0, y0, x1, y1, x2, y2, x3, y3) <= LEN_TH) { 968 return false; 969 } 970 971 final float[] mid = middle; 972 mid[0] = x0; mid[1] = y0; 973 mid[2] = x1; mid[3] = y1; 974 mid[4] = x2; mid[5] = y2; 975 mid[6] = x3; mid[7] = y3; 976 977 return subdivideAtIntersections(8, outCodeOR, out); 978 } 979 980 private boolean subdivideAtIntersections(final int type, final int outCodeOR, 981 final PathConsumer2D out) 982 { 983 final float[] mid = middle; 984 final float[] subTs = subdivTs; 985 986 if (init_clipRectPad) { 987 init_clipRectPad = false; 988 initPaddedClip(); 989 } 990 991 final int nSplits = Helpers.findClipPoints(curve, mid, subTs, type, 992 outCodeOR, clipRectPad); 993 994 if (TRACE) { 995 MarlinUtils.logInfo("nSplits: "+ nSplits); 996 MarlinUtils.logInfo("subTs: "+Arrays.toString(Arrays.copyOfRange(subTs, 0, nSplits))); 997 } 998 if (nSplits == 0) { 999 // only curve support shortcut 1000 return false; 1001 } 1002 float prevT = 0.0f; 1003 1004 for (int i = 0, off = 0; i < nSplits; i++, off += type) { 1005 final float t = subTs[i]; 1006 1007 Helpers.subdivideAt((t - prevT) / (1.0f - prevT), 1008 mid, off, mid, off, type); 1009 prevT = t; 1010 } 1011 1012 for (int i = 0, off = 0; i <= nSplits; i++, off += type) { 1013 if (TRACE) { 1014 MarlinUtils.logInfo("Part Curve "+Arrays.toString(Arrays.copyOfRange(mid, off, off + type))); 1015 } 1016 emitCurrent(type, mid, off, out); 1017 } 1018 return true; 1019 } 1020 1021 static void emitCurrent(final int type, final float[] pts, 1022 final int off, final PathConsumer2D out) 1023 { 1024 // if instead of switch (perf + most probable cases first) 1025 if (type == 8) { 1026 out.curveTo(pts[off + 2], pts[off + 3], 1027 pts[off + 4], pts[off + 5], 1028 pts[off + 6], pts[off + 7]); 1029 } else if (type == 4) { 1030 out.lineTo(pts[off + 2], pts[off + 3]); 1031 } else { 1032 out.quadTo(pts[off + 2], pts[off + 3], 1033 pts[off + 4], pts[off + 5]); 1034 } 1035 } 1036 } 1037 1038 static final class CurveBasicMonotonizer { 1039 1040 private static final int MAX_N_CURVES = 11; 1041 1042 // squared half line width (for stroker) 1043 private float lw2; 1044 1045 // number of splitted curves 1046 int nbSplits; 1047 1048 // This is where the curve to be processed is put. We give it 1049 // enough room to store all curves. 1050 final float[] middle = new float[MAX_N_CURVES * 6 + 2]; 1051 // t values at subdivision points 1052 private final float[] subdivTs = new float[MAX_N_CURVES - 1]; 1053 1054 // dirty curve 1055 private final Curve curve; 1056 1057 CurveBasicMonotonizer(final RendererContext rdrCtx) { 1058 this.curve = rdrCtx.curve; 1059 } 1060 1061 void init(final float lineWidth) { 1062 this.lw2 = (lineWidth * lineWidth) / 4.0f; 1063 } 1064 1065 CurveBasicMonotonizer curve(final float x0, final float y0, 1066 final float x1, final float y1, 1067 final float x2, final float y2, 1068 final float x3, final float y3) 1069 { 1070 final float[] mid = middle; 1071 mid[0] = x0; mid[1] = y0; 1072 mid[2] = x1; mid[3] = y1; 1073 mid[4] = x2; mid[5] = y2; 1074 mid[6] = x3; mid[7] = y3; 1075 1076 final float[] subTs = subdivTs; 1077 final int nSplits = Helpers.findSubdivPoints(curve, mid, subTs, 8, lw2); 1078 1079 float prevT = 0.0f; 1080 for (int i = 0, off = 0; i < nSplits; i++, off += 6) { 1081 final float t = subTs[i]; 1082 1083 Helpers.subdivideCubicAt((t - prevT) / (1.0f - prevT), 1084 mid, off, mid, off, off + 6); 1085 prevT = t; 1086 } 1087 1088 this.nbSplits = nSplits; 1089 return this; 1090 } 1091 1092 CurveBasicMonotonizer quad(final float x0, final float y0, 1093 final float x1, final float y1, 1094 final float x2, final float y2) 1095 { 1096 final float[] mid = middle; 1097 mid[0] = x0; mid[1] = y0; 1098 mid[2] = x1; mid[3] = y1; 1099 mid[4] = x2; mid[5] = y2; 1100 1101 final float[] subTs = subdivTs; 1102 final int nSplits = Helpers.findSubdivPoints(curve, mid, subTs, 6, lw2); 1103 1104 float prevt = 0.0f; 1105 for (int i = 0, off = 0; i < nSplits; i++, off += 4) { 1106 final float t = subTs[i]; 1107 Helpers.subdivideQuadAt((t - prevt) / (1.0f - prevt), 1108 mid, off, mid, off, off + 4); 1109 prevt = t; 1110 } 1111 1112 this.nbSplits = nSplits; 1113 return this; 1114 } 1115 } 1116 1117 static final class PathTracer implements PathConsumer2D { 1118 private final String prefix; 1119 private PathConsumer2D out; 1120 1121 PathTracer(String name) { 1122 this.prefix = name + ": "; 1123 } 1124 1125 PathTracer init(PathConsumer2D out) { 1126 this.out = out; 1127 return this; // fluent API 1128 } 1129 1130 @Override 1131 public void moveTo(float x0, float y0) { 1132 log("moveTo (" + x0 + ", " + y0 + ')'); 1133 out.moveTo(x0, y0); 1134 } 1135 1136 @Override 1137 public void lineTo(float x1, float y1) { 1138 log("lineTo (" + x1 + ", " + y1 + ')'); 1139 out.lineTo(x1, y1); 1140 } 1141 1142 @Override 1143 public void curveTo(float x1, float y1, 1144 float x2, float y2, 1145 float x3, float y3) 1146 { 1147 log("curveTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ')'); 1148 out.curveTo(x1, y1, x2, y2, x3, y3); 1149 } 1150 1151 @Override 1152 public void quadTo(float x1, float y1, float x2, float y2) { 1153 log("quadTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ')'); 1154 out.quadTo(x1, y1, x2, y2); 1155 } 1156 1157 @Override 1158 public void closePath() { 1159 log("closePath"); 1160 out.closePath(); 1161 } 1162 1163 @Override 1164 public void pathDone() { 1165 log("pathDone"); 1166 out.pathDone(); 1167 } 1168 1169 private void log(final String message) { 1170 MarlinUtils.logInfo(prefix + message); 1171 } 1172 1173 @Override 1174 public long getNativeConsumer() { 1175 throw new InternalError("Not using a native peer"); 1176 } 1177 } 1178 }