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.transform.BaseTransform; 30 import com.sun.marlin.DHelpers.IndexStack; 31 import com.sun.marlin.DHelpers.PolyStack; 32 import java.util.Arrays; 33 34 public final class DTransformingPathConsumer2D { 35 36 // smaller uncertainty in double variant 37 static final double CLIP_RECT_PADDING = 0.25d; 38 39 private final DRendererContext rdrCtx; 40 41 // recycled ClosedPathDetector instance from detectClosedPath() 42 private final ClosedPathDetector cpDetector; 43 44 // recycled PathClipFilter instance from pathClipper() 45 private final PathClipFilter pathClipper; 46 47 // recycled DPathConsumer2D instance from wrapPath2D() 48 private final Path2DWrapper wp_Path2DWrapper = new Path2DWrapper(); 49 50 // recycled DPathConsumer2D instances from deltaTransformConsumer() 51 private final DeltaScaleFilter dt_DeltaScaleFilter = new DeltaScaleFilter(); 52 private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter(); 53 54 // recycled DPathConsumer2D instances from inverseDeltaTransformConsumer() 55 private final DeltaScaleFilter iv_DeltaScaleFilter = new DeltaScaleFilter(); 56 private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter(); 57 58 // recycled PathTracer instances from tracer...() methods 59 private final PathTracer tracerInput = new PathTracer("[Input]"); 60 private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector"); 61 private final PathTracer tracerFiller = new PathTracer("Filler"); 62 private final PathTracer tracerStroker = new PathTracer("Stroker"); 63 private final PathTracer tracerDasher = new PathTracer("Dasher"); 64 65 DTransformingPathConsumer2D(final DRendererContext rdrCtx) { 66 // used by RendererContext 67 this.rdrCtx = rdrCtx; 68 this.cpDetector = new ClosedPathDetector(rdrCtx); 69 this.pathClipper = new PathClipFilter(rdrCtx); 70 } 71 72 public DPathConsumer2D wrapPath2D(Path2D p2d) { 73 return wp_Path2DWrapper.init(p2d); 74 } 75 76 public DPathConsumer2D traceInput(DPathConsumer2D out) { 77 return tracerInput.init(out); 78 } 79 80 public DPathConsumer2D traceClosedPathDetector(DPathConsumer2D out) { 81 return tracerCPDetector.init(out); 82 } 83 84 public DPathConsumer2D traceFiller(DPathConsumer2D out) { 85 return tracerFiller.init(out); 86 } 87 88 public DPathConsumer2D traceStroker(DPathConsumer2D out) { 89 return tracerStroker.init(out); 90 } 91 92 public DPathConsumer2D traceDasher(DPathConsumer2D out) { 93 return tracerDasher.init(out); 94 } 95 96 public DPathConsumer2D detectClosedPath(DPathConsumer2D out) { 97 return cpDetector.init(out); 98 } 99 100 public DPathConsumer2D pathClipper(DPathConsumer2D out, 101 final double rdrOffX, 102 final double rdrOffY) 103 { 104 return pathClipper.init(out, rdrOffX, rdrOffY); 105 } 106 107 public DPathConsumer2D deltaTransformConsumer(DPathConsumer2D out, 108 BaseTransform at, 109 final double rdrOffX, 110 final double rdrOffY) 111 { 112 if (at == null) { 113 return out; 114 } 115 final double mxx = at.getMxx(); 116 final double mxy = at.getMxy(); 117 final double myx = at.getMyx(); 118 final double myy = at.getMyy(); 119 120 if (mxy == 0.0d && myx == 0.0d) { 121 if (mxx == 1.0d && myy == 1.0d) { 122 return out; 123 } else { 124 // Scale only 125 if (rdrCtx.doClip) { 126 // adjust clip rectangle (ymin, ymax, xmin, xmax): 127 adjustClipOffset(rdrCtx.clipRect, rdrOffX, rdrOffY); 128 adjustClipScale(rdrCtx.clipRect, mxx, myy); 129 } 130 return dt_DeltaScaleFilter.init(out, mxx, myy); 131 } 132 } else { 133 if (rdrCtx.doClip) { 134 // adjust clip rectangle (ymin, ymax, xmin, xmax): 135 adjustClipOffset(rdrCtx.clipRect, rdrOffX, rdrOffY); 136 adjustClipInverseDelta(rdrCtx.clipRect, mxx, mxy, myx, myy); 137 } 138 return dt_DeltaTransformFilter.init(out, mxx, mxy, myx, myy); 139 } 140 } 141 142 private static void adjustClipOffset(final double[] clipRect, 143 final double rdrOffX, 144 final double rdrOffY) 145 { 146 clipRect[0] += rdrOffY; 147 clipRect[1] += rdrOffY; 148 clipRect[2] += rdrOffX; 149 clipRect[3] += rdrOffX; 150 } 151 152 private static void adjustClipScale(final double[] clipRect, 153 final double mxx, final double myy) 154 { 155 // Adjust the clipping rectangle (iv_DeltaScaleFilter): 156 clipRect[0] /= myy; 157 clipRect[1] /= myy; 158 clipRect[2] /= mxx; 159 clipRect[3] /= mxx; 160 } 161 162 private static void adjustClipInverseDelta(final double[] clipRect, 163 final double mxx, final double mxy, 164 final double myx, final double myy) 165 { 166 // Adjust the clipping rectangle (iv_DeltaTransformFilter): 167 final double det = mxx * myy - mxy * myx; 168 final double imxx = myy / det; 169 final double imxy = -mxy / det; 170 final double imyx = -myx / det; 171 final double imyy = mxx / det; 172 173 double xmin, xmax, ymin, ymax; 174 double x, y; 175 // xmin, ymin: 176 x = clipRect[2] * imxx + clipRect[0] * imxy; 177 y = clipRect[2] * imyx + clipRect[0] * imyy; 178 179 xmin = xmax = x; 180 ymin = ymax = y; 181 182 // xmax, ymin: 183 x = clipRect[3] * imxx + clipRect[0] * imxy; 184 y = clipRect[3] * imyx + clipRect[0] * imyy; 185 186 if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; } 187 if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; } 188 189 // xmin, ymax: 190 x = clipRect[2] * imxx + clipRect[1] * imxy; 191 y = clipRect[2] * imyx + clipRect[1] * imyy; 192 193 if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; } 194 if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; } 195 196 // xmax, ymax: 197 x = clipRect[3] * imxx + clipRect[1] * imxy; 198 y = clipRect[3] * imyx + clipRect[1] * imyy; 199 200 if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; } 201 if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; } 202 203 clipRect[0] = ymin; 204 clipRect[1] = ymax; 205 clipRect[2] = xmin; 206 clipRect[3] = xmax; 207 } 208 209 public DPathConsumer2D inverseDeltaTransformConsumer(DPathConsumer2D out, 210 BaseTransform at) 211 { 212 if (at == null) { 213 return out; 214 } 215 double mxx = at.getMxx(); 216 double mxy = at.getMxy(); 217 double myx = at.getMyx(); 218 double myy = at.getMyy(); 219 220 if (mxy == 0.0d && myx == 0.0d) { 221 if (mxx == 1.0d && myy == 1.0d) { 222 return out; 223 } else { 224 return iv_DeltaScaleFilter.init(out, 1.0d/mxx, 1.0d/myy); 225 } 226 } else { 227 final double det = mxx * myy - mxy * myx; 228 return iv_DeltaTransformFilter.init(out, 229 myy / det, 230 -mxy / det, 231 -myx / det, 232 mxx / det); 233 } 234 } 235 236 static final class DeltaScaleFilter implements DPathConsumer2D { 237 private DPathConsumer2D out; 238 private double sx, sy; 239 240 DeltaScaleFilter() {} 241 242 DeltaScaleFilter init(DPathConsumer2D out, 243 double mxx, double myy) 244 { 245 this.out = out; 246 sx = mxx; 247 sy = myy; 248 return this; // fluent API 249 } 250 251 @Override 252 public void moveTo(double x0, double y0) { 253 out.moveTo(x0 * sx, y0 * sy); 254 } 255 256 @Override 257 public void lineTo(double x1, double y1) { 258 out.lineTo(x1 * sx, y1 * sy); 259 } 260 261 @Override 262 public void quadTo(double x1, double y1, 263 double x2, double y2) 264 { 265 out.quadTo(x1 * sx, y1 * sy, 266 x2 * sx, y2 * sy); 267 } 268 269 @Override 270 public void curveTo(double x1, double y1, 271 double x2, double y2, 272 double x3, double y3) 273 { 274 out.curveTo(x1 * sx, y1 * sy, 275 x2 * sx, y2 * sy, 276 x3 * sx, y3 * sy); 277 } 278 279 @Override 280 public void closePath() { 281 out.closePath(); 282 } 283 284 @Override 285 public void pathDone() { 286 out.pathDone(); 287 } 288 } 289 290 static final class DeltaTransformFilter implements DPathConsumer2D { 291 private DPathConsumer2D out; 292 private double mxx, mxy, myx, myy; 293 294 DeltaTransformFilter() {} 295 296 DeltaTransformFilter init(DPathConsumer2D out, 297 double mxx, double mxy, 298 double myx, double 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(double x0, double y0) { 310 out.moveTo(x0 * mxx + y0 * mxy, 311 x0 * myx + y0 * myy); 312 } 313 314 @Override 315 public void lineTo(double x1, double y1) { 316 out.lineTo(x1 * mxx + y1 * mxy, 317 x1 * myx + y1 * myy); 318 } 319 320 @Override 321 public void quadTo(double x1, double y1, 322 double x2, double 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(double x1, double y1, 332 double x2, double y2, 333 double x3, double 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 354 static final class Path2DWrapper implements DPathConsumer2D { 355 private Path2D p2d; 356 357 Path2DWrapper() {} 358 359 Path2DWrapper init(Path2D p2d) { 360 this.p2d = p2d; 361 return this; 362 } 363 364 @Override 365 public void moveTo(double x0, double y0) { 366 p2d.moveTo((float)x0, (float)y0); 367 } 368 369 @Override 370 public void lineTo(double x1, double y1) { 371 p2d.lineTo((float)x1, (float)y1); 372 } 373 374 @Override 375 public void closePath() { 376 p2d.closePath(); 377 } 378 379 @Override 380 public void pathDone() {} 381 382 @Override 383 public void curveTo(double x1, double y1, 384 double x2, double y2, 385 double x3, double y3) 386 { 387 p2d.curveTo((float)x1, (float)y1, (float)x2, (float)y2, 388 (float)x3, (float)y3); 389 } 390 391 @Override 392 public void quadTo(double x1, double y1, double x2, double y2) { 393 p2d.quadTo((float)x1, (float)y1, (float)x2, (float)y2); 394 } 395 } 396 397 static final class ClosedPathDetector implements DPathConsumer2D { 398 399 private final DRendererContext rdrCtx; 400 private final PolyStack stack; 401 402 private DPathConsumer2D out; 403 404 ClosedPathDetector(final DRendererContext 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(DPathConsumer2D 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(double x0, double 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(double x1, double y1) { 461 stack.pushLine(x1, y1); 462 } 463 464 @Override 465 public void curveTo(double x3, double y3, 466 double x2, double y2, 467 double x1, double y1) 468 { 469 stack.pushCubic(x1, y1, x2, y2, x3, y3); 470 } 471 472 @Override 473 public void quadTo(double x2, double y2, double x1, double y1) { 474 stack.pushQuad(x1, y1, x2, y2); 475 } 476 } 477 478 static final class PathClipFilter implements DPathConsumer2D { 479 480 private DPathConsumer2D out; 481 482 // Bounds of the drawing region, at pixel precision. 483 private final double[] clipRect; 484 485 private final double[] corners = new double[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 double cx0, cy0; 500 501 // The current point OUTSIDE 502 private double cox0, coy0; 503 504 private boolean subdivide = MarlinConst.DO_CLIP_SUBDIVIDER; 505 private final CurveClipSplitter curveSplitter; 506 507 PathClipFilter(final DRendererContext 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 DPathConsumer2D out, 520 final double rdrOffX, 521 final double rdrOffY) 522 { 523 this.out = out; 524 525 // add a small rounding error: 526 final double margin = 1e-3d; 527 528 final double[] _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 double[] _corners = corners; 574 final double[] _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 double x0, final double y0) { 615 finishPath(); 616 617 this.cOutCode = DHelpers.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 double xe, final double ye) { 626 final int outcode0 = this.cOutCode; 627 final int outcode1 = DHelpers.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 double x1, final double y1, 720 final double x2, final double y2, 721 final double xe, final double ye) 722 { 723 final int outcode0 = this.cOutCode; 724 final int outcode1 = DHelpers.outcode(x1, y1, clipRect); 725 final int outcode2 = DHelpers.outcode(x2, y2, clipRect); 726 final int outcode3 = DHelpers.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 double x1, final double y1, 784 final double xe, final double ye) 785 { 786 final int outcode0 = this.cOutCode; 787 final int outcode1 = DHelpers.outcode(x1, y1, clipRect); 788 final int outcode2 = DHelpers.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 double LEN_TH = MarlinProperties.getSubdividerMinLength(); 846 static final boolean DO_CHECK_LENGTH = (LEN_TH > 0.0d); 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 double[] clipRect; 854 855 // clip rectangle (ymin, ymax, xmin, xmax) including padding: 856 final double[] clipRectPad = new double[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 double[] middle = new double[MAX_N_CURVES * 8 + 2]; 862 // t values at subdivision points 863 private final double[] subdivTs = new double[MAX_N_CURVES]; 864 865 // dirty curve 866 private final DCurve curve; 867 868 CurveClipSplitter(final DRendererContext 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 double[] _clipRect = clipRect; 882 final double[] _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 double x0, final double y0, 896 final double x1, final double y1, 897 final int outCodeOR, 898 final DPathConsumer2D out) 899 { 900 if (TRACE) { 901 MarlinUtils.logInfo("divLine P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ")"); 902 } 903 904 if (DO_CHECK_LENGTH && DHelpers.fastLineLen(x0, y0, x1, y1) <= LEN_TH) { 905 return false; 906 } 907 908 final double[] 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 double x0, final double y0, 916 final double x1, final double y1, 917 final double x2, final double y2, 918 final int outCodeOR, 919 final DPathConsumer2D 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 && DHelpers.fastQuadLen(x0, y0, x1, y1, x2, y2) <= LEN_TH) { 926 return false; 927 } 928 929 final double[] 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 double x0, final double y0, 938 final double x1, final double y1, 939 final double x2, final double y2, 940 final double x3, final double y3, 941 final int outCodeOR, 942 final DPathConsumer2D 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 && DHelpers.fastCurvelen(x0, y0, x1, y1, x2, y2, x3, y3) <= LEN_TH) { 949 return false; 950 } 951 952 final double[] 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 DPathConsumer2D out) 963 { 964 final double[] mid = middle; 965 final double[] subTs = subdivTs; 966 967 if (init_clipRectPad) { 968 init_clipRectPad = false; 969 initPaddedClip(); 970 } 971 972 final int nSplits = DHelpers.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 double prevT = 0.0d; 984 985 for (int i = 0, off = 0; i < nSplits; i++, off += type) { 986 final double t = subTs[i]; 987 988 DHelpers.subdivideAt((t - prevT) / (1.0d - 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 double[] pts, 1003 final int off, final DPathConsumer2D 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 double 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 double[] middle = new double[MAX_N_CURVES * 6 + 2]; 1032 // t values at subdivision points 1033 private final double[] subdivTs = new double[MAX_N_CURVES - 1]; 1034 1035 // dirty curve 1036 private final DCurve curve; 1037 1038 CurveBasicMonotonizer(final DRendererContext rdrCtx) { 1039 this.curve = rdrCtx.curve; 1040 } 1041 1042 public void init(final double lineWidth) { 1043 this.lw2 = (lineWidth * lineWidth) / 4.0d; 1044 } 1045 1046 CurveBasicMonotonizer curve(final double x0, final double y0, 1047 final double x1, final double y1, 1048 final double x2, final double y2, 1049 final double x3, final double y3) 1050 { 1051 final double[] 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 double[] subTs = subdivTs; 1058 final int nSplits = DHelpers.findSubdivPoints(curve, mid, subTs, 8, lw2); 1059 1060 double prevT = 0.0d; 1061 for (int i = 0, off = 0; i < nSplits; i++, off += 6) { 1062 final double t = subTs[i]; 1063 1064 DHelpers.subdivideCubicAt((t - prevT) / (1.0d - 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 double x0, final double y0, 1074 final double x1, final double y1, 1075 final double x2, final double y2) 1076 { 1077 final double[] 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 double[] subTs = subdivTs; 1083 final int nSplits = DHelpers.findSubdivPoints(curve, mid, subTs, 6, lw2); 1084 1085 double prevt = 0.0d; 1086 for (int i = 0, off = 0; i < nSplits; i++, off += 4) { 1087 final double t = subTs[i]; 1088 DHelpers.subdivideQuadAt((t - prevt) / (1.0d - 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 DPathConsumer2D { 1099 private final String prefix; 1100 private DPathConsumer2D out; 1101 1102 PathTracer(String name) { 1103 this.prefix = name + ": "; 1104 } 1105 1106 PathTracer init(DPathConsumer2D out) { 1107 this.out = out; 1108 return this; // fluent API 1109 } 1110 1111 @Override 1112 public void moveTo(double x0, double y0) { 1113 log("moveTo (" + x0 + ", " + y0 + ')'); 1114 out.moveTo(x0, y0); 1115 } 1116 1117 @Override 1118 public void lineTo(double x1, double y1) { 1119 log("lineTo (" + x1 + ", " + y1 + ')'); 1120 out.lineTo(x1, y1); 1121 } 1122 1123 @Override 1124 public void curveTo(double x1, double y1, 1125 double x2, double y2, 1126 double x3, double 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(double x1, double y1, double x2, double 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 }