< prev index next >

openjfx9/modules/javafx.graphics/src/main/java/com/sun/prism/impl/shape/MarlinPrismUtils.java

Print this page

        

@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2016, 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

@@ -30,128 +30,475 @@
 import com.sun.javafx.geom.PathIterator;
 import com.sun.javafx.geom.Path2D;
 import com.sun.javafx.geom.Rectangle;
 import com.sun.javafx.geom.Shape;
 import com.sun.javafx.geom.transform.BaseTransform;
-import com.sun.openpisces.Dasher;
-import com.sun.openpisces.Renderer;
-import com.sun.openpisces.Stroker;
-import com.sun.openpisces.TransformingPathConsumer2D;
+import com.sun.marlin.MarlinConst;
+import com.sun.marlin.MarlinRenderer;
+import com.sun.marlin.RendererContext;
+import com.sun.marlin.TransformingPathConsumer2D;
 import com.sun.prism.BasicStroke;
 
-public class OpenPiscesPrismUtils {
-    private static final Renderer savedAARenderer = new Renderer(3, 3);
-    private static final Renderer savedRenderer = new Renderer(0, 0);
-    private static final Stroker savedStroker = new Stroker(savedRenderer);
-    private static final Dasher savedDasher = new Dasher(savedStroker);
-
-    private static TransformingPathConsumer2D.FilterSet transformer =
-        new TransformingPathConsumer2D.FilterSet();
-
-    private static PathConsumer2D initRenderer(BasicStroke stroke,
-                                               BaseTransform tx,
-                                               Rectangle clip,
-                                               int pirule,
-                                               Renderer renderer)
+public final class MarlinPrismUtils {
+
+    private static final boolean FORCE_NO_AA = false;
+
+    static final float UPPER_BND = Float.MAX_VALUE / 2.0f;
+    static final float LOWER_BND = -UPPER_BND;
+
+    /**
+     * Private constructor to prevent instantiation.
+     */
+    private MarlinPrismUtils() {
+    }
+
+    private static PathConsumer2D initRenderer(
+            final RendererContext rdrCtx,
+            final BasicStroke stroke,
+            final BaseTransform tx,
+            final Rectangle clip,
+            final int pirule,
+            final MarlinRenderer renderer)
     {
-        int oprule = (stroke == null && pirule == PathIterator.WIND_EVEN_ODD) ?
-            Renderer.WIND_EVEN_ODD : Renderer.WIND_NON_ZERO;
-        renderer.reset(clip.x, clip.y, clip.width, clip.height, oprule);
-        PathConsumer2D ret = transformer.getConsumer(renderer, tx);
+        final int oprule = (stroke == null && pirule == PathIterator.WIND_EVEN_ODD) ?
+            MarlinRenderer.WIND_EVEN_ODD : MarlinRenderer.WIND_NON_ZERO;
+
+        // We use strokerat so that in Stroker and Dasher we can work only
+        // with the pre-transformation coordinates. This will repeat a lot of
+        // computations done in the path iterator, but the alternative is to
+        // work with transformed paths and compute untransformed coordinates
+        // as needed. This would be faster but I do not think the complexity
+        // of working with both untransformed and transformed coordinates in
+        // the same code is worth it.
+        // However, if a path's width is constant after a transformation,
+        // we can skip all this untransforming.
+
+        // As pathTo() will check transformed coordinates for invalid values
+        // (NaN / Infinity) to ignore such points, it is necessary to apply the
+        // transformation before the path processing.
+        BaseTransform strokerTx = null;
+
+        int dashLen = -1;
+        boolean recycleDashes = false;
+
+        float width = 0f, dashphase = 0f;
+        float[] dashes = null;
+
         if (stroke != null) {
-            savedStroker.reset(stroke.getLineWidth(), stroke.getEndCap(),
-                               stroke.getLineJoin(), stroke.getMiterLimit());
-            savedStroker.setConsumer(ret);
-            ret = savedStroker;
-            float dashes[] = stroke.getDashArray();
-            if (dashes != null) {
-                savedDasher.reset(dashes, stroke.getDashPhase());
-                ret = savedDasher;
+            width = stroke.getLineWidth();
+            dashes = stroke.getDashArray();
+            dashphase = stroke.getDashPhase();
+
+            if (tx != null && !tx.isIdentity()) {
+                final double a = tx.getMxx();
+                final double b = tx.getMxy();
+                final double c = tx.getMyx();
+                final double d = tx.getMyy();
+
+                // If the transform is a constant multiple of an orthogonal transformation
+                // then every length is just multiplied by a constant, so we just
+                // need to transform input paths to stroker and tell stroker
+                // the scaled width. This condition is satisfied if
+                // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we
+                // leave a bit of room for error.
+                if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) {
+                    final float scale = (float) Math.sqrt(a*a + c*c);
+
+                    if (dashes != null) {
+                        recycleDashes = true;
+                        dashLen = dashes.length;
+                        dashes = rdrCtx.dasher.copyDashArray(dashes);
+                        for (int i = 0; i < dashLen; i++) {
+                            dashes[i] *= scale;
+                        }
+                        dashphase *= scale;
+                    }
+                    width *= scale;
+
+                    // by now strokerat == null. Input paths to
+                    // stroker (and maybe dasher) will have the full transform tx
+                    // applied to them and nothing will happen to the output paths.
+                } else {
+                    strokerTx = tx;
+
+                    // by now strokerat == tx. Input paths to
+                    // stroker (and maybe dasher) will have the full transform tx
+                    // applied to them, then they will be normalized, and then
+                    // the inverse of *only the non translation part of tx* will
+                    // be applied to the normalized paths. This won't cause problems
+                    // in stroker, because, suppose tx = T*A, where T is just the
+                    // translation part of tx, and A is the rest. T*A has already
+                    // been applied to Stroker/Dasher's input. Then Ainv will be
+                    // applied. Ainv*T*A is not equal to T, but it is a translation,
+                    // which means that none of stroker's assumptions about its
+                    // input will be violated. After all this, A will be applied
+                    // to stroker's output.
+                }
             }
         }
-        return ret;
-    }
 
-    public static void feedConsumer(PathIterator pi, PathConsumer2D pc) {
-        float[] coords = new float[6];
-        while (!pi.isDone()) {
-            int type = pi.currentSegment(coords);
-            switch (type) {
-                case PathIterator.SEG_MOVETO:
-                    pc.moveTo(coords[0], coords[1]);
-                    break;
-                case PathIterator.SEG_LINETO:
-                    pc.lineTo(coords[0], coords[1]);
-                    break;
-                case PathIterator.SEG_QUADTO:
-                    pc.quadTo(coords[0], coords[1],
-                              coords[2], coords[3]);
-                    break;
-                case PathIterator.SEG_CUBICTO:
-                    pc.curveTo(coords[0], coords[1],
-                               coords[2], coords[3],
-                               coords[4], coords[5]);
-                    break;
-                case PathIterator.SEG_CLOSE:
-                    pc.closePath();
-                    break;
+        PathConsumer2D pc = renderer.init(clip.x, clip.y, clip.width, clip.height, oprule);
+
+        if (MarlinConst.USE_SIMPLIFIER) {
+            // Use simplifier after stroker before Renderer
+            // to remove collinear segments (notably due to cap square)
+            pc = rdrCtx.simplifier.init(pc);
+        }
+
+        final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;
+        pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx);
+
+        if (stroke != null) {
+            pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(),
+                    stroke.getLineJoin(), stroke.getMiterLimit());
+
+            if (dashes != null) {
+                if (!recycleDashes) {
+                    dashLen = dashes.length;
+                }
+                pc = rdrCtx.dasher.init(pc, dashes, dashLen, dashphase, recycleDashes);
             }
-            pi.next();
         }
-        pc.pathDone();
+
+        pc = transformerPC2D.inverseDeltaTransformConsumer(pc, strokerTx);
+
+        /*
+         * Pipeline seems to be:
+         * shape.getPathIterator(tx)
+         * -> (inverseDeltaTransformConsumer)
+         * -> (Dasher)
+         * -> Stroker
+         * -> (deltaTransformConsumer)
+         *
+         * -> (CollinearSimplifier) to remove redundant segments
+         *
+         * -> pc2d = Renderer (bounding box)
+         */
+        return pc;
     }
 
-    public static Renderer setupRenderer(Shape shape,
-                                  BasicStroke stroke,
-                                  BaseTransform xform,
-                                  Rectangle rclip,
-                                  boolean antialiasedShape)
+    private static boolean nearZero(final double num) {
+        return Math.abs(num) < 2.0 * Math.ulp(num);
+    }
+
+    public static MarlinRenderer setupRenderer(
+            final RendererContext rdrCtx,
+            final Shape shape,
+            final BasicStroke stroke,
+            final BaseTransform xform,
+            final Rectangle rclip,
+            final boolean antialiasedShape)
     {
-        PathIterator pi = shape.getPathIterator(null);
-        Renderer r = antialiasedShape ? savedAARenderer : savedRenderer;
-        feedConsumer(pi, initRenderer(stroke, xform, rclip, pi.getWindingRule(), r));
+        // Test if transform is identity:
+        final BaseTransform tf = (xform != null && !xform.isIdentity()) ? xform : null;
+
+        final PathIterator pi = shape.getPathIterator(tf);
+
+        final MarlinRenderer r =  (!FORCE_NO_AA && antialiasedShape) ?
+                rdrCtx.renderer : rdrCtx.getRendererNoAA();
+
+        final PathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, pi.getWindingRule(), r);
+
+        feedConsumer(rdrCtx, pi, pc2d);
+
+        return r;
+    }
+
+    public static MarlinRenderer setupRenderer(
+            final RendererContext rdrCtx,
+            final Path2D p2d,
+            final BasicStroke stroke,
+            final BaseTransform xform,
+            final Rectangle rclip,
+            final boolean antialiasedShape)
+    {
+        // Test if transform is identity:
+        final BaseTransform tf = (xform != null && !xform.isIdentity()) ? xform : null;
+
+        final MarlinRenderer r =  (!FORCE_NO_AA && antialiasedShape) ?
+                rdrCtx.renderer : rdrCtx.getRendererNoAA();
+
+        final PathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, p2d.getWindingRule(), r);
+
+        feedConsumer(rdrCtx, p2d, tf, pc2d);
+
         return r;
     }
 
