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