< prev index next >

modules/javafx.graphics/src/main/java/com/sun/marlin/TransformingPathConsumer2D.java

Print this page

        

@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.  Oracle designates this

@@ -28,13 +28,17 @@
 import com.sun.javafx.geom.Path2D;
 import com.sun.javafx.geom.PathConsumer2D;
 import com.sun.javafx.geom.transform.BaseTransform;
 import com.sun.marlin.Helpers.IndexStack;
 import com.sun.marlin.Helpers.PolyStack;
+import java.util.Arrays;
 
 public final class TransformingPathConsumer2D {
 
+    // higher uncertainty in float variant for huge shapes > 10^7
+    static final float CLIP_RECT_PADDING = 1.0f;
+
     private final RendererContext rdrCtx;
 
     // recycled ClosedPathDetector instance from detectClosedPath()
     private final ClosedPathDetector   cpDetector;
 

@@ -55,10 +59,11 @@
     // recycled PathTracer instances from tracer...() methods
     private final PathTracer tracerInput      = new PathTracer("[Input]");
     private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector");
     private final PathTracer tracerFiller     = new PathTracer("Filler");
     private final PathTracer tracerStroker    = new PathTracer("Stroker");
+    private final PathTracer tracerDasher     = new PathTracer("Dasher");
 
     TransformingPathConsumer2D(final RendererContext rdrCtx) {
         // used by RendererContext
         this.rdrCtx = rdrCtx;
         this.cpDetector = new ClosedPathDetector(rdrCtx);

@@ -83,10 +88,14 @@
 
     public PathConsumer2D traceStroker(PathConsumer2D out) {
         return tracerStroker.init(out);
     }
 
+    public PathConsumer2D traceDasher(PathConsumer2D out) {
+        return tracerDasher.init(out);
+    }
+
     public PathConsumer2D detectClosedPath(PathConsumer2D out) {
         return cpDetector.init(out);
     }
 
     public PathConsumer2D pathClipper(PathConsumer2D out,

@@ -484,15 +493,23 @@
         // the cumulated (and) outcode of the complete path
         private int gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
 
         private boolean outside = false;
 
-        // The current point OUTSIDE
+        // The current point (TODO stupid repeated info)
         private float cx0, cy0;
 
+        // The current point OUTSIDE
+        private float cox0, coy0;
+
+        private boolean subdivide = MarlinConst.DO_CLIP_SUBDIVIDER;
+        private final CurveClipSplitter curveSplitter;
+
         PathClipFilter(final RendererContext rdrCtx) {
             this.clipRect = rdrCtx.clipRect;
+            this.curveSplitter = rdrCtx.curveClipSplitter;
+
             this.stack = (rdrCtx.stats != null) ?
                 new IndexStack(rdrCtx,
                         rdrCtx.stats.stat_pcf_idxstack_indices,
                         rdrCtx.stats.hist_pcf_idxstack_indices,
                         rdrCtx.stats.stat_array_pcf_idxstack_indices)

@@ -513,10 +530,15 @@
             _clipRect[0] -= margin - rdrOffY;
             _clipRect[1] += margin + rdrOffY;
             _clipRect[2] -= margin - rdrOffX;
             _clipRect[3] += margin + rdrOffX;
 
+            if (MarlinConst.DO_CLIP_SUBDIVIDER) {
+                // adjust padded clip rectangle:
+                curveSplitter.init();
+            }
+
             this.init_corners = true;
             this.gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
 
             return this; // fluent API
         }

@@ -563,11 +585,13 @@
                     _corners[6] = _clipRect[3];
                     _corners[7] = _clipRect[1];
                 }
                 stack.pullAll(corners, out);
             }
-            out.lineTo(cx0, cy0);
+            out.lineTo(cox0, coy0);
+            this.cx0 = cox0;
+            this.cy0 = coy0;
         }
 
         @Override
         public void pathDone() {
             finishPath();

@@ -588,42 +612,72 @@
 
         @Override
         public void moveTo(final float x0, final float y0) {
             finishPath();
 
-            final int outcode = Helpers.outcode(x0, y0, clipRect);
-            this.cOutCode = outcode;
+            this.cOutCode = Helpers.outcode(x0, y0, clipRect);
             this.outside = false;
             out.moveTo(x0, y0);
+            this.cx0 = x0;
+            this.cy0 = y0;
         }
 
         @Override
         public void lineTo(final float xe, final float ye) {
             final int outcode0 = this.cOutCode;
             final int outcode1 = Helpers.outcode(xe, ye, clipRect);
-            this.cOutCode = outcode1;
 
-            final int sideCode = (outcode0 & outcode1);
+            // Should clip
+            final int orCode = (outcode0 | outcode1);
+            if (orCode != 0) {
+                final int sideCode = (outcode0 & outcode1);
 
-            // basic rejection criteria:
-            if (sideCode == 0) {
-                this.gOutCode = 0;
-            } else {
-                this.gOutCode &= sideCode;
-                // keep last point coordinate before entering the clip again:
-                this.outside = true;
-                this.cx0 = xe;
-                this.cy0 = ye;
+                // basic rejection criteria:
+                if (sideCode == 0) {
+                    // ovelap clip:
+                    if (subdivide) {
+                        // avoid reentrance
+                        subdivide = false;
+                        boolean ret;
+                        // subdivide curve => callback with subdivided parts:
+                        if (outside) {
+                            ret = curveSplitter.splitLine(cox0, coy0, xe, ye,
+                                                          orCode, this);
+                        } else {
+                            ret = curveSplitter.splitLine(cx0, cy0, xe, ye,
+                                                          orCode, this);
+                        }
+                        // reentrance is done:
+                        subdivide = true;
+                        if (ret) {
+                            return;
+                        }
+                    }
+                    // already subdivided so render it
+                } else {
+                    this.cOutCode = outcode1;
+                    this.gOutCode &= sideCode;
+                    // keep last point coordinate before entering the clip again:
+                    this.outside = true;
+                    this.cox0 = xe;
+                    this.coy0 = ye;
 
-                clip(sideCode, outcode0, outcode1);
-                return;
+                    clip(sideCode, outcode0, outcode1);
+                    return;
+                }
             }
+
+            this.cOutCode = outcode1;
+            this.gOutCode = 0;
+
             if (outside) {
                 finish();
             }
             // clipping disabled:
             out.lineTo(xe, ye);
+            this.cx0 = xe;
+            this.cy0 = ye;
         }
 
         private void clip(final int sideCode,
                           final int outcode0,
                           final int outcode1)

@@ -639,26 +693,22 @@
                 final int off = (lrCode == MarlinConst.OUTCODE_LEFT) ? 0 : 2;
 
                 // add corners to outside stack:
                 switch (tbCode) {
                     case MarlinConst.OUTCODE_TOP:
-// System.out.println("TOP "+ ((off == 0) ? "LEFT" : "RIGHT"));
                         stack.push(off); // top
                         return;
                     case MarlinConst.OUTCODE_BOTTOM:
-// System.out.println("BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT"));
                         stack.push(off + 1); // bottom
                         return;
                     default:
                         // both TOP / BOTTOM:
                         if ((outcode0 & MarlinConst.OUTCODE_TOP) != 0) {
-// System.out.println("TOP + BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT"));
                             // top to bottom
                             stack.push(off); // top
                             stack.push(off + 1); // bottom
                         } else {
-// System.out.println("BOTTOM + TOP "+ ((off == 0) ? "LEFT" : "RIGHT"));
                             // bottom to top
                             stack.push(off + 1); // bottom
                             stack.push(off); // top
                         }
                 }

@@ -669,72 +719,381 @@
         public void curveTo(final float x1, final float y1,
                             final float x2, final float y2,
                             final float xe, final float ye)
         {
             final int outcode0 = this.cOutCode;
+            final int outcode1 = Helpers.outcode(x1, y1, clipRect);
+            final int outcode2 = Helpers.outcode(x2, y2, clipRect);
             final int outcode3 = Helpers.outcode(xe, ye, clipRect);
-            this.cOutCode = outcode3;
-
-            int sideCode = outcode0 & outcode3;
 
-            if (sideCode == 0) {
-                this.gOutCode = 0;
-            } else {
-                sideCode &= Helpers.outcode(x1, y1, clipRect);
-                sideCode &= Helpers.outcode(x2, y2, clipRect);
-                this.gOutCode &= sideCode;
+            // Should clip
+            final int orCode = (outcode0 | outcode1 | outcode2 | outcode3);
+            if (orCode != 0) {
+                final int sideCode = outcode0 & outcode1 & outcode2 & outcode3;
 
                 // basic rejection criteria:
-                if (sideCode != 0) {
+                if (sideCode == 0) {
+                    // ovelap clip:
+                    if (subdivide) {
+                        // avoid reentrance
+                        subdivide = false;
+                        // subdivide curve => callback with subdivided parts:
+                        boolean ret;
+                        if (outside) {
+                            ret = curveSplitter.splitCurve(cox0, coy0, x1, y1,
+                                                           x2, y2, xe, ye,
+                                                           orCode, this);
+                        } else {
+                            ret = curveSplitter.splitCurve(cx0, cy0, x1, y1,
+                                                           x2, y2, xe, ye,
+                                                           orCode, this);
+                        }
+                        // reentrance is done:
+                        subdivide = true;
+                        if (ret) {
+                            return;
+                        }
+                    }
+                    // already subdivided so render it
+                } else {
+                    this.cOutCode = outcode3;
+                    this.gOutCode &= sideCode;
                     // keep last point coordinate before entering the clip again:
                     this.outside = true;
-                    this.cx0 = xe;
-                    this.cy0 = ye;
+                    this.cox0 = xe;
+                    this.coy0 = ye;
 
                     clip(sideCode, outcode0, outcode3);
                     return;
                 }
             }
+
+            this.cOutCode = outcode3;
+            this.gOutCode = 0;
+
             if (outside) {
                 finish();
             }
             // clipping disabled:
             out.curveTo(x1, y1, x2, y2, xe, ye);
+            this.cx0 = xe;
+            this.cy0 = ye;
         }
 
         @Override
         public void quadTo(final float x1, final float y1,
                            final float xe, final float ye)
         {
             final int outcode0 = this.cOutCode;
+            final int outcode1 = Helpers.outcode(x1, y1, clipRect);
             final int outcode2 = Helpers.outcode(xe, ye, clipRect);
-            this.cOutCode = outcode2;
-
-            int sideCode = outcode0 & outcode2;
 
-            if (sideCode == 0) {
-                this.gOutCode = 0;
-            } else {
-                sideCode &= Helpers.outcode(x1, y1, clipRect);
-                this.gOutCode &= sideCode;
+            // Should clip
+            final int orCode = (outcode0 | outcode1 | outcode2);
+            if (orCode != 0) {
+                final int sideCode = outcode0 & outcode1 & outcode2;
 
                 // basic rejection criteria:
-                if (sideCode != 0) {
+                if (sideCode == 0) {
+                    // ovelap clip:
+                    if (subdivide) {
+                        // avoid reentrance
+                        subdivide = false;
+                        // subdivide curve => callback with subdivided parts:
+                        boolean ret;
+                        if (outside) {
+                            ret = curveSplitter.splitQuad(cox0, coy0, x1, y1,
+                                                          xe, ye, orCode, this);
+                        } else {
+                            ret = curveSplitter.splitQuad(cx0, cy0, x1, y1,
+                                                          xe, ye, orCode, this);
+                        }
+                        // reentrance is done:
+                        subdivide = true;
+                        if (ret) {
+                            return;
+                        }
+                    }
+                    // already subdivided so render it
+                } else {
+                    this.cOutCode = outcode2;
+                    this.gOutCode &= sideCode;
                     // keep last point coordinate before entering the clip again:
                     this.outside = true;
-                    this.cx0 = xe;
-                    this.cy0 = ye;
+                    this.cox0 = xe;
+                    this.coy0 = ye;
 
                     clip(sideCode, outcode0, outcode2);
                     return;
                 }
             }
+
+            this.cOutCode = outcode2;
+            this.gOutCode = 0;
+
             if (outside) {
                 finish();
             }
             // clipping disabled:
             out.quadTo(x1, y1, xe, ye);
+            this.cx0 = xe;
+            this.cy0 = ye;
+        }
+    }
+
+    static final class CurveClipSplitter {
+
+        static final float LEN_TH = MarlinProperties.getSubdividerMinLength();
+        static final boolean DO_CHECK_LENGTH = (LEN_TH > 0.0f);
+
+        private static final boolean TRACE = false;
+
+        private static final int MAX_N_CURVES = 3 * 4;
+
+        // clip rectangle (ymin, ymax, xmin, xmax):
+        final float[] clipRect;
+
+        // clip rectangle (ymin, ymax, xmin, xmax) including padding:
+        final float[] clipRectPad = new float[4];
+        private boolean init_clipRectPad = false;
+
+        // This is where the curve to be processed is put. We give it
+        // enough room to store all curves.
+        final float[] middle = new float[MAX_N_CURVES * 8 + 2];
+        // t values at subdivision points
+        private final float[] subdivTs = new float[MAX_N_CURVES];
+
+        // dirty curve
+        private final Curve curve;
+
+        CurveClipSplitter(final RendererContext rdrCtx) {
+            this.clipRect = rdrCtx.clipRect;
+            this.curve = rdrCtx.curve;
+        }
+
+        void init() {
+            this.init_clipRectPad = true;
+        }
+
+        private void initPaddedClip() {
+            // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY
+            // adjust padded clip rectangle (ymin, ymax, xmin, xmax):
+            // add a rounding error (curve subdivision ~ 0.1px):
+            final float[] _clipRect = clipRect;
+            final float[] _clipRectPad = clipRectPad;
+
+            _clipRectPad[0] = _clipRect[0] - CLIP_RECT_PADDING;
+            _clipRectPad[1] = _clipRect[1] + CLIP_RECT_PADDING;
+            _clipRectPad[2] = _clipRect[2] - CLIP_RECT_PADDING;
+            _clipRectPad[3] = _clipRect[3] + CLIP_RECT_PADDING;
+
+            if (TRACE) {
+                MarlinUtils.logInfo("clip: X [" + _clipRectPad[2] + " .. " + _clipRectPad[3] +"] "
+                                        + "Y ["+ _clipRectPad[0] + " .. " + _clipRectPad[1] +"]");
+            }
+        }
+
+        boolean splitLine(final float x0, final float y0,
+                          final float x1, final float y1,
+                          final int outCodeOR,
+                          final PathConsumer2D out)
+        {
+            if (TRACE) {
+                MarlinUtils.logInfo("divLine P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ")");
+            }
+
+            if (DO_CHECK_LENGTH && Helpers.fastLineLen(x0, y0, x1, y1) <= LEN_TH) {
+                return false;
+            }
+
+            final float[] mid = middle;
+            mid[0] = x0;  mid[1] = y0;
+            mid[2] = x1;  mid[3] = y1;
+
+            return subdivideAtIntersections(4, outCodeOR, out);
+        }
+
+        boolean splitQuad(final float x0, final float y0,
+                          final float x1, final float y1,
+                          final float x2, final float y2,
+                          final int outCodeOR,
+                          final PathConsumer2D out)
+        {
+            if (TRACE) {
+                MarlinUtils.logInfo("divQuad P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ")");
+            }
+
+            if (DO_CHECK_LENGTH && Helpers.fastQuadLen(x0, y0, x1, y1, x2, y2) <= LEN_TH) {
+                return false;
+            }
+
+            final float[] mid = middle;
+            mid[0] = x0;  mid[1] = y0;
+            mid[2] = x1;  mid[3] = y1;
+            mid[4] = x2;  mid[5] = y2;
+
+            return subdivideAtIntersections(6, outCodeOR, out);
+        }
+
+        boolean splitCurve(final float x0, final float y0,
+                           final float x1, final float y1,
+                           final float x2, final float y2,
+                           final float x3, final float y3,
+                           final int outCodeOR,
+                           final PathConsumer2D out)
+        {
+            if (TRACE) {
+                MarlinUtils.logInfo("divCurve P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ")");
+            }
+
+            if (DO_CHECK_LENGTH && Helpers.fastCurvelen(x0, y0, x1, y1, x2, y2, x3, y3) <= LEN_TH) {
+                return false;
+            }
+
+            final float[] mid = middle;
+            mid[0] = x0;  mid[1] = y0;
+            mid[2] = x1;  mid[3] = y1;
+            mid[4] = x2;  mid[5] = y2;
+            mid[6] = x3;  mid[7] = y3;
+
+            return subdivideAtIntersections(8, outCodeOR, out);
+        }
+
+        private boolean subdivideAtIntersections(final int type, final int outCodeOR,
+                                                 final PathConsumer2D out)
+        {
+            final float[] mid = middle;
+            final float[] subTs = subdivTs;
+
+            if (init_clipRectPad) {
+                init_clipRectPad = false;
+                initPaddedClip();
+            }
+
+            final int nSplits = Helpers.findClipPoints(curve, mid, subTs, type,
+                                                        outCodeOR, clipRectPad);
+
+            if (TRACE) {
+                MarlinUtils.logInfo("nSplits: "+ nSplits);
+                MarlinUtils.logInfo("subTs: " + Arrays.toString(Arrays.copyOfRange(subTs, 0, nSplits)));
+            }
+            if (nSplits == 0) {
+                // only curve support shortcut
+                return false;
+            }
+            float prevT = 0.0f;
+
+            for (int i = 0, off = 0; i < nSplits; i++, off += type) {
+                final float t = subTs[i];
+
+                Helpers.subdivideAt((t - prevT) / (1.0f - prevT),
+                                     mid, off, mid, off, type);
+                prevT = t;
+            }
+
+            for (int i = 0, off = 0; i <= nSplits; i++, off += type) {
+                if (TRACE) {
+                    MarlinUtils.logInfo("Part Curve " + Arrays.toString(Arrays.copyOfRange(mid, off, off + type)));
+                }
+                emitCurrent(type, mid, off, out);
+            }
+            return true;
+        }
+
+        static void emitCurrent(final int type, final float[] pts,
+                                final int off, final PathConsumer2D out)
+        {
+            // if instead of switch (perf + most probable cases first)
+            if (type == 8) {
+                out.curveTo(pts[off + 2], pts[off + 3],
+                            pts[off + 4], pts[off + 5],
+                            pts[off + 6], pts[off + 7]);
+            } else if (type == 4) {
+                out.lineTo(pts[off + 2], pts[off + 3]);
+            } else {
+                out.quadTo(pts[off + 2], pts[off + 3],
+                           pts[off + 4], pts[off + 5]);
+            }
+        }
+    }
+
+    public static final class CurveBasicMonotonizer {
+
+        private static final int MAX_N_CURVES = 11;
+
+        // squared half line width (for stroker)
+        private float lw2;
+
+        // number of splitted curves
+        int nbSplits;
+
+        // This is where the curve to be processed is put. We give it
+        // enough room to store all curves.
+        final float[] middle = new float[MAX_N_CURVES * 6 + 2];
+        // t values at subdivision points
+        private final float[] subdivTs = new float[MAX_N_CURVES - 1];
+
+        // dirty curve
+        private final Curve curve;
+
+        CurveBasicMonotonizer(final RendererContext rdrCtx) {
+            this.curve = rdrCtx.curve;
+        }
+
+        public void init(final float lineWidth) {
+            this.lw2 = (lineWidth * lineWidth) / 4.0f;
+        }
+
+        CurveBasicMonotonizer curve(final float x0, final float y0,
+                                    final float x1, final float y1,
+                                    final float x2, final float y2,
+                                    final float x3, final float y3)
+        {
+            final float[] mid = middle;
+            mid[0] = x0;  mid[1] = y0;
+            mid[2] = x1;  mid[3] = y1;
+            mid[4] = x2;  mid[5] = y2;
+            mid[6] = x3;  mid[7] = y3;
+
+            final float[] subTs = subdivTs;
+            final int nSplits = Helpers.findSubdivPoints(curve, mid, subTs, 8, lw2);
+
+            float prevT = 0.0f;
+            for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
+                final float t = subTs[i];
+
+                Helpers.subdivideCubicAt((t - prevT) / (1.0f - prevT),
+                                          mid, off, mid, off, off + 6);
+                prevT = t;
+            }
+
+            this.nbSplits = nSplits;
+            return this;
+        }
+
+        CurveBasicMonotonizer quad(final float x0, final float y0,
+                                   final float x1, final float y1,
+                                   final float x2, final float y2)
+        {
+            final float[] mid = middle;
+            mid[0] = x0;  mid[1] = y0;
+            mid[2] = x1;  mid[3] = y1;
+            mid[4] = x2;  mid[5] = y2;
+
+            final float[] subTs = subdivTs;
+            final int nSplits = Helpers.findSubdivPoints(curve, mid, subTs, 6, lw2);
+
+            float prevt = 0.0f;
+            for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
+                final float t = subTs[i];
+                Helpers.subdivideQuadAt((t - prevt) / (1.0f - prevt),
+                                         mid, off, mid, off, off + 4);
+                prevt = t;
+            }
+
+            this.nbSplits = nSplits;
+            return this;
         }
     }
 
     static final class PathTracer implements PathConsumer2D {
         private final String prefix;

@@ -787,9 +1146,9 @@
             log("pathDone");
             out.pathDone();
         }
 
         private void log(final String message) {
-            System.out.println(prefix + message);
+            MarlinUtils.logInfo(prefix + message);
         }
     }
 }
< prev index next >