1 /*
2 * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package com.sun.marlin;
27
28 import com.sun.javafx.geom.Path2D;
29 import com.sun.javafx.geom.transform.BaseTransform;
30 import com.sun.marlin.DHelpers.IndexStack;
31 import com.sun.marlin.DHelpers.PolyStack;
32
33 public final class DTransformingPathConsumer2D {
34
35 private final DRendererContext rdrCtx;
36
37 // recycled ClosedPathDetector instance from detectClosedPath()
38 private final ClosedPathDetector cpDetector;
39
40 // recycled PathClipFilter instance from pathClipper()
41 private final PathClipFilter pathClipper;
42
43 // recycled DPathConsumer2D instance from wrapPath2D()
44 private final Path2DWrapper wp_Path2DWrapper = new Path2DWrapper();
45
46 // recycled DPathConsumer2D instances from deltaTransformConsumer()
47 private final DeltaScaleFilter dt_DeltaScaleFilter = new DeltaScaleFilter();
48 private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter();
49
50 // recycled DPathConsumer2D instances from inverseDeltaTransformConsumer()
51 private final DeltaScaleFilter iv_DeltaScaleFilter = new DeltaScaleFilter();
52 private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter();
53
54 // recycled PathTracer instances from tracer...() methods
55 private final PathTracer tracerInput = new PathTracer("[Input]");
56 private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector");
57 private final PathTracer tracerFiller = new PathTracer("Filler");
58 private final PathTracer tracerStroker = new PathTracer("Stroker");
59
60 DTransformingPathConsumer2D(final DRendererContext rdrCtx) {
61 // used by RendererContext
62 this.rdrCtx = rdrCtx;
63 this.cpDetector = new ClosedPathDetector(rdrCtx);
64 this.pathClipper = new PathClipFilter(rdrCtx);
65 }
66
67 public DPathConsumer2D wrapPath2D(Path2D p2d) {
68 return wp_Path2DWrapper.init(p2d);
69 }
70
71 public DPathConsumer2D traceInput(DPathConsumer2D out) {
72 return tracerInput.init(out);
73 }
74
75 public DPathConsumer2D traceClosedPathDetector(DPathConsumer2D out) {
76 return tracerCPDetector.init(out);
77 }
78
79 public DPathConsumer2D traceFiller(DPathConsumer2D out) {
80 return tracerFiller.init(out);
81 }
82
83 public DPathConsumer2D traceStroker(DPathConsumer2D out) {
84 return tracerStroker.init(out);
85 }
86
87 public DPathConsumer2D detectClosedPath(DPathConsumer2D out) {
88 return cpDetector.init(out);
89 }
90
91 public DPathConsumer2D pathClipper(DPathConsumer2D out,
92 final double rdrOffX,
93 final double rdrOffY)
94 {
95 return pathClipper.init(out, rdrOffX, rdrOffY);
96 }
97
98 public DPathConsumer2D deltaTransformConsumer(DPathConsumer2D out,
99 BaseTransform at,
100 final double rdrOffX,
101 final double rdrOffY)
102 {
103 if (at == null) {
104 return out;
105 }
106 final double mxx = at.getMxx();
469 static final class PathClipFilter implements DPathConsumer2D {
470
471 private DPathConsumer2D out;
472
473 // Bounds of the drawing region, at pixel precision.
474 private final double[] clipRect;
475
476 private final double[] corners = new double[8];
477 private boolean init_corners = false;
478
479 private final IndexStack stack;
480
481 // the current outcode of the current sub path
482 private int cOutCode = 0;
483
484 // the cumulated (and) outcode of the complete path
485 private int gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
486
487 private boolean outside = false;
488
489 // The current point OUTSIDE
490 private double cx0, cy0;
491
492 PathClipFilter(final DRendererContext rdrCtx) {
493 this.clipRect = rdrCtx.clipRect;
494 this.stack = (rdrCtx.stats != null) ?
495 new IndexStack(rdrCtx,
496 rdrCtx.stats.stat_pcf_idxstack_indices,
497 rdrCtx.stats.hist_pcf_idxstack_indices,
498 rdrCtx.stats.stat_array_pcf_idxstack_indices)
499 : new IndexStack(rdrCtx);
500 }
501
502 PathClipFilter init(final DPathConsumer2D out,
503 final double rdrOffX,
504 final double rdrOffY)
505 {
506 this.out = out;
507
508 // add a small rounding error:
509 final double margin = 1e-3d;
510
511 final double[] _clipRect = this.clipRect;
512 // Adjust the clipping rectangle with the renderer offsets
513 _clipRect[0] -= margin - rdrOffY;
514 _clipRect[1] += margin + rdrOffY;
515 _clipRect[2] -= margin - rdrOffX;
516 _clipRect[3] += margin + rdrOffX;
517
518 this.init_corners = true;
519 this.gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
520
521 return this; // fluent API
522 }
523
524 /**
525 * Disposes this instance:
526 * clean up before reusing this instance
527 */
528 void dispose() {
529 stack.dispose();
530 }
531
532 private void finishPath() {
533 if (outside) {
534 // criteria: inside or totally outside ?
535 if (gOutCode == 0) {
536 finish();
537 } else {
548 if (init_corners) {
549 init_corners = false;
550
551 final double[] _corners = corners;
552 final double[] _clipRect = clipRect;
553 // Top Left (0):
554 _corners[0] = _clipRect[2];
555 _corners[1] = _clipRect[0];
556 // Bottom Left (1):
557 _corners[2] = _clipRect[2];
558 _corners[3] = _clipRect[1];
559 // Top right (2):
560 _corners[4] = _clipRect[3];
561 _corners[5] = _clipRect[0];
562 // Bottom Right (3):
563 _corners[6] = _clipRect[3];
564 _corners[7] = _clipRect[1];
565 }
566 stack.pullAll(corners, out);
567 }
568 out.lineTo(cx0, cy0);
569 }
570
571 @Override
572 public void pathDone() {
573 finishPath();
574
575 out.pathDone();
576
577 // TODO: fix possible leak if exception happened
578 // Dispose this instance:
579 dispose();
580 }
581
582 @Override
583 public void closePath() {
584 finishPath();
585
586 out.closePath();
587 }
588
589 @Override
590 public void moveTo(final double x0, final double y0) {
591 finishPath();
592
593 final int outcode = DHelpers.outcode(x0, y0, clipRect);
594 this.cOutCode = outcode;
595 this.outside = false;
596 out.moveTo(x0, y0);
597 }
598
599 @Override
600 public void lineTo(final double xe, final double ye) {
601 final int outcode0 = this.cOutCode;
602 final int outcode1 = DHelpers.outcode(xe, ye, clipRect);
603 this.cOutCode = outcode1;
604
605 final int sideCode = (outcode0 & outcode1);
606
607 // basic rejection criteria:
608 if (sideCode == 0) {
609 this.gOutCode = 0;
610 } else {
611 this.gOutCode &= sideCode;
612 // keep last point coordinate before entering the clip again:
613 this.outside = true;
614 this.cx0 = xe;
615 this.cy0 = ye;
616
617 clip(sideCode, outcode0, outcode1);
618 return;
619 }
620 if (outside) {
621 finish();
622 }
623 // clipping disabled:
624 out.lineTo(xe, ye);
625 }
626
627 private void clip(final int sideCode,
628 final int outcode0,
629 final int outcode1)
630 {
631 // corner or cross-boundary on left or right side:
632 if ((outcode0 != outcode1)
633 && ((sideCode & MarlinConst.OUTCODE_MASK_L_R) != 0))
634 {
635 // combine outcodes:
636 final int mergeCode = (outcode0 | outcode1);
637 final int tbCode = mergeCode & MarlinConst.OUTCODE_MASK_T_B;
638 final int lrCode = mergeCode & MarlinConst.OUTCODE_MASK_L_R;
639 final int off = (lrCode == MarlinConst.OUTCODE_LEFT) ? 0 : 2;
640
641 // add corners to outside stack:
642 switch (tbCode) {
643 case MarlinConst.OUTCODE_TOP:
644 // System.out.println("TOP "+ ((off == 0) ? "LEFT" : "RIGHT"));
645 stack.push(off); // top
646 return;
647 case MarlinConst.OUTCODE_BOTTOM:
648 // System.out.println("BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT"));
649 stack.push(off + 1); // bottom
650 return;
651 default:
652 // both TOP / BOTTOM:
653 if ((outcode0 & MarlinConst.OUTCODE_TOP) != 0) {
654 // System.out.println("TOP + BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT"));
655 // top to bottom
656 stack.push(off); // top
657 stack.push(off + 1); // bottom
658 } else {
659 // System.out.println("BOTTOM + TOP "+ ((off == 0) ? "LEFT" : "RIGHT"));
660 // bottom to top
661 stack.push(off + 1); // bottom
662 stack.push(off); // top
663 }
664 }
665 }
666 }
667
668 @Override
669 public void curveTo(final double x1, final double y1,
670 final double x2, final double y2,
671 final double xe, final double ye)
672 {
673 final int outcode0 = this.cOutCode;
674 final int outcode3 = DHelpers.outcode(xe, ye, clipRect);
675 this.cOutCode = outcode3;
676
677 int sideCode = outcode0 & outcode3;
678
679 if (sideCode == 0) {
680 this.gOutCode = 0;
681 } else {
682 sideCode &= DHelpers.outcode(x1, y1, clipRect);
683 sideCode &= DHelpers.outcode(x2, y2, clipRect);
684 this.gOutCode &= sideCode;
685
686 // basic rejection criteria:
687 if (sideCode != 0) {
688 // keep last point coordinate before entering the clip again:
689 this.outside = true;
690 this.cx0 = xe;
691 this.cy0 = ye;
692
693 clip(sideCode, outcode0, outcode3);
694 return;
695 }
696 }
697 if (outside) {
698 finish();
699 }
700 // clipping disabled:
701 out.curveTo(x1, y1, x2, y2, xe, ye);
702 }
703
704 @Override
705 public void quadTo(final double x1, final double y1,
706 final double xe, final double ye)
707 {
708 final int outcode0 = this.cOutCode;
709 final int outcode2 = DHelpers.outcode(xe, ye, clipRect);
710 this.cOutCode = outcode2;
711
712 int sideCode = outcode0 & outcode2;
713
714 if (sideCode == 0) {
715 this.gOutCode = 0;
716 } else {
717 sideCode &= DHelpers.outcode(x1, y1, clipRect);
718 this.gOutCode &= sideCode;
719
720 // basic rejection criteria:
721 if (sideCode != 0) {
722 // keep last point coordinate before entering the clip again:
723 this.outside = true;
724 this.cx0 = xe;
725 this.cy0 = ye;
726
727 clip(sideCode, outcode0, outcode2);
728 return;
729 }
730 }
731 if (outside) {
732 finish();
733 }
734 // clipping disabled:
735 out.quadTo(x1, y1, xe, ye);
736 }
737 }
738
739 static final class PathTracer implements DPathConsumer2D {
740 private final String prefix;
741 private DPathConsumer2D out;
742
743 PathTracer(String name) {
744 this.prefix = name + ": ";
745 }
746
747 PathTracer init(DPathConsumer2D out) {
748 this.out = out;
749 return this; // fluent API
750 }
751
752 @Override
753 public void moveTo(double x0, double y0) {
754 log("moveTo (" + x0 + ", " + y0 + ')');
755 out.moveTo(x0, y0);
772
773 @Override
774 public void quadTo(double x1, double y1, double x2, double y2) {
775 log("quadTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ')');
776 out.quadTo(x1, y1, x2, y2);
777 }
778
779 @Override
780 public void closePath() {
781 log("closePath");
782 out.closePath();
783 }
784
785 @Override
786 public void pathDone() {
787 log("pathDone");
788 out.pathDone();
789 }
790
791 private void log(final String message) {
792 System.out.println(prefix + message);
793 }
794 }
795 }
|
1 /*
2 * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package com.sun.marlin;
27
28 import com.sun.javafx.geom.Path2D;
29 import com.sun.javafx.geom.transform.BaseTransform;
30 import com.sun.marlin.DHelpers.IndexStack;
31 import com.sun.marlin.DHelpers.PolyStack;
32 import java.util.Arrays;
33
34 public final class DTransformingPathConsumer2D {
35
36 // smaller uncertainty in double variant
37 static final double CLIP_RECT_PADDING = 0.25d;
38
39 private final DRendererContext rdrCtx;
40
41 // recycled ClosedPathDetector instance from detectClosedPath()
42 private final ClosedPathDetector cpDetector;
43
44 // recycled PathClipFilter instance from pathClipper()
45 private final PathClipFilter pathClipper;
46
47 // recycled DPathConsumer2D instance from wrapPath2D()
48 private final Path2DWrapper wp_Path2DWrapper = new Path2DWrapper();
49
50 // recycled DPathConsumer2D instances from deltaTransformConsumer()
51 private final DeltaScaleFilter dt_DeltaScaleFilter = new DeltaScaleFilter();
52 private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter();
53
54 // recycled DPathConsumer2D instances from inverseDeltaTransformConsumer()
55 private final DeltaScaleFilter iv_DeltaScaleFilter = new DeltaScaleFilter();
56 private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter();
57
58 // recycled PathTracer instances from tracer...() methods
59 private final PathTracer tracerInput = new PathTracer("[Input]");
60 private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector");
61 private final PathTracer tracerFiller = new PathTracer("Filler");
62 private final PathTracer tracerStroker = new PathTracer("Stroker");
63 private final PathTracer tracerDasher = new PathTracer("Dasher");
64
65 DTransformingPathConsumer2D(final DRendererContext rdrCtx) {
66 // used by RendererContext
67 this.rdrCtx = rdrCtx;
68 this.cpDetector = new ClosedPathDetector(rdrCtx);
69 this.pathClipper = new PathClipFilter(rdrCtx);
70 }
71
72 public DPathConsumer2D wrapPath2D(Path2D p2d) {
73 return wp_Path2DWrapper.init(p2d);
74 }
75
76 public DPathConsumer2D traceInput(DPathConsumer2D out) {
77 return tracerInput.init(out);
78 }
79
80 public DPathConsumer2D traceClosedPathDetector(DPathConsumer2D out) {
81 return tracerCPDetector.init(out);
82 }
83
84 public DPathConsumer2D traceFiller(DPathConsumer2D out) {
85 return tracerFiller.init(out);
86 }
87
88 public DPathConsumer2D traceStroker(DPathConsumer2D out) {
89 return tracerStroker.init(out);
90 }
91
92 public DPathConsumer2D traceDasher(DPathConsumer2D out) {
93 return tracerDasher.init(out);
94 }
95
96 public DPathConsumer2D detectClosedPath(DPathConsumer2D out) {
97 return cpDetector.init(out);
98 }
99
100 public DPathConsumer2D pathClipper(DPathConsumer2D out,
101 final double rdrOffX,
102 final double rdrOffY)
103 {
104 return pathClipper.init(out, rdrOffX, rdrOffY);
105 }
106
107 public DPathConsumer2D deltaTransformConsumer(DPathConsumer2D out,
108 BaseTransform at,
109 final double rdrOffX,
110 final double rdrOffY)
111 {
112 if (at == null) {
113 return out;
114 }
115 final double mxx = at.getMxx();
478 static final class PathClipFilter implements DPathConsumer2D {
479
480 private DPathConsumer2D out;
481
482 // Bounds of the drawing region, at pixel precision.
483 private final double[] clipRect;
484
485 private final double[] corners = new double[8];
486 private boolean init_corners = false;
487
488 private final IndexStack stack;
489
490 // the current outcode of the current sub path
491 private int cOutCode = 0;
492
493 // the cumulated (and) outcode of the complete path
494 private int gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
495
496 private boolean outside = false;
497
498 // The current point (TODO stupid repeated info)
499 private double cx0, cy0;
500
501 // The current point OUTSIDE
502 private double cox0, coy0;
503
504 private boolean subdivide = MarlinConst.DO_CLIP_SUBDIVIDER;
505 private final CurveClipSplitter curveSplitter;
506
507 PathClipFilter(final DRendererContext rdrCtx) {
508 this.clipRect = rdrCtx.clipRect;
509 this.curveSplitter = rdrCtx.curveClipSplitter;
510
511 this.stack = (rdrCtx.stats != null) ?
512 new IndexStack(rdrCtx,
513 rdrCtx.stats.stat_pcf_idxstack_indices,
514 rdrCtx.stats.hist_pcf_idxstack_indices,
515 rdrCtx.stats.stat_array_pcf_idxstack_indices)
516 : new IndexStack(rdrCtx);
517 }
518
519 PathClipFilter init(final DPathConsumer2D out,
520 final double rdrOffX,
521 final double rdrOffY)
522 {
523 this.out = out;
524
525 // add a small rounding error:
526 final double margin = 1e-3d;
527
528 final double[] _clipRect = this.clipRect;
529 // Adjust the clipping rectangle with the renderer offsets
530 _clipRect[0] -= margin - rdrOffY;
531 _clipRect[1] += margin + rdrOffY;
532 _clipRect[2] -= margin - rdrOffX;
533 _clipRect[3] += margin + rdrOffX;
534
535 if (MarlinConst.DO_CLIP_SUBDIVIDER) {
536 // adjust padded clip rectangle:
537 curveSplitter.init();
538 }
539
540 this.init_corners = true;
541 this.gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
542
543 return this; // fluent API
544 }
545
546 /**
547 * Disposes this instance:
548 * clean up before reusing this instance
549 */
550 void dispose() {
551 stack.dispose();
552 }
553
554 private void finishPath() {
555 if (outside) {
556 // criteria: inside or totally outside ?
557 if (gOutCode == 0) {
558 finish();
559 } else {
570 if (init_corners) {
571 init_corners = false;
572
573 final double[] _corners = corners;
574 final double[] _clipRect = clipRect;
575 // Top Left (0):
576 _corners[0] = _clipRect[2];
577 _corners[1] = _clipRect[0];
578 // Bottom Left (1):
579 _corners[2] = _clipRect[2];
580 _corners[3] = _clipRect[1];
581 // Top right (2):
582 _corners[4] = _clipRect[3];
583 _corners[5] = _clipRect[0];
584 // Bottom Right (3):
585 _corners[6] = _clipRect[3];
586 _corners[7] = _clipRect[1];
587 }
588 stack.pullAll(corners, out);
589 }
590 out.lineTo(cox0, coy0);
591 this.cx0 = cox0;
592 this.cy0 = coy0;
593 }
594
595 @Override
596 public void pathDone() {
597 finishPath();
598
599 out.pathDone();
600
601 // TODO: fix possible leak if exception happened
602 // Dispose this instance:
603 dispose();
604 }
605
606 @Override
607 public void closePath() {
608 finishPath();
609
610 out.closePath();
611 }
612
613 @Override
614 public void moveTo(final double x0, final double y0) {
615 finishPath();
616
617 this.cOutCode = DHelpers.outcode(x0, y0, clipRect);
618 this.outside = false;
619 out.moveTo(x0, y0);
620 this.cx0 = x0;
621 this.cy0 = y0;
622 }
623
624 @Override
625 public void lineTo(final double xe, final double ye) {
626 final int outcode0 = this.cOutCode;
627 final int outcode1 = DHelpers.outcode(xe, ye, clipRect);
628
629 // Should clip
630 final int orCode = (outcode0 | outcode1);
631 if (orCode != 0) {
632 final int sideCode = (outcode0 & outcode1);
633
634 // basic rejection criteria:
635 if (sideCode == 0) {
636 // ovelap clip:
637 if (subdivide) {
638 // avoid reentrance
639 subdivide = false;
640 boolean ret;
641 // subdivide curve => callback with subdivided parts:
642 if (outside) {
643 ret = curveSplitter.splitLine(cox0, coy0, xe, ye,
644 orCode, this);
645 } else {
646 ret = curveSplitter.splitLine(cx0, cy0, xe, ye,
647 orCode, this);
648 }
649 // reentrance is done:
650 subdivide = true;
651 if (ret) {
652 return;
653 }
654 }
655 // already subdivided so render it
656 } else {
657 this.cOutCode = outcode1;
658 this.gOutCode &= sideCode;
659 // keep last point coordinate before entering the clip again:
660 this.outside = true;
661 this.cox0 = xe;
662 this.coy0 = ye;
663
664 clip(sideCode, outcode0, outcode1);
665 return;
666 }
667 }
668
669 this.cOutCode = outcode1;
670 this.gOutCode = 0;
671
672 if (outside) {
673 finish();
674 }
675 // clipping disabled:
676 out.lineTo(xe, ye);
677 this.cx0 = xe;
678 this.cy0 = ye;
679 }
680
681 private void clip(final int sideCode,
682 final int outcode0,
683 final int outcode1)
684 {
685 // corner or cross-boundary on left or right side:
686 if ((outcode0 != outcode1)
687 && ((sideCode & MarlinConst.OUTCODE_MASK_L_R) != 0))
688 {
689 // combine outcodes:
690 final int mergeCode = (outcode0 | outcode1);
691 final int tbCode = mergeCode & MarlinConst.OUTCODE_MASK_T_B;
692 final int lrCode = mergeCode & MarlinConst.OUTCODE_MASK_L_R;
693 final int off = (lrCode == MarlinConst.OUTCODE_LEFT) ? 0 : 2;
694
695 // add corners to outside stack:
696 switch (tbCode) {
697 case MarlinConst.OUTCODE_TOP:
698 stack.push(off); // top
699 return;
700 case MarlinConst.OUTCODE_BOTTOM:
701 stack.push(off + 1); // bottom
702 return;
703 default:
704 // both TOP / BOTTOM:
705 if ((outcode0 & MarlinConst.OUTCODE_TOP) != 0) {
706 // top to bottom
707 stack.push(off); // top
708 stack.push(off + 1); // bottom
709 } else {
710 // bottom to top
711 stack.push(off + 1); // bottom
712 stack.push(off); // top
713 }
714 }
715 }
716 }
717
718 @Override
719 public void curveTo(final double x1, final double y1,
720 final double x2, final double y2,
721 final double xe, final double ye)
722 {
723 final int outcode0 = this.cOutCode;
724 final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
725 final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
726 final int outcode3 = DHelpers.outcode(xe, ye, clipRect);
727
728 // Should clip
729 final int orCode = (outcode0 | outcode1 | outcode2 | outcode3);
730 if (orCode != 0) {
731 final int sideCode = outcode0 & outcode1 & outcode2 & outcode3;
732
733 // basic rejection criteria:
734 if (sideCode == 0) {
735 // ovelap clip:
736 if (subdivide) {
737 // avoid reentrance
738 subdivide = false;
739 // subdivide curve => callback with subdivided parts:
740 boolean ret;
741 if (outside) {
742 ret = curveSplitter.splitCurve(cox0, coy0, x1, y1,
743 x2, y2, xe, ye,
744 orCode, this);
745 } else {
746 ret = curveSplitter.splitCurve(cx0, cy0, x1, y1,
747 x2, y2, xe, ye,
748 orCode, this);
749 }
750 // reentrance is done:
751 subdivide = true;
752 if (ret) {
753 return;
754 }
755 }
756 // already subdivided so render it
757 } else {
758 this.cOutCode = outcode3;
759 this.gOutCode &= sideCode;
760 // keep last point coordinate before entering the clip again:
761 this.outside = true;
762 this.cox0 = xe;
763 this.coy0 = ye;
764
765 clip(sideCode, outcode0, outcode3);
766 return;
767 }
768 }
769
770 this.cOutCode = outcode3;
771 this.gOutCode = 0;
772
773 if (outside) {
774 finish();
775 }
776 // clipping disabled:
777 out.curveTo(x1, y1, x2, y2, xe, ye);
778 this.cx0 = xe;
779 this.cy0 = ye;
780 }
781
782 @Override
783 public void quadTo(final double x1, final double y1,
784 final double xe, final double ye)
785 {
786 final int outcode0 = this.cOutCode;
787 final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
788 final int outcode2 = DHelpers.outcode(xe, ye, clipRect);
789
790 // Should clip
791 final int orCode = (outcode0 | outcode1 | outcode2);
792 if (orCode != 0) {
793 final int sideCode = outcode0 & outcode1 & outcode2;
794
795 // basic rejection criteria:
796 if (sideCode == 0) {
797 // ovelap clip:
798 if (subdivide) {
799 // avoid reentrance
800 subdivide = false;
801 // subdivide curve => callback with subdivided parts:
802 boolean ret;
803 if (outside) {
804 ret = curveSplitter.splitQuad(cox0, coy0, x1, y1,
805 xe, ye, orCode, this);
806 } else {
807 ret = curveSplitter.splitQuad(cx0, cy0, x1, y1,
808 xe, ye, orCode, this);
809 }
810 // reentrance is done:
811 subdivide = true;
812 if (ret) {
813 return;
814 }
815 }
816 // already subdivided so render it
817 } else {
818 this.cOutCode = outcode2;
819 this.gOutCode &= sideCode;
820 // keep last point coordinate before entering the clip again:
821 this.outside = true;
822 this.cox0 = xe;
823 this.coy0 = ye;
824
825 clip(sideCode, outcode0, outcode2);
826 return;
827 }
828 }
829
830 this.cOutCode = outcode2;
831 this.gOutCode = 0;
832
833 if (outside) {
834 finish();
835 }
836 // clipping disabled:
837 out.quadTo(x1, y1, xe, ye);
838 this.cx0 = xe;
839 this.cy0 = ye;
840 }
841 }
842
843 static final class CurveClipSplitter {
844
845 static final double LEN_TH = MarlinProperties.getSubdividerMinLength();
846 static final boolean DO_CHECK_LENGTH = (LEN_TH > 0.0d);
847
848 private static final boolean TRACE = false;
849
850 private static final int MAX_N_CURVES = 3 * 4;
851
852 // clip rectangle (ymin, ymax, xmin, xmax):
853 final double[] clipRect;
854
855 // clip rectangle (ymin, ymax, xmin, xmax) including padding:
856 final double[] clipRectPad = new double[4];
857 private boolean init_clipRectPad = false;
858
859 // This is where the curve to be processed is put. We give it
860 // enough room to store all curves.
861 final double[] middle = new double[MAX_N_CURVES * 8 + 2];
862 // t values at subdivision points
863 private final double[] subdivTs = new double[MAX_N_CURVES];
864
865 // dirty curve
866 private final DCurve curve;
867
868 CurveClipSplitter(final DRendererContext rdrCtx) {
869 this.clipRect = rdrCtx.clipRect;
870 this.curve = rdrCtx.curve;
871 }
872
873 void init() {
874 this.init_clipRectPad = true;
875 }
876
877 private void initPaddedClip() {
878 // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY
879 // adjust padded clip rectangle (ymin, ymax, xmin, xmax):
880 // add a rounding error (curve subdivision ~ 0.1px):
881 final double[] _clipRect = clipRect;
882 final double[] _clipRectPad = clipRectPad;
883
884 _clipRectPad[0] = _clipRect[0] - CLIP_RECT_PADDING;
885 _clipRectPad[1] = _clipRect[1] + CLIP_RECT_PADDING;
886 _clipRectPad[2] = _clipRect[2] - CLIP_RECT_PADDING;
887 _clipRectPad[3] = _clipRect[3] + CLIP_RECT_PADDING;
888
889 if (TRACE) {
890 MarlinUtils.logInfo("clip: X [" + _clipRectPad[2] + " .. " + _clipRectPad[3] +"] "
891 + "Y ["+ _clipRectPad[0] + " .. " + _clipRectPad[1] +"]");
892 }
893 }
894
895 boolean splitLine(final double x0, final double y0,
896 final double x1, final double y1,
897 final int outCodeOR,
898 final DPathConsumer2D out)
899 {
900 if (TRACE) {
901 MarlinUtils.logInfo("divLine P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ")");
902 }
903
904 if (DO_CHECK_LENGTH && DHelpers.fastLineLen(x0, y0, x1, y1) <= LEN_TH) {
905 return false;
906 }
907
908 final double[] mid = middle;
909 mid[0] = x0; mid[1] = y0;
910 mid[2] = x1; mid[3] = y1;
911
912 return subdivideAtIntersections(4, outCodeOR, out);
913 }
914
915 boolean splitQuad(final double x0, final double y0,
916 final double x1, final double y1,
917 final double x2, final double y2,
918 final int outCodeOR,
919 final DPathConsumer2D out)
920 {
921 if (TRACE) {
922 MarlinUtils.logInfo("divQuad P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ")");
923 }
924
925 if (DO_CHECK_LENGTH && DHelpers.fastQuadLen(x0, y0, x1, y1, x2, y2) <= LEN_TH) {
926 return false;
927 }
928
929 final double[] mid = middle;
930 mid[0] = x0; mid[1] = y0;
931 mid[2] = x1; mid[3] = y1;
932 mid[4] = x2; mid[5] = y2;
933
934 return subdivideAtIntersections(6, outCodeOR, out);
935 }
936
937 boolean splitCurve(final double x0, final double y0,
938 final double x1, final double y1,
939 final double x2, final double y2,
940 final double x3, final double y3,
941 final int outCodeOR,
942 final DPathConsumer2D out)
943 {
944 if (TRACE) {
945 MarlinUtils.logInfo("divCurve P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ")");
946 }
947
948 if (DO_CHECK_LENGTH && DHelpers.fastCurvelen(x0, y0, x1, y1, x2, y2, x3, y3) <= LEN_TH) {
949 return false;
950 }
951
952 final double[] mid = middle;
953 mid[0] = x0; mid[1] = y0;
954 mid[2] = x1; mid[3] = y1;
955 mid[4] = x2; mid[5] = y2;
956 mid[6] = x3; mid[7] = y3;
957
958 return subdivideAtIntersections(8, outCodeOR, out);
959 }
960
961 private boolean subdivideAtIntersections(final int type, final int outCodeOR,
962 final DPathConsumer2D out)
963 {
964 final double[] mid = middle;
965 final double[] subTs = subdivTs;
966
967 if (init_clipRectPad) {
968 init_clipRectPad = false;
969 initPaddedClip();
970 }
971
972 final int nSplits = DHelpers.findClipPoints(curve, mid, subTs, type,
973 outCodeOR, clipRectPad);
974
975 if (TRACE) {
976 MarlinUtils.logInfo("nSplits: "+ nSplits);
977 MarlinUtils.logInfo("subTs: " + Arrays.toString(Arrays.copyOfRange(subTs, 0, nSplits)));
978 }
979 if (nSplits == 0) {
980 // only curve support shortcut
981 return false;
982 }
983 double prevT = 0.0d;
984
985 for (int i = 0, off = 0; i < nSplits; i++, off += type) {
986 final double t = subTs[i];
987
988 DHelpers.subdivideAt((t - prevT) / (1.0d - prevT),
989 mid, off, mid, off, type);
990 prevT = t;
991 }
992
993 for (int i = 0, off = 0; i <= nSplits; i++, off += type) {
994 if (TRACE) {
995 MarlinUtils.logInfo("Part Curve " + Arrays.toString(Arrays.copyOfRange(mid, off, off + type)));
996 }
997 emitCurrent(type, mid, off, out);
998 }
999 return true;
1000 }
1001
1002 static void emitCurrent(final int type, final double[] pts,
1003 final int off, final DPathConsumer2D out)
1004 {
1005 // if instead of switch (perf + most probable cases first)
1006 if (type == 8) {
1007 out.curveTo(pts[off + 2], pts[off + 3],
1008 pts[off + 4], pts[off + 5],
1009 pts[off + 6], pts[off + 7]);
1010 } else if (type == 4) {
1011 out.lineTo(pts[off + 2], pts[off + 3]);
1012 } else {
1013 out.quadTo(pts[off + 2], pts[off + 3],
1014 pts[off + 4], pts[off + 5]);
1015 }
1016 }
1017 }
1018
1019 public static final class CurveBasicMonotonizer {
1020
1021 private static final int MAX_N_CURVES = 11;
1022
1023 // squared half line width (for stroker)
1024 private double lw2;
1025
1026 // number of splitted curves
1027 int nbSplits;
1028
1029 // This is where the curve to be processed is put. We give it
1030 // enough room to store all curves.
1031 final double[] middle = new double[MAX_N_CURVES * 6 + 2];
1032 // t values at subdivision points
1033 private final double[] subdivTs = new double[MAX_N_CURVES - 1];
1034
1035 // dirty curve
1036 private final DCurve curve;
1037
1038 CurveBasicMonotonizer(final DRendererContext rdrCtx) {
1039 this.curve = rdrCtx.curve;
1040 }
1041
1042 public void init(final double lineWidth) {
1043 this.lw2 = (lineWidth * lineWidth) / 4.0d;
1044 }
1045
1046 CurveBasicMonotonizer curve(final double x0, final double y0,
1047 final double x1, final double y1,
1048 final double x2, final double y2,
1049 final double x3, final double y3)
1050 {
1051 final double[] mid = middle;
1052 mid[0] = x0; mid[1] = y0;
1053 mid[2] = x1; mid[3] = y1;
1054 mid[4] = x2; mid[5] = y2;
1055 mid[6] = x3; mid[7] = y3;
1056
1057 final double[] subTs = subdivTs;
1058 final int nSplits = DHelpers.findSubdivPoints(curve, mid, subTs, 8, lw2);
1059
1060 double prevT = 0.0d;
1061 for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
1062 final double t = subTs[i];
1063
1064 DHelpers.subdivideCubicAt((t - prevT) / (1.0d - prevT),
1065 mid, off, mid, off, off + 6);
1066 prevT = t;
1067 }
1068
1069 this.nbSplits = nSplits;
1070 return this;
1071 }
1072
1073 CurveBasicMonotonizer quad(final double x0, final double y0,
1074 final double x1, final double y1,
1075 final double x2, final double y2)
1076 {
1077 final double[] mid = middle;
1078 mid[0] = x0; mid[1] = y0;
1079 mid[2] = x1; mid[3] = y1;
1080 mid[4] = x2; mid[5] = y2;
1081
1082 final double[] subTs = subdivTs;
1083 final int nSplits = DHelpers.findSubdivPoints(curve, mid, subTs, 6, lw2);
1084
1085 double prevt = 0.0d;
1086 for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
1087 final double t = subTs[i];
1088 DHelpers.subdivideQuadAt((t - prevt) / (1.0d - prevt),
1089 mid, off, mid, off, off + 4);
1090 prevt = t;
1091 }
1092
1093 this.nbSplits = nSplits;
1094 return this;
1095 }
1096 }
1097
1098 static final class PathTracer implements DPathConsumer2D {
1099 private final String prefix;
1100 private DPathConsumer2D out;
1101
1102 PathTracer(String name) {
1103 this.prefix = name + ": ";
1104 }
1105
1106 PathTracer init(DPathConsumer2D out) {
1107 this.out = out;
1108 return this; // fluent API
1109 }
1110
1111 @Override
1112 public void moveTo(double x0, double y0) {
1113 log("moveTo (" + x0 + ", " + y0 + ')');
1114 out.moveTo(x0, y0);
1131
1132 @Override
1133 public void quadTo(double x1, double y1, double x2, double y2) {
1134 log("quadTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ')');
1135 out.quadTo(x1, y1, x2, y2);
1136 }
1137
1138 @Override
1139 public void closePath() {
1140 log("closePath");
1141 out.closePath();
1142 }
1143
1144 @Override
1145 public void pathDone() {
1146 log("pathDone");
1147 out.pathDone();
1148 }
1149
1150 private void log(final String message) {
1151 MarlinUtils.logInfo(prefix + message);
1152 }
1153 }
1154 }
|