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