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.PathConsumer2D;
30 import com.sun.javafx.geom.transform.BaseTransform;
31 import com.sun.marlin.Helpers.IndexStack;
32 import com.sun.marlin.Helpers.PolyStack;
33
34 public 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 public PathConsumer2D wrapPath2D(Path2D p2d) {
69 return wp_Path2DWrapper.init(p2d);
70 }
71
72 public PathConsumer2D traceInput(PathConsumer2D out) {
73 return tracerInput.init(out);
74 }
75
76 public PathConsumer2D traceClosedPathDetector(PathConsumer2D out) {
77 return tracerCPDetector.init(out);
78 }
79
80 public PathConsumer2D traceFiller(PathConsumer2D out) {
81 return tracerFiller.init(out);
82 }
83
84 public PathConsumer2D traceStroker(PathConsumer2D out) {
85 return tracerStroker.init(out);
86 }
87
88 public PathConsumer2D detectClosedPath(PathConsumer2D out) {
89 return cpDetector.init(out);
90 }
91
92 public PathConsumer2D pathClipper(PathConsumer2D out,
93 final float rdrOffX,
94 final float rdrOffY)
95 {
96 return pathClipper.init(out, rdrOffX, rdrOffY);
97 }
98
99 public PathConsumer2D deltaTransformConsumer(PathConsumer2D out,
100 BaseTransform at,
101 final float rdrOffX,
102 final float rdrOffY)
103 {
104 if (at == null) {
105 return out;
106 }
107 final float mxx = (float) at.getMxx();
469 static final class PathClipFilter implements PathConsumer2D {
470
471 private PathConsumer2D out;
472
473 // Bounds of the drawing region, at pixel precision.
474 private final float[] clipRect;
475
476 private final float[] corners = new float[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 float cx0, cy0;
491
492 PathClipFilter(final RendererContext 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 PathConsumer2D out,
503 final double rdrOffX,
504 final double rdrOffY)
505 {
506 this.out = out;
507
508 // add a small rounding error:
509 final float margin = 1e-3f;
510
511 final float[] _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 float[] _corners = corners;
552 final float[] _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 float x0, final float y0) {
591 finishPath();
592
593 final int outcode = Helpers.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 float xe, final float ye) {
601 final int outcode0 = this.cOutCode;
602 final int outcode1 = Helpers.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 float x1, final float y1,
670 final float x2, final float y2,
671 final float xe, final float ye)
672 {
673 final int outcode0 = this.cOutCode;
674 final int outcode3 = Helpers.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 &= Helpers.outcode(x1, y1, clipRect);
683 sideCode &= Helpers.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 float x1, final float y1,
706 final float xe, final float ye)
707 {
708 final int outcode0 = this.cOutCode;
709 final int outcode2 = Helpers.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 &= Helpers.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 PathConsumer2D {
740 private final String prefix;
741 private PathConsumer2D out;
742
743 PathTracer(String name) {
744 this.prefix = name + ": ";
745 }
746
747 PathTracer init(PathConsumer2D out) {
748 this.out = out;
749 return this; // fluent API
750 }
751
752 @Override
753 public void moveTo(float x0, float y0) {
754 log("moveTo (" + x0 + ", " + y0 + ')');
755 out.moveTo(x0, y0);
772
773 @Override
774 public void quadTo(float x1, float y1, float x2, float 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.PathConsumer2D;
30 import com.sun.javafx.geom.transform.BaseTransform;
31 import com.sun.marlin.Helpers.IndexStack;
32 import com.sun.marlin.Helpers.PolyStack;
33 import java.util.Arrays;
34
35 public final class TransformingPathConsumer2D {
36
37 // higher uncertainty in float variant for huge shapes > 10^7
38 static final float CLIP_RECT_PADDING = 1.0f;
39
40 private final RendererContext rdrCtx;
41
42 // recycled ClosedPathDetector instance from detectClosedPath()
43 private final ClosedPathDetector cpDetector;
44
45 // recycled PathClipFilter instance from pathClipper()
46 private final PathClipFilter pathClipper;
47
48 // recycled PathConsumer2D instance from wrapPath2D()
49 private final Path2DWrapper wp_Path2DWrapper = new Path2DWrapper();
50
51 // recycled PathConsumer2D instances from deltaTransformConsumer()
52 private final DeltaScaleFilter dt_DeltaScaleFilter = new DeltaScaleFilter();
53 private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter();
54
55 // recycled PathConsumer2D instances from inverseDeltaTransformConsumer()
56 private final DeltaScaleFilter iv_DeltaScaleFilter = new DeltaScaleFilter();
57 private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter();
58
59 // recycled PathTracer instances from tracer...() methods
60 private final PathTracer tracerInput = new PathTracer("[Input]");
61 private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector");
62 private final PathTracer tracerFiller = new PathTracer("Filler");
63 private final PathTracer tracerStroker = new PathTracer("Stroker");
64 private final PathTracer tracerDasher = new PathTracer("Dasher");
65
66 TransformingPathConsumer2D(final RendererContext rdrCtx) {
67 // used by RendererContext
68 this.rdrCtx = rdrCtx;
69 this.cpDetector = new ClosedPathDetector(rdrCtx);
70 this.pathClipper = new PathClipFilter(rdrCtx);
71 }
72
73 public PathConsumer2D wrapPath2D(Path2D p2d) {
74 return wp_Path2DWrapper.init(p2d);
75 }
76
77 public PathConsumer2D traceInput(PathConsumer2D out) {
78 return tracerInput.init(out);
79 }
80
81 public PathConsumer2D traceClosedPathDetector(PathConsumer2D out) {
82 return tracerCPDetector.init(out);
83 }
84
85 public PathConsumer2D traceFiller(PathConsumer2D out) {
86 return tracerFiller.init(out);
87 }
88
89 public PathConsumer2D traceStroker(PathConsumer2D out) {
90 return tracerStroker.init(out);
91 }
92
93 public PathConsumer2D traceDasher(PathConsumer2D out) {
94 return tracerDasher.init(out);
95 }
96
97 public PathConsumer2D detectClosedPath(PathConsumer2D out) {
98 return cpDetector.init(out);
99 }
100
101 public PathConsumer2D pathClipper(PathConsumer2D out,
102 final float rdrOffX,
103 final float rdrOffY)
104 {
105 return pathClipper.init(out, rdrOffX, rdrOffY);
106 }
107
108 public PathConsumer2D deltaTransformConsumer(PathConsumer2D out,
109 BaseTransform at,
110 final float rdrOffX,
111 final float rdrOffY)
112 {
113 if (at == null) {
114 return out;
115 }
116 final float mxx = (float) at.getMxx();
478 static final class PathClipFilter implements PathConsumer2D {
479
480 private PathConsumer2D out;
481
482 // Bounds of the drawing region, at pixel precision.
483 private final float[] clipRect;
484
485 private final float[] corners = new float[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 float cx0, cy0;
500
501 // The current point OUTSIDE
502 private float cox0, coy0;
503
504 private boolean subdivide = MarlinConst.DO_CLIP_SUBDIVIDER;
505 private final CurveClipSplitter curveSplitter;
506
507 PathClipFilter(final RendererContext 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 PathConsumer2D out,
520 final double rdrOffX,
521 final double rdrOffY)
522 {
523 this.out = out;
524
525 // add a small rounding error:
526 final float margin = 1e-3f;
527
528 final float[] _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 float[] _corners = corners;
574 final float[] _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 float x0, final float y0) {
615 finishPath();
616
617 this.cOutCode = Helpers.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 float xe, final float ye) {
626 final int outcode0 = this.cOutCode;
627 final int outcode1 = Helpers.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 float x1, final float y1,
720 final float x2, final float y2,
721 final float xe, final float ye)
722 {
723 final int outcode0 = this.cOutCode;
724 final int outcode1 = Helpers.outcode(x1, y1, clipRect);
725 final int outcode2 = Helpers.outcode(x2, y2, clipRect);
726 final int outcode3 = Helpers.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 float x1, final float y1,
784 final float xe, final float ye)
785 {
786 final int outcode0 = this.cOutCode;
787 final int outcode1 = Helpers.outcode(x1, y1, clipRect);
788 final int outcode2 = Helpers.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 float LEN_TH = MarlinProperties.getSubdividerMinLength();
846 static final boolean DO_CHECK_LENGTH = (LEN_TH > 0.0f);
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 float[] clipRect;
854
855 // clip rectangle (ymin, ymax, xmin, xmax) including padding:
856 final float[] clipRectPad = new float[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 float[] middle = new float[MAX_N_CURVES * 8 + 2];
862 // t values at subdivision points
863 private final float[] subdivTs = new float[MAX_N_CURVES];
864
865 // dirty curve
866 private final Curve curve;
867
868 CurveClipSplitter(final RendererContext 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 float[] _clipRect = clipRect;
882 final float[] _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 float x0, final float y0,
896 final float x1, final float y1,
897 final int outCodeOR,
898 final PathConsumer2D out)
899 {
900 if (TRACE) {
901 MarlinUtils.logInfo("divLine P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ")");
902 }
903
904 if (DO_CHECK_LENGTH && Helpers.fastLineLen(x0, y0, x1, y1) <= LEN_TH) {
905 return false;
906 }
907
908 final float[] 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 float x0, final float y0,
916 final float x1, final float y1,
917 final float x2, final float y2,
918 final int outCodeOR,
919 final PathConsumer2D 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 && Helpers.fastQuadLen(x0, y0, x1, y1, x2, y2) <= LEN_TH) {
926 return false;
927 }
928
929 final float[] 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 float x0, final float y0,
938 final float x1, final float y1,
939 final float x2, final float y2,
940 final float x3, final float y3,
941 final int outCodeOR,
942 final PathConsumer2D 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 && Helpers.fastCurvelen(x0, y0, x1, y1, x2, y2, x3, y3) <= LEN_TH) {
949 return false;
950 }
951
952 final float[] mid = middle;
953 mid[0] = x0; mid[1] = y0;
954 mid[2] = x1; mid[3] = y1;
955 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 PathConsumer2D out)
963 {
964 final float[] mid = middle;
965 final float[] subTs = subdivTs;
966
967 if (init_clipRectPad) {
968 init_clipRectPad = false;
969 initPaddedClip();
970 }
971
972 final int nSplits = Helpers.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 float prevT = 0.0f;
984
985 for (int i = 0, off = 0; i < nSplits; i++, off += type) {
986 final float t = subTs[i];
987
988 Helpers.subdivideAt((t - prevT) / (1.0f - 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 float[] pts,
1003 final int off, final PathConsumer2D 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 float 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 float[] middle = new float[MAX_N_CURVES * 6 + 2];
1032 // t values at subdivision points
1033 private final float[] subdivTs = new float[MAX_N_CURVES - 1];
1034
1035 // dirty curve
1036 private final Curve curve;
1037
1038 CurveBasicMonotonizer(final RendererContext rdrCtx) {
1039 this.curve = rdrCtx.curve;
1040 }
1041
1042 public void init(final float lineWidth) {
1043 this.lw2 = (lineWidth * lineWidth) / 4.0f;
1044 }
1045
1046 CurveBasicMonotonizer curve(final float x0, final float y0,
1047 final float x1, final float y1,
1048 final float x2, final float y2,
1049 final float x3, final float y3)
1050 {
1051 final float[] 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 float[] subTs = subdivTs;
1058 final int nSplits = Helpers.findSubdivPoints(curve, mid, subTs, 8, lw2);
1059
1060 float prevT = 0.0f;
1061 for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
1062 final float t = subTs[i];
1063
1064 Helpers.subdivideCubicAt((t - prevT) / (1.0f - 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 float x0, final float y0,
1074 final float x1, final float y1,
1075 final float x2, final float y2)
1076 {
1077 final float[] 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 float[] subTs = subdivTs;
1083 final int nSplits = Helpers.findSubdivPoints(curve, mid, subTs, 6, lw2);
1084
1085 float prevt = 0.0f;
1086 for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
1087 final float t = subTs[i];
1088 Helpers.subdivideQuadAt((t - prevt) / (1.0f - 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 PathConsumer2D {
1099 private final String prefix;
1100 private PathConsumer2D out;
1101
1102 PathTracer(String name) {
1103 this.prefix = name + ": ";
1104 }
1105
1106 PathTracer init(PathConsumer2D out) {
1107 this.out = out;
1108 return this; // fluent API
1109 }
1110
1111 @Override
1112 public void moveTo(float x0, float y0) {
1113 log("moveTo (" + x0 + ", " + y0 + ')');
1114 out.moveTo(x0, y0);
1131
1132 @Override
1133 public void quadTo(float x1, float y1, float x2, float 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 }
|