-    public static Renderer setupRenderer(Path2D p2d,
-                                  BasicStroke stroke,
-                                  BaseTransform xform,
-                                  Rectangle rclip,
-                                  boolean antialiasedShape)
+    private static void feedConsumer(final RendererContext rdrCtx, final PathIterator pi,
+                                     final PathConsumer2D pc2d)
+    {
+        // mark context as DIRTY:
+        rdrCtx.dirty = true;
+
+        final float[] coords = rdrCtx.float6;
+
+        // ported from DuctusRenderingEngine.feedConsumer() but simplified:
+        // - removed skip flag = !subpathStarted
+        // - removed pathClosed (ie subpathStarted not set to false)
+        boolean subpathStarted = false;
+
+        for (; !pi.isDone(); pi.next()) {
+            switch (pi.currentSegment(coords)) {
+            case PathIterator.SEG_MOVETO:
+                /* Checking SEG_MOVETO coordinates if they are out of the
+                 * [LOWER_BND, UPPER_BND] range. This check also handles NaN
+                 * and Infinity values. Skipping next path segment in case of
+                 * invalid data.
+                 */
+                if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
+                    coords[1] < UPPER_BND && coords[1] > LOWER_BND)
+                {
+                    pc2d.moveTo(coords[0], coords[1]);
+                    subpathStarted = true;
+                }
+                break;
+            case PathIterator.SEG_LINETO:
+                /* Checking SEG_LINETO coordinates if they are out of the
+                 * [LOWER_BND, UPPER_BND] range. This check also handles NaN
+                 * and Infinity values. Ignoring current path segment in case
+                 * of invalid data. If segment is skipped its endpoint
+                 * (if valid) is used to begin new subpath.
+                 */
+                if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
+                    coords[1] < UPPER_BND && coords[1] > LOWER_BND)
+                {
+                    if (subpathStarted) {
+                        pc2d.lineTo(coords[0], coords[1]);
+                    } else {
+                        pc2d.moveTo(coords[0], coords[1]);
+                        subpathStarted = true;
+                    }
+                }
+                break;
+            case PathIterator.SEG_QUADTO:
+                // Quadratic curves take two points
+                /* Checking SEG_QUADTO coordinates if they are out of the
+                 * [LOWER_BND, UPPER_BND] range. This check also handles NaN
+                 * and Infinity values. Ignoring current path segment in case
+                 * of invalid endpoints's data. Equivalent to the SEG_LINETO
+                 * if endpoint coordinates are valid but there are invalid data
+                 * among other coordinates
+                 */
+                if (coords[2] < UPPER_BND && coords[2] > LOWER_BND &&
+                    coords[3] < UPPER_BND && coords[3] > LOWER_BND)
+                {
+                    if (subpathStarted) {
+                        if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
+                            coords[1] < UPPER_BND && coords[1] > LOWER_BND)
+                        {
+                            pc2d.quadTo(coords[0], coords[1],
+                                        coords[2], coords[3]);
+                        } else {
+                            pc2d.lineTo(coords[2], coords[3]);
+                        }
+                    } else {
+                        pc2d.moveTo(coords[2], coords[3]);
+                        subpathStarted = true;
+                    }
+                }
+                break;
+            case PathIterator.SEG_CUBICTO:
+                // Cubic curves take three points
+                /* Checking SEG_CUBICTO coordinates if they are out of the
+                 * [LOWER_BND, UPPER_BND] range. This check also handles NaN
+                 * and Infinity values. Ignoring current path segment in case
+                 * of invalid endpoints's data. Equivalent to the SEG_LINETO
+                 * if endpoint coordinates are valid but there are invalid data
+                 * among other coordinates
+                 */
+                if (coords[4] < UPPER_BND && coords[4] > LOWER_BND &&
+                    coords[5] < UPPER_BND && coords[5] > LOWER_BND)
+                {
+                    if (subpathStarted) {
+                        if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
+                            coords[1] < UPPER_BND && coords[1] > LOWER_BND &&
+                            coords[2] < UPPER_BND && coords[2] > LOWER_BND &&
+                            coords[3] < UPPER_BND && coords[3] > LOWER_BND)
+                        {
+                            pc2d.curveTo(coords[0], coords[1],
+                                         coords[2], coords[3],
+                                         coords[4], coords[5]);
+                        } else {
+                            pc2d.lineTo(coords[4], coords[5]);
+                        }
+                    } else {
+                        pc2d.moveTo(coords[4], coords[5]);
+                        subpathStarted = true;
+                    }
+                }
+                break;
+            case PathIterator.SEG_CLOSE:
+                if (subpathStarted) {
+                    pc2d.closePath();
+                    // do not set subpathStarted to false
+                    // in case of missing moveTo() after close()
+                }
+                break;
+            default:
+            }
+        }
+        pc2d.pathDone();
+
+        // mark context as CLEAN:
+        rdrCtx.dirty = false;
+    }
+
+    private static void feedConsumer(final RendererContext rdrCtx,
+                                     final Path2D p2d,
+                                     final BaseTransform xform,
+                                     final PathConsumer2D pc2d)
     {
-        Renderer r = antialiasedShape ? savedAARenderer : savedRenderer;
-        PathConsumer2D pc2d = initRenderer(stroke, xform, rclip, p2d.getWindingRule(), r);
+        // mark context as DIRTY:
+        rdrCtx.dirty = true;
+
+        final float[] coords = rdrCtx.float6;
+
+        // ported from DuctusRenderingEngine.feedConsumer() but simplified:
+        // - removed skip flag = !subpathStarted
+        // - removed pathClosed (ie subpathStarted not set to false)
+        boolean subpathStarted = false;
 
-        float coords[] = p2d.getFloatCoordsNoClone();
-        byte types[] = p2d.getCommandsNoClone();
-        int nsegs = p2d.getNumCommands();
-        int coff = 0;
-        for (int i = 0; i < nsegs; i++) {
-            switch (types[i]) {
-                case PathIterator.SEG_MOVETO:
-                    pc2d.moveTo(coords[coff+0], coords[coff+1]);
-                    coff += 2;
-                    break;
-                case PathIterator.SEG_LINETO:
-                    pc2d.lineTo(coords[coff+0], coords[coff+1]);
-                    coff += 2;
-                    break;
-                case PathIterator.SEG_QUADTO:
-                    pc2d.quadTo(coords[coff+0], coords[coff+1],
-                                coords[coff+2], coords[coff+3]);
-                    coff += 4;
-                    break;
-                case PathIterator.SEG_CUBICTO:
-                    pc2d.curveTo(coords[coff+0], coords[coff+1],
-                                 coords[coff+2], coords[coff+3],
-                                 coords[coff+4], coords[coff+5]);
-                    coff += 6;
-                    break;
-                case PathIterator.SEG_CLOSE:
+        final float pCoords[] = p2d.getFloatCoordsNoClone();
+        final byte pTypes[] = p2d.getCommandsNoClone();
+        final int nsegs = p2d.getNumCommands();
+
+        for (int i = 0, coff = 0; i < nsegs; i++) {
+            switch (pTypes[i]) {
+            case PathIterator.SEG_MOVETO:
+                if (xform == null) {
+                    coords[0] = pCoords[coff];
+                    coords[1] = pCoords[coff+1];
+                } else {
+                    xform.transform(pCoords, coff, coords, 0, 1);
+                }
+                coff += 2;
+                /* Checking SEG_MOVETO coordinates if they are out of the
+                 * [LOWER_BND, UPPER_BND] range. This check also handles NaN
+                 * and Infinity values. Skipping next path segment in case of
+                 * invalid data.
+                 */
+                if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
+                    coords[1] < UPPER_BND && coords[1] > LOWER_BND)
+                {
+                    pc2d.moveTo(coords[0], coords[1]);
+                    subpathStarted = true;
+                }
+                break;
+            case PathIterator.SEG_LINETO:
+                if (xform == null) {
+                    coords[0] = pCoords[coff];
+                    coords[1] = pCoords[coff+1];
+                } else {
+                    xform.transform(pCoords, coff, coords, 0, 1);
+                }
+                coff += 2;
+                /* Checking SEG_LINETO coordinates if they are out of the
+                 * [LOWER_BND, UPPER_BND] range. This check also handles NaN
+                 * and Infinity values. Ignoring current path segment in case
+                 * of invalid data. If segment is skipped its endpoint
+                 * (if valid) is used to begin new subpath.
+                 */
+                if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
+                    coords[1] < UPPER_BND && coords[1] > LOWER_BND)
+                {
+                    if (subpathStarted) {
+                        pc2d.lineTo(coords[0], coords[1]);
+                    } else {
+                        pc2d.moveTo(coords[0], coords[1]);
+                        subpathStarted = true;
+                    }
+                }
+                break;
+            case PathIterator.SEG_QUADTO:
+                if (xform == null) {
+                    coords[0] = pCoords[coff];
+                    coords[1] = pCoords[coff+1];
+                    coords[2] = pCoords[coff+2];
+                    coords[3] = pCoords[coff+3];
+                } else {
+                    xform.transform(pCoords, coff, coords, 0, 2);
+                }
+                coff += 4;
+                // Quadratic curves take two points
+                /* Checking SEG_QUADTO coordinates if they are out of the
+                 * [LOWER_BND, UPPER_BND] range. This check also handles NaN
+                 * and Infinity values. Ignoring current path segment in case
+                 * of invalid endpoints's data. Equivalent to the SEG_LINETO
+                 * if endpoint coordinates are valid but there are invalid data
+                 * among other coordinates
+                 */
+                if (coords[2] < UPPER_BND && coords[2] > LOWER_BND &&
+                    coords[3] < UPPER_BND && coords[3] > LOWER_BND)
+                {
+                    if (subpathStarted) {
+                        if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
+                            coords[1] < UPPER_BND && coords[1] > LOWER_BND)
+                        {
+                            pc2d.quadTo(coords[0], coords[1],
+                                        coords[2], coords[3]);
+                        } else {
+                            pc2d.lineTo(coords[2], coords[3]);
+                        }
+                    } else {
+                        pc2d.moveTo(coords[2], coords[3]);
+                        subpathStarted = true;
+                    }
+                }
+                break;
+            case PathIterator.SEG_CUBICTO:
+                if (xform == null) {
+                    coords[0] = pCoords[coff];
+                    coords[1] = pCoords[coff+1];
+                    coords[2] = pCoords[coff+2];
+                    coords[3] = pCoords[coff+3];
+                    coords[4] = pCoords[coff+4];
+                    coords[5] = pCoords[coff+5];
+                } else {
+                    xform.transform(pCoords, coff, coords, 0, 3);
+                }
+                coff += 6;
+                // Cubic curves take three points
+                /* Checking SEG_CUBICTO coordinates if they are out of the
+                 * [LOWER_BND, UPPER_BND] range. This check also handles NaN
+                 * and Infinity values. Ignoring current path segment in case
+                 * of invalid endpoints's data. Equivalent to the SEG_LINETO
+                 * if endpoint coordinates are valid but there are invalid data
+                 * among other coordinates
+                 */
+                if (coords[4] < UPPER_BND && coords[4] > LOWER_BND &&
+                    coords[5] < UPPER_BND && coords[5] > LOWER_BND)
+                {
+                    if (subpathStarted) {
+                        if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
+                            coords[1] < UPPER_BND && coords[1] > LOWER_BND &&
+                            coords[2] < UPPER_BND && coords[2] > LOWER_BND &&
+                            coords[3] < UPPER_BND && coords[3] > LOWER_BND)
+                        {
+                            pc2d.curveTo(coords[0], coords[1],
+                                         coords[2], coords[3],
+                                         coords[4], coords[5]);
+                        } else {
+                            pc2d.lineTo(coords[4], coords[5]);
+                        }
+                    } else {
+                        pc2d.moveTo(coords[4], coords[5]);
+                        subpathStarted = true;
+                    }
+                }
+                break;
+            case PathIterator.SEG_CLOSE:
+                if (subpathStarted) {
                     pc2d.closePath();
-                    break;
+                    // do not set subpathStarted to false
+                    // in case of missing moveTo() after close()
+                }
+                break;
+            default:
             }
         }
         pc2d.pathDone();
-        return r;
+
+        // mark context as CLEAN:
+        rdrCtx.dirty = false;
     }
 }
< prev index next >