--- /dev/null 2016-11-30 21:27:13.355352085 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/prism/impl/shape/DMarlinPrismUtils.java 2016-11-30 22:48:58.866420999 +0100 @@ -0,0 +1,505 @@ +/* + * 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 + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.prism.impl.shape; + + +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.marlin.MarlinConst; +import com.sun.marlin.DMarlinRenderer; +import com.sun.marlin.DPathConsumer2D; +import com.sun.marlin.DRendererContext; +import com.sun.marlin.DTransformingPathConsumer2D; +import com.sun.prism.BasicStroke; + +public final class DMarlinPrismUtils { + + 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 DMarlinPrismUtils() { + } + + private static DPathConsumer2D initRenderer( + final DRendererContext rdrCtx, + final BasicStroke stroke, + final BaseTransform tx, + final Rectangle clip, + final int pirule, + final DMarlinRenderer renderer) + { + final int oprule = (stroke == null && pirule == PathIterator.WIND_EVEN_ODD) ? + DMarlinRenderer.WIND_EVEN_ODD : DMarlinRenderer.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; + + double width = 0f, dashphase = 0f; + double[] dashesD = null; + + if (stroke != null) { + width = stroke.getLineWidth(); + final float[] dashes = stroke.getDashArray(); + dashphase = stroke.getDashPhase(); + + // Ensure converting dashes to double precision: + if (dashes != null) { + recycleDashes = true; + dashLen = dashes.length; + dashesD = rdrCtx.dasher.copyDashArray(dashes); + } + + 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 double scale = Math.sqrt(a*a + c*c); + + if (dashesD != null) { + for (int i = 0; i < dashLen; i++) { + dashesD[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. + } + } + } + + DPathConsumer2D 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 DTransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; + pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx); + + if (stroke != null) { + pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(), + stroke.getLineJoin(), stroke.getMiterLimit()); + + if (dashesD != null) { + pc = rdrCtx.dasher.init(pc, dashesD, dashLen, dashphase, recycleDashes); + } + } + + 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; + } + + private static boolean nearZero(final double num) { + return Math.abs(num) < 2.0 * Math.ulp(num); + } + + public static DMarlinRenderer setupRenderer( + final DRendererContext rdrCtx, + final Shape shape, + 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 PathIterator pi = shape.getPathIterator(tf); + + final DMarlinRenderer r = (!FORCE_NO_AA && antialiasedShape) ? + rdrCtx.renderer : rdrCtx.getRendererNoAA(); + + final DPathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, pi.getWindingRule(), r); + + feedConsumer(rdrCtx, pi, pc2d); + + return r; + } + + public static DMarlinRenderer setupRenderer( + final DRendererContext 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 DMarlinRenderer r = (!FORCE_NO_AA && antialiasedShape) ? + rdrCtx.renderer : rdrCtx.getRendererNoAA(); + + final DPathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, p2d.getWindingRule(), r); + + feedConsumer(rdrCtx, p2d, tf, pc2d); + + return r; + } + + private static void feedConsumer(final DRendererContext rdrCtx, final PathIterator pi, + final DPathConsumer2D 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 DRendererContext rdrCtx, + final Path2D p2d, + final BaseTransform xform, + final DPathConsumer2D 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; + + 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(); + // do not set subpathStarted to false + // in case of missing moveTo() after close() + } + break; + default: + } + } + pc2d.pathDone(); + + // mark context as CLEAN: + rdrCtx.dirty = false; + } +}