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