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