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