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