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 sun.awt.geom.PathConsumer2D;
29 import java.awt.geom.AffineTransform;
30 import java.awt.geom.Path2D;
31 import sun.java2d.marlin.Helpers.IndexStack;
32 import sun.java2d.marlin.Helpers.PolyStack;
33
34 final class TransformingPathConsumer2D {
35
36 private final RendererContext rdrCtx;
37
38 // recycled ClosedPathDetector instance from detectClosedPath()
39 private final ClosedPathDetector cpDetector;
40
41 // recycled PathClipFilter instance from pathClipper()
42 private final PathClipFilter pathClipper;
43
44 // recycled PathConsumer2D instance from wrapPath2D()
45 private final Path2DWrapper wp_Path2DWrapper = new Path2DWrapper();
46
47 // recycled PathConsumer2D instances from deltaTransformConsumer()
48 private final DeltaScaleFilter dt_DeltaScaleFilter = new DeltaScaleFilter();
49 private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter();
50
51 // recycled PathConsumer2D instances from inverseDeltaTransformConsumer()
52 private final DeltaScaleFilter iv_DeltaScaleFilter = new DeltaScaleFilter();
53 private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter();
54
55 // recycled PathTracer instances from tracer...() methods
56 private final PathTracer tracerInput = new PathTracer("[Input]");
57 private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector");
58 private final PathTracer tracerFiller = new PathTracer("Filler");
59 private final PathTracer tracerStroker = new PathTracer("Stroker");
60
61 TransformingPathConsumer2D(final RendererContext rdrCtx) {
62 // used by RendererContext
63 this.rdrCtx = rdrCtx;
64 this.cpDetector = new ClosedPathDetector(rdrCtx);
65 this.pathClipper = new PathClipFilter(rdrCtx);
66 }
67
68 PathConsumer2D wrapPath2D(Path2D.Float p2d) {
69 return wp_Path2DWrapper.init(p2d);
70 }
71
72 PathConsumer2D traceInput(PathConsumer2D out) {
73 return tracerInput.init(out);
74 }
75
76 PathConsumer2D traceClosedPathDetector(PathConsumer2D out) {
77 return tracerCPDetector.init(out);
78 }
79
80 PathConsumer2D traceFiller(PathConsumer2D out) {
81 return tracerFiller.init(out);
82 }
83
84 PathConsumer2D traceStroker(PathConsumer2D out) {
85 return tracerStroker.init(out);
86 }
87
88 PathConsumer2D detectClosedPath(PathConsumer2D out) {
89 return cpDetector.init(out);
90 }
91
92 PathConsumer2D pathClipper(PathConsumer2D out) {
93 return pathClipper.init(out);
94 }
95
96 PathConsumer2D deltaTransformConsumer(PathConsumer2D out,
97 AffineTransform at)
98 {
99 if (at == null) {
100 return out;
101 }
102 final float mxx = (float) at.getScaleX();
103 final float mxy = (float) at.getShearX();
104 final float myx = (float) at.getShearY();
105 final float myy = (float) at.getScaleY();
106
107 if (mxy == 0.0f && myx == 0.0f) {
483 static final class PathClipFilter implements PathConsumer2D {
484
485 private PathConsumer2D out;
486
487 // Bounds of the drawing region, at pixel precision.
488 private final float[] clipRect;
489
490 private final float[] corners = new float[8];
491 private boolean init_corners = false;
492
493 private final IndexStack stack;
494
495 // the current outcode of the current sub path
496 private int cOutCode = 0;
497
498 // the cumulated (and) outcode of the complete path
499 private int gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
500
501 private boolean outside = false;
502
503 // The current point OUTSIDE
504 private float cx0, cy0;
505
506 PathClipFilter(final RendererContext rdrCtx) {
507 this.clipRect = rdrCtx.clipRect;
508 this.stack = (rdrCtx.stats != null) ?
509 new IndexStack(rdrCtx,
510 rdrCtx.stats.stat_pcf_idxstack_indices,
511 rdrCtx.stats.hist_pcf_idxstack_indices,
512 rdrCtx.stats.stat_array_pcf_idxstack_indices)
513 : new IndexStack(rdrCtx);
514 }
515
516 PathClipFilter init(final PathConsumer2D out) {
517 this.out = out;
518
519 // Adjust the clipping rectangle with the renderer offsets
520 final float rdrOffX = Renderer.RDR_OFFSET_X;
521 final float rdrOffY = Renderer.RDR_OFFSET_Y;
522
523 // add a small rounding error:
524 final float margin = 1e-3f;
525
526 final float[] _clipRect = this.clipRect;
527 _clipRect[0] -= margin - rdrOffY;
528 _clipRect[1] += margin + rdrOffY;
529 _clipRect[2] -= margin - rdrOffX;
530 _clipRect[3] += margin + rdrOffX;
531
532 this.init_corners = true;
533 this.gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
534
535 return this; // fluent API
536 }
537
538 /**
539 * Disposes this instance:
540 * clean up before reusing this instance
541 */
542 void dispose() {
543 stack.dispose();
544 }
545
546 private void finishPath() {
547 if (outside) {
548 // criteria: inside or totally outside ?
549 if (gOutCode == 0) {
550 finish();
551 } else {
562 if (init_corners) {
563 init_corners = false;
564
565 final float[] _corners = corners;
566 final float[] _clipRect = clipRect;
567 // Top Left (0):
568 _corners[0] = _clipRect[2];
569 _corners[1] = _clipRect[0];
570 // Bottom Left (1):
571 _corners[2] = _clipRect[2];
572 _corners[3] = _clipRect[1];
573 // Top right (2):
574 _corners[4] = _clipRect[3];
575 _corners[5] = _clipRect[0];
576 // Bottom Right (3):
577 _corners[6] = _clipRect[3];
578 _corners[7] = _clipRect[1];
579 }
580 stack.pullAll(corners, out);
581 }
582 out.lineTo(cx0, cy0);
583 }
584
585 @Override
586 public void pathDone() {
587 finishPath();
588
589 out.pathDone();
590
591 // TODO: fix possible leak if exception happened
592 // Dispose this instance:
593 dispose();
594 }
595
596 @Override
597 public void closePath() {
598 finishPath();
599
600 out.closePath();
601 }
602
603 @Override
604 public void moveTo(final float x0, final float y0) {
605 finishPath();
606
607 final int outcode = Helpers.outcode(x0, y0, clipRect);
608 this.cOutCode = outcode;
609 this.outside = false;
610 out.moveTo(x0, y0);
611 }
612
613 @Override
614 public void lineTo(final float xe, final float ye) {
615 final int outcode0 = this.cOutCode;
616 final int outcode1 = Helpers.outcode(xe, ye, clipRect);
617 this.cOutCode = outcode1;
618
619 final int sideCode = (outcode0 & outcode1);
620
621 // basic rejection criteria:
622 if (sideCode == 0) {
623 this.gOutCode = 0;
624 } else {
625 this.gOutCode &= sideCode;
626 // keep last point coordinate before entering the clip again:
627 this.outside = true;
628 this.cx0 = xe;
629 this.cy0 = ye;
630
631 clip(sideCode, outcode0, outcode1);
632 return;
633 }
634 if (outside) {
635 finish();
636 }
637 // clipping disabled:
638 out.lineTo(xe, ye);
639 }
640
641 private void clip(final int sideCode,
642 final int outcode0,
643 final int outcode1)
644 {
645 // corner or cross-boundary on left or right side:
646 if ((outcode0 != outcode1)
647 && ((sideCode & MarlinConst.OUTCODE_MASK_L_R) != 0))
648 {
649 // combine outcodes:
650 final int mergeCode = (outcode0 | outcode1);
651 final int tbCode = mergeCode & MarlinConst.OUTCODE_MASK_T_B;
652 final int lrCode = mergeCode & MarlinConst.OUTCODE_MASK_L_R;
653 final int off = (lrCode == MarlinConst.OUTCODE_LEFT) ? 0 : 2;
654
655 // add corners to outside stack:
656 switch (tbCode) {
657 case MarlinConst.OUTCODE_TOP:
658 // System.out.println("TOP "+ ((off == 0) ? "LEFT" : "RIGHT"));
659 stack.push(off); // top
660 return;
661 case MarlinConst.OUTCODE_BOTTOM:
662 // System.out.println("BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT"));
663 stack.push(off + 1); // bottom
664 return;
665 default:
666 // both TOP / BOTTOM:
667 if ((outcode0 & MarlinConst.OUTCODE_TOP) != 0) {
668 // System.out.println("TOP + BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT"));
669 // top to bottom
670 stack.push(off); // top
671 stack.push(off + 1); // bottom
672 } else {
673 // System.out.println("BOTTOM + TOP "+ ((off == 0) ? "LEFT" : "RIGHT"));
674 // bottom to top
675 stack.push(off + 1); // bottom
676 stack.push(off); // top
677 }
678 }
679 }
680 }
681
682 @Override
683 public void curveTo(final float x1, final float y1,
684 final float x2, final float y2,
685 final float xe, final float ye)
686 {
687 final int outcode0 = this.cOutCode;
688 final int outcode3 = Helpers.outcode(xe, ye, clipRect);
689 this.cOutCode = outcode3;
690
691 int sideCode = outcode0 & outcode3;
692
693 if (sideCode == 0) {
694 this.gOutCode = 0;
695 } else {
696 sideCode &= Helpers.outcode(x1, y1, clipRect);
697 sideCode &= Helpers.outcode(x2, y2, clipRect);
698 this.gOutCode &= sideCode;
699
700 // basic rejection criteria:
701 if (sideCode != 0) {
702 // keep last point coordinate before entering the clip again:
703 this.outside = true;
704 this.cx0 = xe;
705 this.cy0 = ye;
706
707 clip(sideCode, outcode0, outcode3);
708 return;
709 }
710 }
711 if (outside) {
712 finish();
713 }
714 // clipping disabled:
715 out.curveTo(x1, y1, x2, y2, xe, ye);
716 }
717
718 @Override
719 public void quadTo(final float x1, final float y1,
720 final float xe, final float ye)
721 {
722 final int outcode0 = this.cOutCode;
723 final int outcode2 = Helpers.outcode(xe, ye, clipRect);
724 this.cOutCode = outcode2;
725
726 int sideCode = outcode0 & outcode2;
727
728 if (sideCode == 0) {
729 this.gOutCode = 0;
730 } else {
731 sideCode &= Helpers.outcode(x1, y1, clipRect);
732 this.gOutCode &= sideCode;
733
734 // basic rejection criteria:
735 if (sideCode != 0) {
736 // keep last point coordinate before entering the clip again:
737 this.outside = true;
738 this.cx0 = xe;
739 this.cy0 = ye;
740
741 clip(sideCode, outcode0, outcode2);
742 return;
743 }
744 }
745 if (outside) {
746 finish();
747 }
748 // clipping disabled:
749 out.quadTo(x1, y1, xe, ye);
750 }
751
752 @Override
753 public long getNativeConsumer() {
754 throw new InternalError("Not using a native peer");
755 }
756 }
757
758 static final class PathTracer implements PathConsumer2D {
759 private final String prefix;
760 private PathConsumer2D out;
761
762 PathTracer(String name) {
763 this.prefix = name + ": ";
764 }
765
766 PathTracer init(PathConsumer2D out) {
767 this.out = out;
768 return this; // fluent API
769 }
770
771 @Override
772 public void moveTo(float x0, float y0) {
773 log("moveTo (" + x0 + ", " + y0 + ')');
774 out.moveTo(x0, y0);
775 }
776
777 @Override
791
792 @Override
793 public void quadTo(float x1, float y1, float x2, float y2) {
794 log("quadTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ')');
795 out.quadTo(x1, y1, x2, y2);
796 }
797
798 @Override
799 public void closePath() {
800 log("closePath");
801 out.closePath();
802 }
803
804 @Override
805 public void pathDone() {
806 log("pathDone");
807 out.pathDone();
808 }
809
810 private void log(final String message) {
811 System.out.println(prefix + message);
812 }
813
814 @Override
815 public long getNativeConsumer() {
816 throw new InternalError("Not using a native peer");
817 }
818 }
819 }
|
1 /*
2 * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package sun.java2d.marlin;
27
28 import sun.awt.geom.PathConsumer2D;
29 import java.awt.geom.AffineTransform;
30 import java.awt.geom.Path2D;
31 import java.util.Arrays;
32 import sun.java2d.marlin.Helpers.IndexStack;
33 import sun.java2d.marlin.Helpers.PolyStack;
34
35 final class TransformingPathConsumer2D {
36
37 // higher uncertainty in float variant for huge shapes > 10^7
38 static final float CLIP_RECT_PADDING = 1.0f;
39
40 private final RendererContext rdrCtx;
41
42 // recycled ClosedPathDetector instance from detectClosedPath()
43 private final ClosedPathDetector cpDetector;
44
45 // recycled PathClipFilter instance from pathClipper()
46 private final PathClipFilter pathClipper;
47
48 // recycled PathConsumer2D instance from wrapPath2D()
49 private final Path2DWrapper wp_Path2DWrapper = new Path2DWrapper();
50
51 // recycled PathConsumer2D instances from deltaTransformConsumer()
52 private final DeltaScaleFilter dt_DeltaScaleFilter = new DeltaScaleFilter();
53 private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter();
54
55 // recycled PathConsumer2D instances from inverseDeltaTransformConsumer()
56 private final DeltaScaleFilter iv_DeltaScaleFilter = new DeltaScaleFilter();
57 private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter();
58
59 // recycled PathTracer instances from tracer...() methods
60 private final PathTracer tracerInput = new PathTracer("[Input]");
61 private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector");
62 private final PathTracer tracerFiller = new PathTracer("Filler");
63 private final PathTracer tracerStroker = new PathTracer("Stroker");
64 private final PathTracer tracerDasher = new PathTracer("Dasher");
65
66 TransformingPathConsumer2D(final RendererContext rdrCtx) {
67 // used by RendererContext
68 this.rdrCtx = rdrCtx;
69 this.cpDetector = new ClosedPathDetector(rdrCtx);
70 this.pathClipper = new PathClipFilter(rdrCtx);
71 }
72
73 PathConsumer2D wrapPath2D(Path2D.Float p2d) {
74 return wp_Path2DWrapper.init(p2d);
75 }
76
77 PathConsumer2D traceInput(PathConsumer2D out) {
78 return tracerInput.init(out);
79 }
80
81 PathConsumer2D traceClosedPathDetector(PathConsumer2D out) {
82 return tracerCPDetector.init(out);
83 }
84
85 PathConsumer2D traceFiller(PathConsumer2D out) {
86 return tracerFiller.init(out);
87 }
88
89 PathConsumer2D traceStroker(PathConsumer2D out) {
90 return tracerStroker.init(out);
91 }
92
93 PathConsumer2D traceDasher(PathConsumer2D out) {
94 return tracerDasher.init(out);
95 }
96
97 PathConsumer2D detectClosedPath(PathConsumer2D out) {
98 return cpDetector.init(out);
99 }
100
101 PathConsumer2D pathClipper(PathConsumer2D out) {
102 return pathClipper.init(out);
103 }
104
105 PathConsumer2D deltaTransformConsumer(PathConsumer2D out,
106 AffineTransform at)
107 {
108 if (at == null) {
109 return out;
110 }
111 final float mxx = (float) at.getScaleX();
112 final float mxy = (float) at.getShearX();
113 final float myx = (float) at.getShearY();
114 final float myy = (float) at.getScaleY();
115
116 if (mxy == 0.0f && myx == 0.0f) {
492 static final class PathClipFilter implements PathConsumer2D {
493
494 private PathConsumer2D out;
495
496 // Bounds of the drawing region, at pixel precision.
497 private final float[] clipRect;
498
499 private final float[] corners = new float[8];
500 private boolean init_corners = false;
501
502 private final IndexStack stack;
503
504 // the current outcode of the current sub path
505 private int cOutCode = 0;
506
507 // the cumulated (and) outcode of the complete path
508 private int gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
509
510 private boolean outside = false;
511
512 // The current point (TODO stupid repeated info)
513 private float cx0, cy0;
514
515 // The current point OUTSIDE
516 private float cox0, coy0;
517
518 private boolean subdivide = MarlinConst.DO_CLIP_SUBDIVIDER;
519 private final CurveClipSplitter curveSplitter;
520
521 PathClipFilter(final RendererContext rdrCtx) {
522 this.clipRect = rdrCtx.clipRect;
523 this.curveSplitter = rdrCtx.curveClipSplitter;
524
525 this.stack = (rdrCtx.stats != null) ?
526 new IndexStack(rdrCtx,
527 rdrCtx.stats.stat_pcf_idxstack_indices,
528 rdrCtx.stats.hist_pcf_idxstack_indices,
529 rdrCtx.stats.stat_array_pcf_idxstack_indices)
530 : new IndexStack(rdrCtx);
531 }
532
533 PathClipFilter init(final PathConsumer2D out) {
534 this.out = out;
535
536 // Adjust the clipping rectangle with the renderer offsets
537 final float rdrOffX = Renderer.RDR_OFFSET_X;
538 final float rdrOffY = Renderer.RDR_OFFSET_Y;
539
540 // add a small rounding error:
541 final float margin = 1e-3f;
542
543 final float[] _clipRect = this.clipRect;
544 _clipRect[0] -= margin - rdrOffY;
545 _clipRect[1] += margin + rdrOffY;
546 _clipRect[2] -= margin - rdrOffX;
547 _clipRect[3] += margin + rdrOffX;
548
549 if (MarlinConst.DO_CLIP_SUBDIVIDER) {
550 // adjust padded clip rectangle:
551 curveSplitter.init();
552 }
553
554 this.init_corners = true;
555 this.gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
556
557 return this; // fluent API
558 }
559
560 /**
561 * Disposes this instance:
562 * clean up before reusing this instance
563 */
564 void dispose() {
565 stack.dispose();
566 }
567
568 private void finishPath() {
569 if (outside) {
570 // criteria: inside or totally outside ?
571 if (gOutCode == 0) {
572 finish();
573 } else {
584 if (init_corners) {
585 init_corners = false;
586
587 final float[] _corners = corners;
588 final float[] _clipRect = clipRect;
589 // Top Left (0):
590 _corners[0] = _clipRect[2];
591 _corners[1] = _clipRect[0];
592 // Bottom Left (1):
593 _corners[2] = _clipRect[2];
594 _corners[3] = _clipRect[1];
595 // Top right (2):
596 _corners[4] = _clipRect[3];
597 _corners[5] = _clipRect[0];
598 // Bottom Right (3):
599 _corners[6] = _clipRect[3];
600 _corners[7] = _clipRect[1];
601 }
602 stack.pullAll(corners, out);
603 }
604 out.lineTo(cox0, coy0);
605 this.cx0 = cox0;
606 this.cy0 = coy0;
607 }
608
609 @Override
610 public void pathDone() {
611 finishPath();
612
613 out.pathDone();
614
615 // TODO: fix possible leak if exception happened
616 // Dispose this instance:
617 dispose();
618 }
619
620 @Override
621 public void closePath() {
622 finishPath();
623
624 out.closePath();
625 }
626
627 @Override
628 public void moveTo(final float x0, final float y0) {
629 finishPath();
630
631 this.cOutCode = Helpers.outcode(x0, y0, clipRect);
632 this.outside = false;
633 out.moveTo(x0, y0);
634 this.cx0 = x0;
635 this.cy0 = y0;
636 }
637
638 @Override
639 public void lineTo(final float xe, final float ye) {
640 final int outcode0 = this.cOutCode;
641 final int outcode1 = Helpers.outcode(xe, ye, clipRect);
642
643 // Should clip
644 final int orCode = (outcode0 | outcode1);
645 if (orCode != 0) {
646 final int sideCode = (outcode0 & outcode1);
647
648 // basic rejection criteria:
649 if (sideCode == 0) {
650 // ovelap clip:
651 if (subdivide) {
652 // avoid reentrance
653 subdivide = false;
654 boolean ret;
655 // subdivide curve => callback with subdivided parts:
656 if (outside) {
657 ret = curveSplitter.splitLine(cox0, coy0, xe, ye,
658 orCode, this);
659 } else {
660 ret = curveSplitter.splitLine(cx0, cy0, xe, ye,
661 orCode, this);
662 }
663 // reentrance is done:
664 subdivide = true;
665 if (ret) {
666 return;
667 }
668 }
669 // already subdivided so render it
670 } else {
671 this.cOutCode = outcode1;
672 this.gOutCode &= sideCode;
673 // keep last point coordinate before entering the clip again:
674 this.outside = true;
675 this.cox0 = xe;
676 this.coy0 = ye;
677
678 clip(sideCode, outcode0, outcode1);
679 return;
680 }
681 }
682
683 this.cOutCode = outcode1;
684 this.gOutCode = 0;
685
686 if (outside) {
687 finish();
688 }
689 // clipping disabled:
690 out.lineTo(xe, ye);
691 this.cx0 = xe;
692 this.cy0 = ye;
693 }
694
695 private void clip(final int sideCode,
696 final int outcode0,
697 final int outcode1)
698 {
699 // corner or cross-boundary on left or right side:
700 if ((outcode0 != outcode1)
701 && ((sideCode & MarlinConst.OUTCODE_MASK_L_R) != 0))
702 {
703 // combine outcodes:
704 final int mergeCode = (outcode0 | outcode1);
705 final int tbCode = mergeCode & MarlinConst.OUTCODE_MASK_T_B;
706 final int lrCode = mergeCode & MarlinConst.OUTCODE_MASK_L_R;
707 final int off = (lrCode == MarlinConst.OUTCODE_LEFT) ? 0 : 2;
708
709 // add corners to outside stack:
710 switch (tbCode) {
711 case MarlinConst.OUTCODE_TOP:
712 stack.push(off); // top
713 return;
714 case MarlinConst.OUTCODE_BOTTOM:
715 stack.push(off + 1); // bottom
716 return;
717 default:
718 // both TOP / BOTTOM:
719 if ((outcode0 & MarlinConst.OUTCODE_TOP) != 0) {
720 // top to bottom
721 stack.push(off); // top
722 stack.push(off + 1); // bottom
723 } else {
724 // bottom to top
725 stack.push(off + 1); // bottom
726 stack.push(off); // top
727 }
728 }
729 }
730 }
731
732 @Override
733 public void curveTo(final float x1, final float y1,
734 final float x2, final float y2,
735 final float xe, final float ye)
736 {
737 final int outcode0 = this.cOutCode;
738 final int outcode1 = Helpers.outcode(x1, y1, clipRect);
739 final int outcode2 = Helpers.outcode(x2, y2, clipRect);
740 final int outcode3 = Helpers.outcode(xe, ye, clipRect);
741
742 // Should clip
743 final int orCode = (outcode0 | outcode1 | outcode2 | outcode3);
744 if (orCode != 0) {
745 final int sideCode = outcode0 & outcode1 & outcode2 & outcode3;
746
747 // basic rejection criteria:
748 if (sideCode == 0) {
749 // ovelap clip:
750 if (subdivide) {
751 // avoid reentrance
752 subdivide = false;
753 // subdivide curve => callback with subdivided parts:
754 boolean ret;
755 if (outside) {
756 ret = curveSplitter.splitCurve(cox0, coy0, x1, y1,
757 x2, y2, xe, ye,
758 orCode, this);
759 } else {
760 ret = curveSplitter.splitCurve(cx0, cy0, x1, y1,
761 x2, y2, xe, ye,
762 orCode, this);
763 }
764 // reentrance is done:
765 subdivide = true;
766 if (ret) {
767 return;
768 }
769 }
770 // already subdivided so render it
771 } else {
772 this.cOutCode = outcode3;
773 this.gOutCode &= sideCode;
774 // keep last point coordinate before entering the clip again:
775 this.outside = true;
776 this.cox0 = xe;
777 this.coy0 = ye;
778
779 clip(sideCode, outcode0, outcode3);
780 return;
781 }
782 }
783
784 this.cOutCode = outcode3;
785 this.gOutCode = 0;
786
787 if (outside) {
788 finish();
789 }
790 // clipping disabled:
791 out.curveTo(x1, y1, x2, y2, xe, ye);
792 this.cx0 = xe;
793 this.cy0 = ye;
794 }
795
796 @Override
797 public void quadTo(final float x1, final float y1,
798 final float xe, final float ye)
799 {
800 final int outcode0 = this.cOutCode;
801 final int outcode1 = Helpers.outcode(x1, y1, clipRect);
802 final int outcode2 = Helpers.outcode(xe, ye, clipRect);
803
804 // Should clip
805 final int orCode = (outcode0 | outcode1 | outcode2);
806 if (orCode != 0) {
807 final int sideCode = outcode0 & outcode1 & outcode2;
808
809 // basic rejection criteria:
810 if (sideCode == 0) {
811 // ovelap clip:
812 if (subdivide) {
813 // avoid reentrance
814 subdivide = false;
815 // subdivide curve => callback with subdivided parts:
816 boolean ret;
817 if (outside) {
818 ret = curveSplitter.splitQuad(cox0, coy0, x1, y1,
819 xe, ye, orCode, this);
820 } else {
821 ret = curveSplitter.splitQuad(cx0, cy0, x1, y1,
822 xe, ye, orCode, this);
823 }
824 // reentrance is done:
825 subdivide = true;
826 if (ret) {
827 return;
828 }
829 }
830 // already subdivided so render it
831 } else {
832 this.cOutCode = outcode2;
833 this.gOutCode &= sideCode;
834 // keep last point coordinate before entering the clip again:
835 this.outside = true;
836 this.cox0 = xe;
837 this.coy0 = ye;
838
839 clip(sideCode, outcode0, outcode2);
840 return;
841 }
842 }
843
844 this.cOutCode = outcode2;
845 this.gOutCode = 0;
846
847 if (outside) {
848 finish();
849 }
850 // clipping disabled:
851 out.quadTo(x1, y1, xe, ye);
852 this.cx0 = xe;
853 this.cy0 = ye;
854 }
855
856 @Override
857 public long getNativeConsumer() {
858 throw new InternalError("Not using a native peer");
859 }
860 }
861
862 static final class CurveClipSplitter {
863
864 static final float LEN_TH = MarlinProperties.getSubdividerMinLength();
865 static final boolean DO_CHECK_LENGTH = (LEN_TH > 0.0f);
866
867 private static final boolean TRACE = false;
868
869 private static final int MAX_N_CURVES = 3 * 4;
870
871 // clip rectangle (ymin, ymax, xmin, xmax):
872 final float[] clipRect;
873
874 // clip rectangle (ymin, ymax, xmin, xmax) including padding:
875 final float[] clipRectPad = new float[4];
876 private boolean init_clipRectPad = false;
877
878 // This is where the curve to be processed is put. We give it
879 // enough room to store all curves.
880 final float[] middle = new float[MAX_N_CURVES * 8 + 2];
881 // t values at subdivision points
882 private final float[] subdivTs = new float[MAX_N_CURVES];
883
884 // dirty curve
885 private final Curve curve;
886
887 CurveClipSplitter(final RendererContext rdrCtx) {
888 this.clipRect = rdrCtx.clipRect;
889 this.curve = rdrCtx.curve;
890 }
891
892 void init() {
893 this.init_clipRectPad = true;
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) <= LEN_TH) {
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) <= LEN_TH) {
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) <= LEN_TH) {
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 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 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
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 @Override
1174 public long getNativeConsumer() {
1175 throw new InternalError("Not using a native peer");
1176 }
1177 }
1178 }
|