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