--- old/modules/javafx.graphics/src/main/java/com/sun/marlin/ByteArrayCache.java 2016-11-30 22:48:47.178420090 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/ByteArrayCache.java 2016-11-30 22:48:46.950420072 +0100 @@ -46,7 +46,7 @@ // % sed -e 's/(b\yte)[ ]*//g' -e 's/b\yte/int/g' -e 's/B\yte/Int/g' < B\yteArrayCache.java > IntArrayCache.java // % sed -e 's/(b\yte)[ ]*/(float) /g' -e 's/b\yte/float/g' -e 's/B\yte/Float/g' < B\yteArrayCache.java > FloatArrayCache.java -final class ByteArrayCache implements MarlinConst { +public final class ByteArrayCache implements MarlinConst { final boolean clean; private final int bucketCapacity; @@ -246,8 +246,8 @@ } } - static void check(final byte[] array, final int fromIndex, - final int toIndex, final byte value) + public static void check(final byte[] array, final int fromIndex, + final int toIndex, final byte value) { if (DO_CHECKS) { // check zero on full array: --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/Dasher.java 2016-11-30 22:48:47.654420127 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/Dasher.java 2016-11-30 22:48:47.426420108 +0100 @@ -190,12 +190,12 @@ final int len = dashes.length; final float[] newDashes; if (len <= MarlinConst.INITIAL_ARRAY) { - newDashes = rdrCtx.dasher.dashes_ref.initial; + newDashes = dashes_ref.initial; } else { if (DO_STATS) { rdrCtx.stats.stat_array_dasher_dasher.add(len); } - newDashes = rdrCtx.dasher.dashes_ref.getArray(len); + newDashes = dashes_ref.getArray(len); } System.arraycopy(dashes, 0, newDashes, 0, len); return newDashes; --- /dev/null 2016-11-30 21:27:13.355352085 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/DoubleArrayCache.java 2016-11-30 22:48:47.906420147 +0100 @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2015, 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.marlin; + +import static com.sun.marlin.ArrayCacheConst.ARRAY_SIZES; +import static com.sun.marlin.ArrayCacheConst.BUCKETS; +import static com.sun.marlin.ArrayCacheConst.MAX_ARRAY_SIZE; +import static com.sun.marlin.MarlinUtils.logInfo; +import static com.sun.marlin.MarlinUtils.logException; + +import java.lang.ref.WeakReference; +import java.util.Arrays; + +import com.sun.marlin.ArrayCacheConst.BucketStats; +import com.sun.marlin.ArrayCacheConst.CacheStats; + +/* + * Note that the [BYTE/INT/FLOAT]ArrayCache files are nearly identical except + * for a few type and name differences. Typically, the [BYTE]ArrayCache.java file + * is edited manually and then [INT]ArrayCache.java and [FLOAT]ArrayCache.java + * files are generated with the following command lines: + */ +// % sed -e 's/(b\yte)[ ]*//g' -e 's/b\yte/int/g' -e 's/B\yte/Int/g' < B\yteArrayCache.java > IntArrayCache.java +// % sed -e 's/(b\yte)[ ]*/(float) /g' -e 's/b\yte/float/g' -e 's/B\yte/Float/g' < B\yteArrayCache.java > FloatArrayCache.java + +public final class DoubleArrayCache implements MarlinConst { + + final boolean clean; + private final int bucketCapacity; + private WeakReference refBuckets = null; + final CacheStats stats; + + DoubleArrayCache(final boolean clean, final int bucketCapacity) { + this.clean = clean; + this.bucketCapacity = bucketCapacity; + this.stats = (DO_STATS) ? + new CacheStats(getLogPrefix(clean) + "DoubleArrayCache") : null; + } + + Bucket getCacheBucket(final int length) { + final int bucket = ArrayCacheConst.getBucket(length); + return getBuckets()[bucket]; + } + + private Bucket[] getBuckets() { + // resolve reference: + Bucket[] buckets = (refBuckets != null) ? refBuckets.get() : null; + + // create a new buckets ? + if (buckets == null) { + buckets = new Bucket[BUCKETS]; + + for (int i = 0; i < BUCKETS; i++) { + buckets[i] = new Bucket(clean, ARRAY_SIZES[i], bucketCapacity, + (DO_STATS) ? stats.bucketStats[i] : null); + } + + // update weak reference: + refBuckets = new WeakReference(buckets); + } + return buckets; + } + + Reference createRef(final int initialSize) { + return new Reference(this, initialSize); + } + + static final class Reference { + + // initial array reference (direct access) + final double[] initial; + private final boolean clean; + private final DoubleArrayCache cache; + + Reference(final DoubleArrayCache cache, final int initialSize) { + this.cache = cache; + this.clean = cache.clean; + this.initial = createArray(initialSize, clean); + if (DO_STATS) { + cache.stats.totalInitial += initialSize; + } + } + + double[] getArray(final int length) { + if (length <= MAX_ARRAY_SIZE) { + return cache.getCacheBucket(length).getArray(); + } + if (DO_STATS) { + cache.stats.oversize++; + } + if (DO_LOG_OVERSIZE) { + logInfo(getLogPrefix(clean) + "DoubleArrayCache: " + + "getArray[oversize]: length=\t" + length); + } + return createArray(length, clean); + } + + double[] widenArray(final double[] array, final int usedSize, + final int needSize) + { + final int length = array.length; + if (DO_CHECKS && length >= needSize) { + return array; + } + if (DO_STATS) { + cache.stats.resize++; + } + + // maybe change bucket: + // ensure getNewSize() > newSize: + final double[] res = getArray(ArrayCacheConst.getNewSize(usedSize, needSize)); + + // use wrapper to ensure proper copy: + System.arraycopy(array, 0, res, 0, usedSize); // copy only used elements + + // maybe return current array: + putArray(array, 0, usedSize); // ensure array is cleared + + if (DO_LOG_WIDEN_ARRAY) { + logInfo(getLogPrefix(clean) + "DoubleArrayCache: " + + "widenArray[" + res.length + + "]: usedSize=\t" + usedSize + "\tlength=\t" + length + + "\tneeded length=\t" + needSize); + } + return res; + } + + double[] putArray(final double[] array) + { + // dirty array helper: + return putArray(array, 0, array.length); + } + + double[] putArray(final double[] array, final int fromIndex, + final int toIndex) + { + if (array.length <= MAX_ARRAY_SIZE) { + if ((clean || DO_CLEAN_DIRTY) && (toIndex != 0)) { + // clean-up array of dirty part[fromIndex; toIndex[ + fill(array, fromIndex, toIndex, (double) 0); + } + // ensure to never store initial arrays in cache: + if (array != initial) { + cache.getCacheBucket(array.length).putArray(array); + } + } + return initial; + } + } + + static final class Bucket { + + private int tail = 0; + private final int arraySize; + private final boolean clean; + private final double[][] arrays; + private final BucketStats stats; + + Bucket(final boolean clean, final int arraySize, + final int capacity, final BucketStats stats) + { + this.arraySize = arraySize; + this.clean = clean; + this.stats = stats; + this.arrays = new double[capacity][]; + } + + double[] getArray() { + if (DO_STATS) { + stats.getOp++; + } + // use cache: + if (tail != 0) { + final double[] array = arrays[--tail]; + arrays[tail] = null; + return array; + } + if (DO_STATS) { + stats.createOp++; + } + return createArray(arraySize, clean); + } + + void putArray(final double[] array) + { + if (DO_CHECKS && (array.length != arraySize)) { + logInfo(getLogPrefix(clean) + "DoubleArrayCache: " + + "bad length = " + array.length); + return; + } + if (DO_STATS) { + stats.returnOp++; + } + // fill cache: + if (arrays.length > tail) { + arrays[tail++] = array; + + if (DO_STATS) { + stats.updateMaxSize(tail); + } + } else if (DO_CHECKS) { + logInfo(getLogPrefix(clean) + "DoubleArrayCache: " + + "array capacity exceeded !"); + } + } + } + + static double[] createArray(final int length, final boolean clean) { + if (clean) { + return new double[length]; + } + // use JDK9 Unsafe.allocateUninitializedArray(class, length): + return (double[]) OffHeapArray.UNSAFE.allocateUninitializedArray(double.class, length); + } + + static void fill(final double[] array, final int fromIndex, + final int toIndex, final double value) + { + // clear array data: + Arrays.fill(array, fromIndex, toIndex, value); + if (DO_CHECKS) { + check(array, fromIndex, toIndex, value); + } + } + + public static void check(final double[] array, final int fromIndex, + final int toIndex, final double value) + { + if (DO_CHECKS) { + // check zero on full array: + for (int i = 0; i < array.length; i++) { + if (array[i] != value) { + logException("Invalid value at: " + i + " = " + array[i] + + " from: " + fromIndex + " to: " + toIndex + "\n" + + Arrays.toString(array), new Throwable()); + + // ensure array is correctly filled: + Arrays.fill(array, value); + + return; + } + } + } + } + + static String getLogPrefix(final boolean clean) { + return (clean) ? "Clean" : "Dirty"; + } +} --- /dev/null 2016-11-30 21:27:13.355352085 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/DCollinearSimplifier.java 2016-11-30 22:48:48.382420184 +0100 @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2015, 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.marlin; + + + +public final class DCollinearSimplifier implements DPathConsumer2D { + + enum SimplifierState { + + Empty, PreviousPoint, PreviousLine + }; + // slope precision threshold + static final double EPS = 1e-4D; // aaime proposed 1e-3D + + DPathConsumer2D delegate; + SimplifierState state; + double px1, py1, px2, py2; + double pslope; + + DCollinearSimplifier() { + } + + public DCollinearSimplifier init(DPathConsumer2D delegate) { + this.delegate = delegate; + this.state = SimplifierState.Empty; + + return this; // fluent API + } + + @Override + public void pathDone() { + emitStashedLine(); + state = SimplifierState.Empty; + delegate.pathDone(); + } + + @Override + public void closePath() { + emitStashedLine(); + state = SimplifierState.Empty; + delegate.closePath(); + } + + @Override + public void quadTo(double x1, double y1, double x2, double y2) { + emitStashedLine(); + delegate.quadTo(x1, y1, x2, y2); + // final end point: + state = SimplifierState.PreviousPoint; + px1 = x2; + py1 = y2; + } + + @Override + public void curveTo(double x1, double y1, double x2, double y2, + double x3, double y3) { + emitStashedLine(); + delegate.curveTo(x1, y1, x2, y2, x3, y3); + // final end point: + state = SimplifierState.PreviousPoint; + px1 = x3; + py1 = y3; + } + + @Override + public void moveTo(double x, double y) { + emitStashedLine(); + delegate.moveTo(x, y); + state = SimplifierState.PreviousPoint; + px1 = x; + py1 = y; + } + + @Override + public void lineTo(final double x, final double y) { + switch (state) { + case Empty: + delegate.lineTo(x, y); + state = SimplifierState.PreviousPoint; + px1 = x; + py1 = y; + return; + + case PreviousPoint: + state = SimplifierState.PreviousLine; + px2 = x; + py2 = y; + pslope = getSlope(px1, py1, x, y); + return; + + case PreviousLine: + final double slope = getSlope(px2, py2, x, y); + // test for collinearity + if ((slope == pslope) || (Math.abs(pslope - slope) < EPS)) { + // merge segments + px2 = x; + py2 = y; + return; + } + // emit previous segment + delegate.lineTo(px2, py2); + px1 = px2; + py1 = py2; + px2 = x; + py2 = y; + pslope = slope; + return; + default: + } + } + + private void emitStashedLine() { + if (state == SimplifierState.PreviousLine) { + delegate.lineTo(px2, py2); + } + } + + private static double getSlope(double x1, double y1, double x2, double y2) { + double dy = y2 - y1; + if (dy == 0D) { + return (x2 > x1) ? Double.POSITIVE_INFINITY + : Double.NEGATIVE_INFINITY; + } + return (x2 - x1) / dy; + } +} --- /dev/null 2016-11-30 21:27:13.355352085 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/DCurve.java 2016-11-30 22:48:48.858420221 +0100 @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2007, 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.marlin; + +final class DCurve { + + double ax, ay, bx, by, cx, cy, dx, dy; + double dax, day, dbx, dby; + + DCurve() { + } + + void set(double[] points, int type) { + switch(type) { + case 8: + set(points[0], points[1], + points[2], points[3], + points[4], points[5], + points[6], points[7]); + return; + case 6: + set(points[0], points[1], + points[2], points[3], + points[4], points[5]); + return; + default: + throw new InternalError("DCurves can only be cubic or quadratic"); + } + } + + void set(double x1, double y1, + double x2, double y2, + double x3, double y3, + double x4, double y4) + { + ax = 3D * (x2 - x3) + x4 - x1; + ay = 3D * (y2 - y3) + y4 - y1; + bx = 3D * (x1 - 2D * x2 + x3); + by = 3D * (y1 - 2D * y2 + y3); + cx = 3D * (x2 - x1); + cy = 3D * (y2 - y1); + dx = x1; + dy = y1; + dax = 3D * ax; day = 3D * ay; + dbx = 2D * bx; dby = 2D * by; + } + + void set(double x1, double y1, + double x2, double y2, + double x3, double y3) + { + ax = 0D; ay = 0D; + bx = x1 - 2D * x2 + x3; + by = y1 - 2D * y2 + y3; + cx = 2D * (x2 - x1); + cy = 2D * (y2 - y1); + dx = x1; + dy = y1; + dax = 0D; day = 0D; + dbx = 2D * bx; dby = 2D * by; + } + + double xat(double t) { + return t * (t * (t * ax + bx) + cx) + dx; + } + double yat(double t) { + return t * (t * (t * ay + by) + cy) + dy; + } + + double dxat(double t) { + return t * (t * dax + dbx) + cx; + } + + double dyat(double t) { + return t * (t * day + dby) + cy; + } + + int dxRoots(double[] roots, int off) { + return DHelpers.quadraticRoots(dax, dbx, cx, roots, off); + } + + int dyRoots(double[] roots, int off) { + return DHelpers.quadraticRoots(day, dby, cy, roots, off); + } + + int infPoints(double[] pts, int off) { + // inflection point at t if -f'(t)x*f''(t)y + f'(t)y*f''(t)x == 0 + // Fortunately, this turns out to be quadratic, so there are at + // most 2 inflection points. + final double a = dax * dby - dbx * day; + final double b = 2D * (cy * dax - day * cx); + final double c = cy * dbx - cx * dby; + + return DHelpers.quadraticRoots(a, b, c, pts, off); + } + + // finds points where the first and second derivative are + // perpendicular. This happens when g(t) = f'(t)*f''(t) == 0 (where + // * is a dot product). Unfortunately, we have to solve a cubic. + private int perpendiculardfddf(double[] pts, int off) { + assert pts.length >= off + 4; + + // these are the coefficients of some multiple of g(t) (not g(t), + // because the roots of a polynomial are not changed after multiplication + // by a constant, and this way we save a few multiplications). + final double a = 2D * (dax*dax + day*day); + final double b = 3D * (dax*dbx + day*dby); + final double c = 2D * (dax*cx + day*cy) + dbx*dbx + dby*dby; + final double d = dbx*cx + dby*cy; + return DHelpers.cubicRootsInAB(a, b, c, d, pts, off, 0D, 1D); + } + + // Tries to find the roots of the function ROC(t)-w in [0, 1). It uses + // a variant of the false position algorithm to find the roots. False + // position requires that 2 initial values x0,x1 be given, and that the + // function must have opposite signs at those values. To find such + // values, we need the local extrema of the ROC function, for which we + // need the roots of its derivative; however, it's harder to find the + // roots of the derivative in this case than it is to find the roots + // of the original function. So, we find all points where this curve's + // first and second derivative are perpendicular, and we pretend these + // are our local extrema. There are at most 3 of these, so we will check + // at most 4 sub-intervals of (0,1). ROC has asymptotes at inflection + // points, so roc-w can have at least 6 roots. This shouldn't be a + // problem for what we're trying to do (draw a nice looking curve). + int rootsOfROCMinusW(double[] roots, int off, final double w, final double err) { + // no OOB exception, because by now off<=6, and roots.length >= 10 + assert off <= 6 && roots.length >= 10; + int ret = off; + int numPerpdfddf = perpendiculardfddf(roots, off); + double t0 = 0, ft0 = ROCsq(t0) - w*w; + roots[off + numPerpdfddf] = 1D; // always check interval end points + numPerpdfddf++; + for (int i = off; i < off + numPerpdfddf; i++) { + double t1 = roots[i], ft1 = ROCsq(t1) - w*w; + if (ft0 == 0D) { + roots[ret++] = t0; + } else if (ft1 * ft0 < 0D) { // have opposite signs + // (ROC(t)^2 == w^2) == (ROC(t) == w) is true because + // ROC(t) >= 0 for all t. + roots[ret++] = falsePositionROCsqMinusX(t0, t1, w*w, err); + } + t0 = t1; + ft0 = ft1; + } + + return ret - off; + } + + private static double eliminateInf(double x) { + return (x == Double.POSITIVE_INFINITY ? Double.MAX_VALUE : + (x == Double.NEGATIVE_INFINITY ? Double.MIN_VALUE : x)); + } + + // A slight modification of the false position algorithm on wikipedia. + // This only works for the ROCsq-x functions. It might be nice to have + // the function as an argument, but that would be awkward in java6. + // TODO: It is something to consider for java8 (or whenever lambda + // expressions make it into the language), depending on how closures + // and turn out. Same goes for the newton's method + // algorithm in DHelpers.java + private double falsePositionROCsqMinusX(double x0, double x1, + final double x, final double err) + { + final int iterLimit = 100; + int side = 0; + double t = x1, ft = eliminateInf(ROCsq(t) - x); + double s = x0, fs = eliminateInf(ROCsq(s) - x); + double r = s, fr; + for (int i = 0; i < iterLimit && Math.abs(t - s) > err * Math.abs(t + s); i++) { + r = (fs * t - ft * s) / (fs - ft); + fr = ROCsq(r) - x; + if (sameSign(fr, ft)) { + ft = fr; t = r; + if (side < 0) { + fs /= (1 << (-side)); + side--; + } else { + side = -1; + } + } else if (fr * fs > 0) { + fs = fr; s = r; + if (side > 0) { + ft /= (1 << side); + side++; + } else { + side = 1; + } + } else { + break; + } + } + return r; + } + + private static boolean sameSign(double x, double y) { + // another way is to test if x*y > 0. This is bad for small x, y. + return (x < 0D && y < 0D) || (x > 0D && y > 0D); + } + + // returns the radius of curvature squared at t of this curve + // see http://en.wikipedia.org/wiki/Radius_of_curvature_(applications) + private double ROCsq(final double t) { + // dx=xat(t) and dy=yat(t). These calls have been inlined for efficiency + final double dx = t * (t * dax + dbx) + cx; + final double dy = t * (t * day + dby) + cy; + final double ddx = 2D * dax * t + dbx; + final double ddy = 2D * day * t + dby; + final double dx2dy2 = dx*dx + dy*dy; + final double ddx2ddy2 = ddx*ddx + ddy*ddy; + final double ddxdxddydy = ddx*dx + ddy*dy; + return dx2dy2*((dx2dy2*dx2dy2) / (dx2dy2 * ddx2ddy2 - ddxdxddydy*ddxdxddydy)); + } +} --- /dev/null 2016-11-30 21:27:13.355352085 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/DDasher.java 2016-11-30 22:48:49.338420257 +0100 @@ -0,0 +1,745 @@ +/* + * Copyright (c) 2007, 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.marlin; + +import java.util.Arrays; + + +/** + * The DDasher class takes a series of linear commands + * (moveTo, lineTo, close and + * end) and breaks them into smaller segments according to a + * dash pattern array and a starting dash phase. + * + *

Issues: in J2Se, a zero length dash segment as drawn as a very + * short dash, whereas Pisces does not draw anything. The PostScript + * semantics are unclear. + * + */ +public final class DDasher implements DPathConsumer2D, MarlinConst { + + static final int REC_LIMIT = 4; + static final double ERR = 0.01D; + static final double MIN_T_INC = 1D / (1 << REC_LIMIT); + + // More than 24 bits of mantissa means we can no longer accurately + // measure the number of times cycled through the dash array so we + // punt and override the phase to just be 0 past that point. + static final double MAX_CYCLES = 16000000D; + + private DPathConsumer2D out; + private double[] dash; + private int dashLen; + private double startPhase; + private boolean startDashOn; + private int startIdx; + + private boolean starting; + private boolean needsMoveTo; + + private int idx; + private boolean dashOn; + private double phase; + + private double sx, sy; + private double x0, y0; + + // temporary storage for the current curve + private final double[] curDCurvepts; + + // per-thread renderer context + final DRendererContext rdrCtx; + + // flag to recycle dash array copy + boolean recycleDashes; + + // dashes ref (dirty) + final DoubleArrayCache.Reference dashes_ref; + // firstSegmentsBuffer ref (dirty) + final DoubleArrayCache.Reference firstSegmentsBuffer_ref; + + /** + * Constructs a DDasher. + * @param rdrCtx per-thread renderer context + */ + DDasher(final DRendererContext rdrCtx) { + this.rdrCtx = rdrCtx; + + dashes_ref = rdrCtx.newDirtyDoubleArrayRef(INITIAL_ARRAY); // 1K + + firstSegmentsBuffer_ref = rdrCtx.newDirtyDoubleArrayRef(INITIAL_ARRAY); // 1K + firstSegmentsBuffer = firstSegmentsBuffer_ref.initial; + + // we need curDCurvepts to be able to contain 2 curves because when + // dashing curves, we need to subdivide it + curDCurvepts = new double[8 * 2]; + } + + /** + * Initialize the DDasher. + * + * @param out an output DPathConsumer2D. + * @param dash an array of doubles containing the dash pattern + * @param dashLen length of the given dash array + * @param phase a double containing the dash phase + * @param recycleDashes true to indicate to recycle the given dash array + * @return this instance + */ + public DDasher init(final DPathConsumer2D out, double[] dash, int dashLen, + double phase, boolean recycleDashes) + { + this.out = out; + + // Normalize so 0 <= phase < dash[0] + int sidx = 0; + dashOn = true; + double sum = 0D; + for (double d : dash) { + sum += d; + } + double cycles = phase / sum; + if (phase < 0D) { + if (-cycles >= MAX_CYCLES) { + phase = 0D; + } else { + int fullcycles = FloatMath.floor_int(-cycles); + if ((fullcycles & dash.length & 1) != 0) { + dashOn = !dashOn; + } + phase += fullcycles * sum; + while (phase < 0D) { + if (--sidx < 0) { + sidx = dash.length - 1; + } + phase += dash[sidx]; + dashOn = !dashOn; + } + } + } else if (phase > 0) { + if (cycles >= MAX_CYCLES) { + phase = 0D; + } else { + int fullcycles = FloatMath.floor_int(cycles); + if ((fullcycles & dash.length & 1) != 0) { + dashOn = !dashOn; + } + phase -= fullcycles * sum; + double d; + while (phase >= (d = dash[sidx])) { + phase -= d; + sidx = (sidx + 1) % dash.length; + dashOn = !dashOn; + } + } + } + + this.dash = dash; + this.dashLen = dashLen; + this.startPhase = this.phase = phase; + this.startDashOn = dashOn; + this.startIdx = sidx; + this.starting = true; + needsMoveTo = false; + firstSegidx = 0; + + this.recycleDashes = recycleDashes; + + return this; // fluent API + } + + /** + * Disposes this dasher: + * clean up before reusing this instance + */ + void dispose() { + if (DO_CLEAN_DIRTY) { + // Force zero-fill dirty arrays: + Arrays.fill(curDCurvepts, 0D); + } + // Return arrays: + if (recycleDashes) { + dash = dashes_ref.putArray(dash); + } + firstSegmentsBuffer = firstSegmentsBuffer_ref.putArray(firstSegmentsBuffer); + } + + public double[] copyDashArray(final float[] dashes) { + final int len = dashes.length; + final double[] newDashes; + if (len <= MarlinConst.INITIAL_ARRAY) { + newDashes = dashes_ref.initial; + } else { + if (DO_STATS) { + rdrCtx.stats.stat_array_dasher_dasher.add(len); + } + newDashes = dashes_ref.getArray(len); + } + for (int i = 0; i < len; i++) { + newDashes[i] = dashes[i]; + } + return newDashes; + } + + @Override + public void moveTo(double x0, double y0) { + if (firstSegidx > 0) { + out.moveTo(sx, sy); + emitFirstSegments(); + } + needsMoveTo = true; + this.idx = startIdx; + this.dashOn = this.startDashOn; + this.phase = this.startPhase; + this.sx = this.x0 = x0; + this.sy = this.y0 = y0; + this.starting = true; + } + + private void emitSeg(double[] buf, int off, int type) { + switch (type) { + case 8: + out.curveTo(buf[off+0], buf[off+1], + buf[off+2], buf[off+3], + buf[off+4], buf[off+5]); + return; + case 6: + out.quadTo(buf[off+0], buf[off+1], + buf[off+2], buf[off+3]); + return; + case 4: + out.lineTo(buf[off], buf[off+1]); + return; + default: + } + } + + private void emitFirstSegments() { + final double[] fSegBuf = firstSegmentsBuffer; + + for (int i = 0; i < firstSegidx; ) { + int type = (int)fSegBuf[i]; + emitSeg(fSegBuf, i + 1, type); + i += (type - 1); + } + firstSegidx = 0; + } + // We don't emit the first dash right away. If we did, caps would be + // drawn on it, but we need joins to be drawn if there's a closePath() + // So, we store the path elements that make up the first dash in the + // buffer below. + private double[] firstSegmentsBuffer; // dynamic array + private int firstSegidx; + + // precondition: pts must be in relative coordinates (relative to x0,y0) + // fullDCurve is true iff the curve in pts has not been split. + private void goTo(double[] pts, int off, final int type) { + double x = pts[off + type - 4]; + double y = pts[off + type - 3]; + if (dashOn) { + if (starting) { + int len = type - 1; // - 2 + 1 + int segIdx = firstSegidx; + double[] buf = firstSegmentsBuffer; + if (segIdx + len > buf.length) { + if (DO_STATS) { + rdrCtx.stats.stat_array_dasher_firstSegmentsBuffer + .add(segIdx + len); + } + firstSegmentsBuffer = buf + = firstSegmentsBuffer_ref.widenArray(buf, segIdx, + segIdx + len); + } + buf[segIdx++] = type; + len--; + // small arraycopy (2, 4 or 6) but with offset: + System.arraycopy(pts, off, buf, segIdx, len); + segIdx += len; + firstSegidx = segIdx; + } else { + if (needsMoveTo) { + out.moveTo(x0, y0); + needsMoveTo = false; + } + emitSeg(pts, off, type); + } + } else { + starting = false; + needsMoveTo = true; + } + this.x0 = x; + this.y0 = y; + } + + @Override + public void lineTo(double x1, double y1) { + double dx = x1 - x0; + double dy = y1 - y0; + + double len = dx*dx + dy*dy; + if (len == 0D) { + return; + } + len = Math.sqrt(len); + + // The scaling factors needed to get the dx and dy of the + // transformed dash segments. + final double cx = dx / len; + final double cy = dy / len; + + final double[] _curDCurvepts = curDCurvepts; + final double[] _dash = dash; + + double leftInThisDashSegment; + double dashdx, dashdy, p; + + while (true) { + leftInThisDashSegment = _dash[idx] - phase; + + if (len <= leftInThisDashSegment) { + _curDCurvepts[0] = x1; + _curDCurvepts[1] = y1; + goTo(_curDCurvepts, 0, 4); + + // Advance phase within current dash segment + phase += len; + // TODO: compare double values using epsilon: + if (len == leftInThisDashSegment) { + phase = 0D; + idx = (idx + 1) % dashLen; + dashOn = !dashOn; + } + return; + } + + dashdx = _dash[idx] * cx; + dashdy = _dash[idx] * cy; + + if (phase == 0D) { + _curDCurvepts[0] = x0 + dashdx; + _curDCurvepts[1] = y0 + dashdy; + } else { + p = leftInThisDashSegment / _dash[idx]; + _curDCurvepts[0] = x0 + p * dashdx; + _curDCurvepts[1] = y0 + p * dashdy; + } + + goTo(_curDCurvepts, 0, 4); + + len -= leftInThisDashSegment; + // Advance to next dash segment + idx = (idx + 1) % dashLen; + dashOn = !dashOn; + phase = 0D; + } + } + + // shared instance in DDasher + private final LengthIterator li = new LengthIterator(); + + // preconditions: curDCurvepts must be an array of length at least 2 * type, + // that contains the curve we want to dash in the first type elements + private void somethingTo(int type) { + if (pointDCurve(curDCurvepts, type)) { + return; + } + li.initializeIterationOnDCurve(curDCurvepts, type); + + // initially the current curve is at curDCurvepts[0...type] + int curDCurveoff = 0; + double lastSplitT = 0D; + double t; + double leftInThisDashSegment = dash[idx] - phase; + + while ((t = li.next(leftInThisDashSegment)) < 1D) { + if (t != 0D) { + DHelpers.subdivideAt((t - lastSplitT) / (1D - lastSplitT), + curDCurvepts, curDCurveoff, + curDCurvepts, 0, + curDCurvepts, type, type); + lastSplitT = t; + goTo(curDCurvepts, 2, type); + curDCurveoff = type; + } + // Advance to next dash segment + idx = (idx + 1) % dashLen; + dashOn = !dashOn; + phase = 0D; + leftInThisDashSegment = dash[idx]; + } + goTo(curDCurvepts, curDCurveoff+2, type); + phase += li.lastSegLen(); + if (phase >= dash[idx]) { + phase = 0D; + idx = (idx + 1) % dashLen; + dashOn = !dashOn; + } + // reset LengthIterator: + li.reset(); + } + + private static boolean pointDCurve(double[] curve, int type) { + for (int i = 2; i < type; i++) { + if (curve[i] != curve[i-2]) { + return false; + } + } + return true; + } + + // Objects of this class are used to iterate through curves. They return + // t values where the left side of the curve has a specified length. + // It does this by subdividing the input curve until a certain error + // condition has been met. A recursive subdivision procedure would + // return as many as 1<= 0; i--) { + Arrays.fill(recDCurveStack[i], 0D); + } + Arrays.fill(sides, Side.LEFT); + Arrays.fill(curLeafCtrlPolyLengths, 0D); + Arrays.fill(nextRoots, 0D); + Arrays.fill(flatLeafCoefCache, 0D); + flatLeafCoefCache[2] = -1D; + } + } + + void initializeIterationOnDCurve(double[] pts, int type) { + // optimize arraycopy (8 values faster than 6 = type): + System.arraycopy(pts, 0, recDCurveStack[0], 0, 8); + this.curveType = type; + this.recLevel = 0; + this.lastT = 0D; + this.lenAtLastT = 0D; + this.nextT = 0D; + this.lenAtNextT = 0D; + goLeft(); // initializes nextT and lenAtNextT properly + this.lenAtLastSplit = 0D; + if (recLevel > 0) { + this.sides[0] = Side.LEFT; + this.done = false; + } else { + // the root of the tree is a leaf so we're done. + this.sides[0] = Side.RIGHT; + this.done = true; + } + this.lastSegLen = 0D; + } + + // 0 == false, 1 == true, -1 == invalid cached value. + private int cachedHaveLowAcceleration = -1; + + private boolean haveLowAcceleration(double err) { + if (cachedHaveLowAcceleration == -1) { + final double len1 = curLeafCtrlPolyLengths[0]; + final double len2 = curLeafCtrlPolyLengths[1]; + // the test below is equivalent to !within(len1/len2, 1, err). + // It is using a multiplication instead of a division, so it + // should be a bit faster. + if (!DHelpers.within(len1, len2, err*len2)) { + cachedHaveLowAcceleration = 0; + return false; + } + if (curveType == 8) { + final double len3 = curLeafCtrlPolyLengths[2]; + // if len1 is close to 2 and 2 is close to 3, that probably + // means 1 is close to 3 so the second part of this test might + // not be needed, but it doesn't hurt to include it. + final double errLen3 = err * len3; + if (!(DHelpers.within(len2, len3, errLen3) && + DHelpers.within(len1, len3, errLen3))) { + cachedHaveLowAcceleration = 0; + return false; + } + } + cachedHaveLowAcceleration = 1; + return true; + } + + return (cachedHaveLowAcceleration == 1); + } + + // we want to avoid allocations/gc so we keep this array so we + // can put roots in it, + private final double[] nextRoots = new double[4]; + + // caches the coefficients of the current leaf in its flattened + // form (see inside next() for what that means). The cache is + // invalid when it's third element is negative, since in any + // valid flattened curve, this would be >= 0. + private final double[] flatLeafCoefCache = new double[]{0D, 0D, -1D, 0D}; + + // returns the t value where the remaining curve should be split in + // order for the left subdivided curve to have length len. If len + // is >= than the length of the uniterated curve, it returns 1. + double next(final double len) { + final double targetLength = lenAtLastSplit + len; + while (lenAtNextT < targetLength) { + if (done) { + lastSegLen = lenAtNextT - lenAtLastSplit; + return 1D; + } + goToNextLeaf(); + } + lenAtLastSplit = targetLength; + final double leaflen = lenAtNextT - lenAtLastT; + double t = (targetLength - lenAtLastT) / leaflen; + + // cubicRootsInAB is a fairly expensive call, so we just don't do it + // if the acceleration in this section of the curve is small enough. + if (!haveLowAcceleration(0.05D)) { + // We flatten the current leaf along the x axis, so that we're + // left with a, b, c which define a 1D Bezier curve. We then + // solve this to get the parameter of the original leaf that + // gives us the desired length. + final double[] _flatLeafCoefCache = flatLeafCoefCache; + + if (_flatLeafCoefCache[2] < 0) { + double x = 0D + curLeafCtrlPolyLengths[0], + y = x + curLeafCtrlPolyLengths[1]; + if (curveType == 8) { + double z = y + curLeafCtrlPolyLengths[2]; + _flatLeafCoefCache[0] = 3D * (x - y) + z; + _flatLeafCoefCache[1] = 3D * (y - 2D * x); + _flatLeafCoefCache[2] = 3D * x; + _flatLeafCoefCache[3] = -z; + } else if (curveType == 6) { + _flatLeafCoefCache[0] = 0D; + _flatLeafCoefCache[1] = y - 2D * x; + _flatLeafCoefCache[2] = 2D * x; + _flatLeafCoefCache[3] = -y; + } + } + double a = _flatLeafCoefCache[0]; + double b = _flatLeafCoefCache[1]; + double c = _flatLeafCoefCache[2]; + double d = t * _flatLeafCoefCache[3]; + + // we use cubicRootsInAB here, because we want only roots in 0, 1, + // and our quadratic root finder doesn't filter, so it's just a + // matter of convenience. + int n = DHelpers.cubicRootsInAB(a, b, c, d, nextRoots, 0, 0, 1); + if (n == 1 && !Double.isNaN(nextRoots[0])) { + t = nextRoots[0]; + } + } + // t is relative to the current leaf, so we must make it a valid parameter + // of the original curve. + t = t * (nextT - lastT) + lastT; + if (t >= 1D) { + t = 1D; + done = true; + } + // even if done = true, if we're here, that means targetLength + // is equal to, or very, very close to the total length of the + // curve, so lastSegLen won't be too high. In cases where len + // overshoots the curve, this method will exit in the while + // loop, and lastSegLen will still be set to the right value. + lastSegLen = len; + return t; + } + + double lastSegLen() { + return lastSegLen; + } + + // go to the next leaf (in an inorder traversal) in the recursion tree + // preconditions: must be on a leaf, and that leaf must not be the root. + private void goToNextLeaf() { + // We must go to the first ancestor node that has an unvisited + // right child. + int _recLevel = recLevel; + final Side[] _sides = sides; + + _recLevel--; + while(_sides[_recLevel] == Side.RIGHT) { + if (_recLevel == 0) { + recLevel = 0; + done = true; + return; + } + _recLevel--; + } + + _sides[_recLevel] = Side.RIGHT; + // optimize arraycopy (8 values faster than 6 = type): + System.arraycopy(recDCurveStack[_recLevel], 0, + recDCurveStack[_recLevel+1], 0, 8); + _recLevel++; + + recLevel = _recLevel; + goLeft(); + } + + // go to the leftmost node from the current node. Return its length. + private void goLeft() { + double len = onLeaf(); + if (len >= 0D) { + lastT = nextT; + lenAtLastT = lenAtNextT; + nextT += (1 << (REC_LIMIT - recLevel)) * MIN_T_INC; + lenAtNextT += len; + // invalidate caches + flatLeafCoefCache[2] = -1D; + cachedHaveLowAcceleration = -1; + } else { + DHelpers.subdivide(recDCurveStack[recLevel], 0, + recDCurveStack[recLevel+1], 0, + recDCurveStack[recLevel], 0, curveType); + sides[recLevel] = Side.LEFT; + recLevel++; + goLeft(); + } + } + + // this is a bit of a hack. It returns -1 if we're not on a leaf, and + // the length of the leaf if we are on a leaf. + private double onLeaf() { + double[] curve = recDCurveStack[recLevel]; + double polyLen = 0D; + + double x0 = curve[0], y0 = curve[1]; + for (int i = 2; i < curveType; i += 2) { + final double x1 = curve[i], y1 = curve[i+1]; + final double len = DHelpers.linelen(x0, y0, x1, y1); + polyLen += len; + curLeafCtrlPolyLengths[i/2 - 1] = len; + x0 = x1; + y0 = y1; + } + + final double lineLen = DHelpers.linelen(curve[0], curve[1], + curve[curveType-2], + curve[curveType-1]); + if ((polyLen - lineLen) < ERR || recLevel == REC_LIMIT) { + return (polyLen + lineLen) / 2D; + } + return -1D; + } + } + + @Override + public void curveTo(double x1, double y1, + double x2, double y2, + double x3, double y3) + { + final double[] _curDCurvepts = curDCurvepts; + _curDCurvepts[0] = x0; _curDCurvepts[1] = y0; + _curDCurvepts[2] = x1; _curDCurvepts[3] = y1; + _curDCurvepts[4] = x2; _curDCurvepts[5] = y2; + _curDCurvepts[6] = x3; _curDCurvepts[7] = y3; + somethingTo(8); + } + + @Override + public void quadTo(double x1, double y1, double x2, double y2) { + final double[] _curDCurvepts = curDCurvepts; + _curDCurvepts[0] = x0; _curDCurvepts[1] = y0; + _curDCurvepts[2] = x1; _curDCurvepts[3] = y1; + _curDCurvepts[4] = x2; _curDCurvepts[5] = y2; + somethingTo(6); + } + + @Override + public void closePath() { + lineTo(sx, sy); + if (firstSegidx > 0) { + if (!dashOn || needsMoveTo) { + out.moveTo(sx, sy); + } + emitFirstSegments(); + } + moveTo(sx, sy); + } + + @Override + public void pathDone() { + if (firstSegidx > 0) { + out.moveTo(sx, sy); + emitFirstSegments(); + } + out.pathDone(); + + // Dispose this instance: + dispose(); + } +} + --- /dev/null 2016-11-30 21:27:13.355352085 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/DHelpers.java 2016-11-30 22:48:49.810420295 +0100 @@ -0,0 +1,441 @@ +/* + * Copyright (c) 2007, 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.marlin; + +import static java.lang.Math.PI; +import static java.lang.Math.cos; +import static java.lang.Math.sqrt; +import static java.lang.Math.cbrt; +import static java.lang.Math.acos; + +final class DHelpers implements MarlinConst { + + private DHelpers() { + throw new Error("This is a non instantiable class"); + } + + static boolean withinUNUSED(final double x, final double y, final double err) { + final double d = y - x; + return (d <= err && d >= -err); + } + + static boolean within(final double x, final double y, final double err) { + final double d = y - x; + return (d <= err && d >= -err); + } + + static int quadraticRoots(final double a, final double b, + final double c, double[] zeroes, final int off) + { + int ret = off; + double t; + if (a != 0D) { + final double dis = b*b - 4*a*c; + if (dis > 0D) { + final double sqrtDis = Math.sqrt(dis); + // depending on the sign of b we use a slightly different + // algorithm than the traditional one to find one of the roots + // so we can avoid adding numbers of different signs (which + // might result in loss of precision). + if (b >= 0D) { + zeroes[ret++] = (2D * c) / (-b - sqrtDis); + zeroes[ret++] = (-b - sqrtDis) / (2D * a); + } else { + zeroes[ret++] = (-b + sqrtDis) / (2D * a); + zeroes[ret++] = (2D * c) / (-b + sqrtDis); + } + } else if (dis == 0D) { + t = (-b) / (2D * a); + zeroes[ret++] = t; + } + } else { + if (b != 0D) { + t = (-c) / b; + zeroes[ret++] = t; + } + } + return ret - off; + } + + // find the roots of g(t) = d*t^3 + a*t^2 + b*t + c in [A,B) + static int cubicRootsInAB(double d, double a, double b, double c, + double[] pts, final int off, + final double A, final double B) + { + if (d == 0D) { + int num = quadraticRoots(a, b, c, pts, off); + return filterOutNotInAB(pts, off, num, A, B) - off; + } + // From Graphics Gems: + // http://tog.acm.org/resources/GraphicsGems/gems/Roots3And4.c + // (also from awt.geom.CubicDCurve2D. But here we don't need as + // much accuracy and we don't want to create arrays so we use + // our own customized version). + + // normal form: x^3 + ax^2 + bx + c = 0 + a /= d; + b /= d; + c /= d; + + // substitute x = y - A/3 to eliminate quadratic term: + // x^3 +Px + Q = 0 + // + // Since we actually need P/3 and Q/2 for all of the + // calculations that follow, we will calculate + // p = P/3 + // q = Q/2 + // instead and use those values for simplicity of the code. + double sq_A = a * a; + double p = (1.0/3.0) * ((-1.0/3.0) * sq_A + b); + double q = (1.0/2.0) * ((2.0/27.0) * a * sq_A - (1.0/3.0) * a * b + c); + + // use Cardano's formula + + double cb_p = p * p * p; + double D = q * q + cb_p; + + int num; + if (D < 0.0) { + // see: http://en.wikipedia.org/wiki/Cubic_function#Trigonometric_.28and_hyperbolic.29_method + final double phi = (1.0/3.0) * acos(-q / sqrt(-cb_p)); + final double t = 2.0 * sqrt(-p); + + pts[ off+0 ] = ( t * cos(phi)); + pts[ off+1 ] = (-t * cos(phi + (PI / 3.0))); + pts[ off+2 ] = (-t * cos(phi - (PI / 3.0))); + num = 3; + } else { + final double sqrt_D = sqrt(D); + final double u = cbrt(sqrt_D - q); + final double v = - cbrt(sqrt_D + q); + + pts[ off ] = (u + v); + num = 1; + + if (within(D, 0.0, 1e-8)) { + pts[off+1] = -(pts[off] / 2D); + num = 2; + } + } + + final double sub = (1D/3D) * a; + + for (int i = 0; i < num; ++i) { + pts[ off+i ] -= sub; + } + + return filterOutNotInAB(pts, off, num, A, B) - off; + } + + static double evalCubic(final double a, final double b, + final double c, final double d, + final double t) + { + return t * (t * (t * a + b) + c) + d; + } + + static double evalQuad(final double a, final double b, + final double c, final double t) + { + return t * (t * a + b) + c; + } + + // returns the index 1 past the last valid element remaining after filtering + static int filterOutNotInAB(double[] nums, final int off, final int len, + final double a, final double b) + { + int ret = off; + for (int i = off, end = off + len; i < end; i++) { + if (nums[i] >= a && nums[i] < b) { + nums[ret++] = nums[i]; + } + } + return ret; + } + + static double polyLineLength(double[] poly, final int off, final int nCoords) { + assert nCoords % 2 == 0 && poly.length >= off + nCoords : ""; + double acc = 0; + for (int i = off + 2; i < off + nCoords; i += 2) { + acc += linelen(poly[i], poly[i+1], poly[i-2], poly[i-1]); + } + return acc; + } + + static double linelen(double x1, double y1, double x2, double y2) { + final double dx = x2 - x1; + final double dy = y2 - y1; + return Math.sqrt(dx*dx + dy*dy); + } + + static void subdivide(double[] src, int srcoff, double[] left, int leftoff, + double[] right, int rightoff, int type) + { + switch(type) { + case 6: + DHelpers.subdivideQuad(src, srcoff, left, leftoff, right, rightoff); + return; + case 8: + DHelpers.subdivideCubic(src, srcoff, left, leftoff, right, rightoff); + return; + default: + throw new InternalError("Unsupported curve type"); + } + } + + static void isort(double[] a, int off, int len) { + for (int i = off + 1, end = off + len; i < end; i++) { + double ai = a[i]; + int j = i - 1; + for (; j >= off && a[j] > ai; j--) { + a[j+1] = a[j]; + } + a[j+1] = ai; + } + } + + // Most of these are copied from classes in java.awt.geom because we need + // double versions of these functions, and Line2D, CubicDCurve2D, + // QuadDCurve2D don't provide them. + /** + * Subdivides the cubic curve specified by the coordinates + * stored in the src array at indices srcoff + * through (srcoff + 7) and stores the + * resulting two subdivided curves into the two result arrays at the + * corresponding indices. + * Either or both of the left and right + * arrays may be null or a reference to the same array + * as the src array. + * Note that the last point in the first subdivided curve is the + * same as the first point in the second subdivided curve. Thus, + * it is possible to pass the same array for left + * and right and to use offsets, such as rightoff + * equals (leftoff + 6), in order + * to avoid allocating extra storage for this common point. + * @param src the array holding the coordinates for the source curve + * @param srcoff the offset into the array of the beginning of the + * the 6 source coordinates + * @param left the array for storing the coordinates for the first + * half of the subdivided curve + * @param leftoff the offset into the array of the beginning of the + * the 6 left coordinates + * @param right the array for storing the coordinates for the second + * half of the subdivided curve + * @param rightoff the offset into the array of the beginning of the + * the 6 right coordinates + * @since 1.7 + */ + static void subdivideCubic(double[] src, int srcoff, + double[] left, int leftoff, + double[] right, int rightoff) + { + double x1 = src[srcoff + 0]; + double y1 = src[srcoff + 1]; + double ctrlx1 = src[srcoff + 2]; + double ctrly1 = src[srcoff + 3]; + double ctrlx2 = src[srcoff + 4]; + double ctrly2 = src[srcoff + 5]; + double x2 = src[srcoff + 6]; + double y2 = src[srcoff + 7]; + if (left != null) { + left[leftoff + 0] = x1; + left[leftoff + 1] = y1; + } + if (right != null) { + right[rightoff + 6] = x2; + right[rightoff + 7] = y2; + } + x1 = (x1 + ctrlx1) / 2D; + y1 = (y1 + ctrly1) / 2D; + x2 = (x2 + ctrlx2) / 2D; + y2 = (y2 + ctrly2) / 2D; + double centerx = (ctrlx1 + ctrlx2) / 2D; + double centery = (ctrly1 + ctrly2) / 2D; + ctrlx1 = (x1 + centerx) / 2D; + ctrly1 = (y1 + centery) / 2D; + ctrlx2 = (x2 + centerx) / 2D; + ctrly2 = (y2 + centery) / 2D; + centerx = (ctrlx1 + ctrlx2) / 2D; + centery = (ctrly1 + ctrly2) / 2D; + if (left != null) { + left[leftoff + 2] = x1; + left[leftoff + 3] = y1; + left[leftoff + 4] = ctrlx1; + left[leftoff + 5] = ctrly1; + left[leftoff + 6] = centerx; + left[leftoff + 7] = centery; + } + if (right != null) { + right[rightoff + 0] = centerx; + right[rightoff + 1] = centery; + right[rightoff + 2] = ctrlx2; + right[rightoff + 3] = ctrly2; + right[rightoff + 4] = x2; + right[rightoff + 5] = y2; + } + } + + + static void subdivideCubicAt(double t, double[] src, int srcoff, + double[] left, int leftoff, + double[] right, int rightoff) + { + double x1 = src[srcoff + 0]; + double y1 = src[srcoff + 1]; + double ctrlx1 = src[srcoff + 2]; + double ctrly1 = src[srcoff + 3]; + double ctrlx2 = src[srcoff + 4]; + double ctrly2 = src[srcoff + 5]; + double x2 = src[srcoff + 6]; + double y2 = src[srcoff + 7]; + if (left != null) { + left[leftoff + 0] = x1; + left[leftoff + 1] = y1; + } + if (right != null) { + right[rightoff + 6] = x2; + right[rightoff + 7] = y2; + } + x1 = x1 + t * (ctrlx1 - x1); + y1 = y1 + t * (ctrly1 - y1); + x2 = ctrlx2 + t * (x2 - ctrlx2); + y2 = ctrly2 + t * (y2 - ctrly2); + double centerx = ctrlx1 + t * (ctrlx2 - ctrlx1); + double centery = ctrly1 + t * (ctrly2 - ctrly1); + ctrlx1 = x1 + t * (centerx - x1); + ctrly1 = y1 + t * (centery - y1); + ctrlx2 = centerx + t * (x2 - centerx); + ctrly2 = centery + t * (y2 - centery); + centerx = ctrlx1 + t * (ctrlx2 - ctrlx1); + centery = ctrly1 + t * (ctrly2 - ctrly1); + if (left != null) { + left[leftoff + 2] = x1; + left[leftoff + 3] = y1; + left[leftoff + 4] = ctrlx1; + left[leftoff + 5] = ctrly1; + left[leftoff + 6] = centerx; + left[leftoff + 7] = centery; + } + if (right != null) { + right[rightoff + 0] = centerx; + right[rightoff + 1] = centery; + right[rightoff + 2] = ctrlx2; + right[rightoff + 3] = ctrly2; + right[rightoff + 4] = x2; + right[rightoff + 5] = y2; + } + } + + static void subdivideQuad(double[] src, int srcoff, + double[] left, int leftoff, + double[] right, int rightoff) + { + double x1 = src[srcoff + 0]; + double y1 = src[srcoff + 1]; + double ctrlx = src[srcoff + 2]; + double ctrly = src[srcoff + 3]; + double x2 = src[srcoff + 4]; + double y2 = src[srcoff + 5]; + if (left != null) { + left[leftoff + 0] = x1; + left[leftoff + 1] = y1; + } + if (right != null) { + right[rightoff + 4] = x2; + right[rightoff + 5] = y2; + } + x1 = (x1 + ctrlx) / 2D; + y1 = (y1 + ctrly) / 2D; + x2 = (x2 + ctrlx) / 2D; + y2 = (y2 + ctrly) / 2D; + ctrlx = (x1 + x2) / 2D; + ctrly = (y1 + y2) / 2D; + if (left != null) { + left[leftoff + 2] = x1; + left[leftoff + 3] = y1; + left[leftoff + 4] = ctrlx; + left[leftoff + 5] = ctrly; + } + if (right != null) { + right[rightoff + 0] = ctrlx; + right[rightoff + 1] = ctrly; + right[rightoff + 2] = x2; + right[rightoff + 3] = y2; + } + } + + static void subdivideQuadAt(double t, double[] src, int srcoff, + double[] left, int leftoff, + double[] right, int rightoff) + { + double x1 = src[srcoff + 0]; + double y1 = src[srcoff + 1]; + double ctrlx = src[srcoff + 2]; + double ctrly = src[srcoff + 3]; + double x2 = src[srcoff + 4]; + double y2 = src[srcoff + 5]; + if (left != null) { + left[leftoff + 0] = x1; + left[leftoff + 1] = y1; + } + if (right != null) { + right[rightoff + 4] = x2; + right[rightoff + 5] = y2; + } + x1 = x1 + t * (ctrlx - x1); + y1 = y1 + t * (ctrly - y1); + x2 = ctrlx + t * (x2 - ctrlx); + y2 = ctrly + t * (y2 - ctrly); + ctrlx = x1 + t * (x2 - x1); + ctrly = y1 + t * (y2 - y1); + if (left != null) { + left[leftoff + 2] = x1; + left[leftoff + 3] = y1; + left[leftoff + 4] = ctrlx; + left[leftoff + 5] = ctrly; + } + if (right != null) { + right[rightoff + 0] = ctrlx; + right[rightoff + 1] = ctrly; + right[rightoff + 2] = x2; + right[rightoff + 3] = y2; + } + } + + static void subdivideAt(double t, double[] src, int srcoff, + double[] left, int leftoff, + double[] right, int rightoff, int size) + { + switch(size) { + case 8: + subdivideCubicAt(t, src, srcoff, left, leftoff, right, rightoff); + return; + case 6: + subdivideQuadAt(t, src, srcoff, left, leftoff, right, rightoff); + return; + } + } +} --- /dev/null 2016-11-30 21:27:13.355352085 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/DMarlinRenderer.java 2016-11-30 22:48:50.286420332 +0100 @@ -0,0 +1,48 @@ +/* + * Copyright (c) 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.marlin; + +public interface DMarlinRenderer extends DPathConsumer2D { + + public static final int WIND_EVEN_ODD = 0; + public static final int WIND_NON_ZERO = 1; + + public DMarlinRenderer init(final int pix_boundsX, final int pix_boundsY, + final int pix_boundsWidth, final int pix_boundsHeight, + final int windingRule); + + /** + * Disposes this renderer and recycle it clean up before reusing this instance + */ + public void dispose(); + + public int getOutpixMinX(); + public int getOutpixMaxX(); + public int getOutpixMinY(); + public int getOutpixMaxY(); + + public void produceAlphas(MarlinAlphaConsumer ac); +} --- /dev/null 2016-11-30 21:27:13.355352085 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/DMarlinRenderingEngine.java 2016-11-30 22:48:50.762420368 +0100 @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2007, 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.marlin; + +import java.security.AccessController; +import static com.sun.marlin.MarlinUtils.logInfo; +import com.sun.util.reentrant.ReentrantContextProvider; +import com.sun.util.reentrant.ReentrantContextProviderCLQ; +import com.sun.util.reentrant.ReentrantContextProviderTL; +import com.sun.javafx.geom.PathIterator; +import com.sun.prism.BasicStroke; +import java.security.PrivilegedAction; + +/** + * Marlin RendererEngine implementation (derived from Pisces) + */ +public class DMarlinRenderingEngine implements MarlinConst +{ + /** + * Private constructor to prevent instantiation. + */ + private DMarlinRenderingEngine() { + } + + static { + if (PathIterator.WIND_NON_ZERO != DMarlinRenderer.WIND_NON_ZERO || + PathIterator.WIND_EVEN_ODD != DMarlinRenderer.WIND_EVEN_ODD || + BasicStroke.JOIN_MITER != Stroker.JOIN_MITER || + BasicStroke.JOIN_ROUND != Stroker.JOIN_ROUND || + BasicStroke.JOIN_BEVEL != Stroker.JOIN_BEVEL || + BasicStroke.CAP_BUTT != Stroker.CAP_BUTT || + BasicStroke.CAP_ROUND != Stroker.CAP_ROUND || + BasicStroke.CAP_SQUARE != Stroker.CAP_SQUARE) + { + throw new InternalError("mismatched renderer constants"); + } + } + + // --- DRendererContext handling --- + // use ThreadLocal or ConcurrentLinkedQueue to get one DRendererContext + private static final boolean USE_THREAD_LOCAL; + + // reference type stored in either TL or CLQ + static final int REF_TYPE; + + // Per-thread DRendererContext + private static final ReentrantContextProvider RDR_CTX_PROVIDER; + + // Static initializer to use TL or CLQ mode + static { + USE_THREAD_LOCAL = MarlinProperties.isUseThreadLocal(); + + // Soft reference by default: + final String refType = AccessController.doPrivileged( + (PrivilegedAction) () -> { + String value = System.getProperty("prism.marlin.useRef"); + return (value == null) ? "soft" : value; + }); + switch (refType) { + default: + case "soft": + REF_TYPE = ReentrantContextProvider.REF_SOFT; + break; + case "weak": + REF_TYPE = ReentrantContextProvider.REF_WEAK; + break; + case "hard": + REF_TYPE = ReentrantContextProvider.REF_HARD; + break; + } + + if (USE_THREAD_LOCAL) { + RDR_CTX_PROVIDER = new ReentrantContextProviderTL(REF_TYPE) + { + @Override + protected DRendererContext newContext() { + return DRendererContext.createContext(); + } + }; + } else { + RDR_CTX_PROVIDER = new ReentrantContextProviderCLQ(REF_TYPE) + { + @Override + protected DRendererContext newContext() { + return DRendererContext.createContext(); + } + }; + } + + logSettings(DRenderer.class.getName()); + } + + private static boolean SETTINGS_LOGGED = !ENABLE_LOGS; + + public static void logSettings(final String reClass) { + // log information at startup + if (SETTINGS_LOGGED) { + return; + } + SETTINGS_LOGGED = true; + + String refType; + switch (REF_TYPE) { + default: + case ReentrantContextProvider.REF_HARD: + refType = "hard"; + break; + case ReentrantContextProvider.REF_SOFT: + refType = "soft"; + break; + case ReentrantContextProvider.REF_WEAK: + refType = "weak"; + break; + } + + logInfo("==========================================================" + + "====================="); + + logInfo("Marlin software rasterizer = ENABLED"); + logInfo("Version = [" + + Version.getVersion() + "]"); + logInfo("prism.marlin = " + + reClass); + logInfo("prism.marlin.useThreadLocal = " + + USE_THREAD_LOCAL); + logInfo("prism.marlin.useRef = " + + refType); + + logInfo("prism.marlin.edges = " + + MarlinConst.INITIAL_EDGES_COUNT); + logInfo("prism.marlin.pixelsize = " + + MarlinConst.INITIAL_PIXEL_DIM); + + logInfo("prism.marlin.subPixel_log2_X = " + + MarlinConst.SUBPIXEL_LG_POSITIONS_X); + logInfo("prism.marlin.subPixel_log2_Y = " + + MarlinConst.SUBPIXEL_LG_POSITIONS_Y); + + logInfo("prism.marlin.blockSize_log2 = " + + MarlinConst.BLOCK_SIZE_LG); + + // RLE / blockFlags settings + + logInfo("prism.marlin.forceRLE = " + + MarlinProperties.isForceRLE()); + logInfo("prism.marlin.forceNoRLE = " + + MarlinProperties.isForceNoRLE()); + logInfo("prism.marlin.useTileFlags = " + + MarlinProperties.isUseTileFlags()); + logInfo("prism.marlin.useTileFlags.useHeuristics = " + + MarlinProperties.isUseTileFlagsWithHeuristics()); + logInfo("prism.marlin.rleMinWidth = " + + MarlinConst.RLE_MIN_WIDTH); + + // optimisation parameters + logInfo("prism.marlin.useSimplifier = " + + MarlinConst.USE_SIMPLIFIER); + + // debugging parameters + logInfo("prism.marlin.doStats = " + + MarlinConst.DO_STATS); + logInfo("prism.marlin.doMonitors = " + + MarlinConst.DO_MONITORS); + logInfo("prism.marlin.doChecks = " + + MarlinConst.DO_CHECKS); + + // logging parameters + logInfo("prism.marlin.log = " + + MarlinConst.ENABLE_LOGS); + logInfo("prism.marlin.useLogger = " + + MarlinConst.USE_LOGGER); + logInfo("prism.marlin.logCreateContext = " + + MarlinConst.LOG_CREATE_CONTEXT); + logInfo("prism.marlin.logUnsafeMalloc = " + + MarlinConst.LOG_UNSAFE_MALLOC); + + // quality settings + logInfo("Renderer settings:"); + logInfo("CUB_COUNT_LG = " + Renderer.CUB_COUNT_LG); + logInfo("CUB_DEC_BND = " + Renderer.CUB_DEC_BND); + logInfo("CUB_INC_BND = " + Renderer.CUB_INC_BND); + logInfo("QUAD_DEC_BND = " + Renderer.QUAD_DEC_BND); + + logInfo("USE_SUBDIVIDE_QUAD = " + RendererNoAA.USE_SUBDIVIDE_QUAD); + logInfo("QUAD_ERR_SUBPIX = " + RendererNoAA.QUAD_ERR_SUBPIX); + + logInfo("INITIAL_EDGES_CAPACITY = " + + MarlinConst.INITIAL_EDGES_CAPACITY); + logInfo("INITIAL_CROSSING_COUNT = " + + Renderer.INITIAL_CROSSING_COUNT); + + logInfo("==========================================================" + + "====================="); + } + + /** + * Get the DRendererContext instance dedicated to the current thread + * @return DRendererContext instance + */ + @SuppressWarnings({"unchecked"}) + public static DRendererContext getRendererContext() { + final DRendererContext rdrCtx = RDR_CTX_PROVIDER.acquire(); + if (DO_MONITORS) { + rdrCtx.stats.mon_pre_getAATileGenerator.start(); + } + return rdrCtx; + } + + /** + * Reset and return the given DRendererContext instance for reuse + * @param rdrCtx DRendererContext instance + */ + public static void returnRendererContext(final DRendererContext rdrCtx) { + rdrCtx.dispose(); + + if (DO_MONITORS) { + rdrCtx.stats.mon_pre_getAATileGenerator.stop(); + } + RDR_CTX_PROVIDER.release(rdrCtx); + } +} --- /dev/null 2016-11-30 21:27:13.355352085 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/DPathConsumer2D.java 2016-11-30 22:48:51.234420405 +0100 @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2011, 2013, 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.marlin; + +public interface DPathConsumer2D { + public void moveTo(double x0, double y0); + public void lineTo(double x1, double y1); + public void quadTo(double xc, double yc, + double x1, double y1); + public void curveTo(double xc0, double yc0, + double xc1, double yc1, + double x1, double y1); + public void closePath(); + public void pathDone(); +} --- /dev/null 2016-11-30 21:27:13.355352085 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/DRenderer.java 2016-11-30 22:48:51.710420442 +0100 @@ -0,0 +1,1688 @@ +/* + * Copyright (c) 2007, 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.marlin; + +import static com.sun.marlin.OffHeapArray.SIZE_INT; +import jdk.internal.misc.Unsafe; + +public final class DRenderer implements DMarlinRenderer, MarlinConst { + + static final boolean DISABLE_RENDER = false; + + private static final int ALL_BUT_LSB = 0xfffffffe; + private static final int ERR_STEP_MAX = 0x7Dffffff; // = 2^31 - 1 + + private static final double POWER_2_TO_32 = 0x1.0p32; + + // use double to make tosubpix methods faster (no int to double conversion) + static final double F_SUBPIXEL_POSITIONS_X + = SUBPIXEL_POSITIONS_X; + static final double F_SUBPIXEL_POSITIONS_Y + = SUBPIXEL_POSITIONS_Y; + static final int SUBPIXEL_MASK_X = SUBPIXEL_POSITIONS_X - 1; + static final int SUBPIXEL_MASK_Y = SUBPIXEL_POSITIONS_Y - 1; + + // 2048 (pixelSize) pixels (height) x 8 subpixels = 64K + static final int INITIAL_BUCKET_ARRAY + = INITIAL_PIXEL_DIM * SUBPIXEL_POSITIONS_Y; + + // crossing capacity = edges count / 4 ~ 1024 + static final int INITIAL_CROSSING_COUNT = INITIAL_EDGES_COUNT >> 2; + + // common to all types of input path segments. + // OFFSET as bytes + // only integer values: + public static final long OFF_CURX_OR = 0; + public static final long OFF_ERROR = OFF_CURX_OR + SIZE_INT; + public static final long OFF_BUMP_X = OFF_ERROR + SIZE_INT; + public static final long OFF_BUMP_ERR = OFF_BUMP_X + SIZE_INT; + public static final long OFF_NEXT = OFF_BUMP_ERR + SIZE_INT; + public static final long OFF_YMAX = OFF_NEXT + SIZE_INT; + + // size of one edge in bytes + public static final int SIZEOF_EDGE_BYTES = (int)(OFF_YMAX + SIZE_INT); + + // curve break into lines + // cubic error in subpixels to decrement step + private static final double CUB_DEC_ERR_SUBPIX + = 1D * (NORM_SUBPIXELS / 8D); // 1 subpixel for typical 8x8 subpixels + // cubic error in subpixels to increment step + private static final double CUB_INC_ERR_SUBPIX + = 0.4D * (NORM_SUBPIXELS / 8D); // 0.4 subpixel for typical 8x8 subpixels + + // cubic bind length to decrement step = 8 * error in subpixels + // multiply by 8 = error scale factor: + public static final double CUB_DEC_BND + = 8D * CUB_DEC_ERR_SUBPIX; + // cubic bind length to increment step = 8 * error in subpixels + public static final double CUB_INC_BND + = 8D * CUB_INC_ERR_SUBPIX; + + // cubic countlg + public static final int CUB_COUNT_LG = 2; + // cubic count = 2^countlg + private static final int CUB_COUNT = 1 << CUB_COUNT_LG; + // cubic count^2 = 4^countlg + private static final int CUB_COUNT_2 = 1 << (2 * CUB_COUNT_LG); + // cubic count^3 = 8^countlg + private static final int CUB_COUNT_3 = 1 << (3 * CUB_COUNT_LG); + // cubic dt = 1 / count + private static final double CUB_INV_COUNT = 1D / CUB_COUNT; + // cubic dt^2 = 1 / count^2 = 1 / 4^countlg + private static final double CUB_INV_COUNT_2 = 1D / CUB_COUNT_2; + // cubic dt^3 = 1 / count^3 = 1 / 8^countlg + private static final double CUB_INV_COUNT_3 = 1D / CUB_COUNT_3; + + // quad break into lines + // quadratic error in subpixels + private static final double QUAD_DEC_ERR_SUBPIX + = 0.5D * (NORM_SUBPIXELS / 8D); // 1 subpixel for typical 8x8 subpixels + + // quadratic bind length to decrement step = 8 * error in subpixels + public static final double QUAD_DEC_BND + = 8D * QUAD_DEC_ERR_SUBPIX; + + public static final boolean USE_SUBDIVIDE_QUAD = false; + public static final int SUBDIVIDE_MAX = 20; + + public static final double QUAD_ERR_SUBPIX = 1D / 16D; + public static final double MAX_FLAT_SQ + = 4D * QUAD_ERR_SUBPIX * QUAD_ERR_SUBPIX; // x4 + +////////////////////////////////////////////////////////////////////////////// +// SCAN LINE +////////////////////////////////////////////////////////////////////////////// + // crossings ie subpixel edge x coordinates + private int[] crossings; + // auxiliary storage for crossings (merge sort) + private int[] aux_crossings; + + // indices into the segment pointer lists. They indicate the "active" + // sublist in the segment lists (the portion of the list that contains + // all the segments that cross the next scan line). + private int edgeCount; + private int[] edgePtrs; + // auxiliary storage for edge pointers (merge sort) + private int[] aux_edgePtrs; + + // max used for both edgePtrs and crossings (stats only) + private int activeEdgeMaxUsed; + + // crossings ref (dirty) + private final IntArrayCache.Reference crossings_ref; + // edgePtrs ref (dirty) + private final IntArrayCache.Reference edgePtrs_ref; + // merge sort initial arrays (large enough to satisfy most usages) (1024) + // aux_crossings ref (dirty) + private final IntArrayCache.Reference aux_crossings_ref; + // aux_edgePtrs ref (dirty) + private final IntArrayCache.Reference aux_edgePtrs_ref; + +////////////////////////////////////////////////////////////////////////////// +// EDGE LIST +////////////////////////////////////////////////////////////////////////////// + private int edgeMinY = Integer.MAX_VALUE; + private int edgeMaxY = Integer.MIN_VALUE; + private double edgeMinX = Double.POSITIVE_INFINITY; + private double edgeMaxX = Double.NEGATIVE_INFINITY; + + // edges [doubles|ints] stored in off-heap memory + private final OffHeapArray edges; + + private int[] edgeBuckets; + private int[] edgeBucketCounts; // 2*newedges + (1 if pruning needed) + // used range for edgeBuckets / edgeBucketCounts + private int buckets_minY; + private int buckets_maxY; + + // edgeBuckets ref (clean) + private final IntArrayCache.Reference edgeBuckets_ref; + // edgeBucketCounts ref (clean) + private final IntArrayCache.Reference edgeBucketCounts_ref; + + boolean useRLE = false; + + // Flattens using adaptive forward differencing. This only carries out + // one iteration of the AFD loop. All it does is update AFD variables (i.e. + // X0, Y0, D*[X|Y], COUNT; not variables used for computing scanline crossings). + private void quadBreakIntoLinesAndAdd(double x0, double y0, + final DCurve c, + final double x2, final double y2) + { + int count = 1; // dt = 1 / count + + // maximum(ddX|Y) = norm(dbx, dby) * dt^2 (= 1) + double maxDD = FloatMath.max(Math.abs(c.dbx), Math.abs(c.dby)); + + final double _DEC_BND = QUAD_DEC_BND; + + while (maxDD >= _DEC_BND) { + // divide step by half: + maxDD /= 4D; // error divided by 2^2 = 4 + + count <<= 1; + if (DO_STATS) { + rdrCtx.stats.stat_rdr_quadBreak_dec.add(count); + } + } + + int nL = 0; // line count + if (count > 1) { + final double icount = 1D / count; // dt + final double icount2 = icount * icount; // dt^2 + + final double ddx = c.dbx * icount2; + final double ddy = c.dby * icount2; + double dx = c.bx * icount2 + c.cx * icount; + double dy = c.by * icount2 + c.cy * icount; + + double x1, y1; + + while (--count > 0) { + x1 = x0 + dx; + dx += ddx; + y1 = y0 + dy; + dy += ddy; + + addLine(x0, y0, x1, y1); + + if (DO_STATS) { nL++; } + x0 = x1; + y0 = y1; + } + } + addLine(x0, y0, x2, y2); + + if (DO_STATS) { + rdrCtx.stats.stat_rdr_quadBreak.add(nL + 1); + } + } + + // x0, y0 and x3,y3 are the endpoints of the curve. We could compute these + // using c.xat(0),c.yat(0) and c.xat(1),c.yat(1), but this might introduce + // numerical errors, and our callers already have the exact values. + // Another alternative would be to pass all the control points, and call + // c.set here, but then too many numbers are passed around. + private void curveBreakIntoLinesAndAdd(double x0, double y0, + final DCurve c, + final double x3, final double y3) + { + int count = CUB_COUNT; + final double icount = CUB_INV_COUNT; // dt + final double icount2 = CUB_INV_COUNT_2; // dt^2 + final double icount3 = CUB_INV_COUNT_3; // dt^3 + + // the dx and dy refer to forward differencing variables, not the last + // coefficients of the "points" polynomial + double dddx, dddy, ddx, ddy, dx, dy; + dddx = 2D * c.dax * icount3; + dddy = 2D * c.day * icount3; + ddx = dddx + c.dbx * icount2; + ddy = dddy + c.dby * icount2; + dx = c.ax * icount3 + c.bx * icount2 + c.cx * icount; + dy = c.ay * icount3 + c.by * icount2 + c.cy * icount; + + // we use x0, y0 to walk the line + double x1 = x0, y1 = y0; + int nL = 0; // line count + + final double _DEC_BND = CUB_DEC_BND; + final double _INC_BND = CUB_INC_BND; + + while (count > 0) { + // divide step by half: + while (Math.abs(ddx) >= _DEC_BND || Math.abs(ddy) >= _DEC_BND) { + dddx /= 8D; + dddy /= 8D; + ddx = ddx/4D - dddx; + ddy = ddy/4D - dddy; + dx = (dx - ddx) / 2D; + dy = (dy - ddy) / 2D; + + count <<= 1; + if (DO_STATS) { + rdrCtx.stats.stat_rdr_curveBreak_dec.add(count); + } + } + + // double step: + // TODO: why use first derivative dX|Y instead of second ddX|Y ? + // both scale changes should use speed or acceleration to have the same metric. + + // can only do this on even "count" values, because we must divide count by 2 + while (count % 2 == 0 + && Math.abs(dx) <= _INC_BND && Math.abs(dy) <= _INC_BND) + { + dx = 2D * dx + ddx; + dy = 2D * dy + ddy; + ddx = 4D * (ddx + dddx); + ddy = 4D * (ddy + dddy); + dddx *= 8D; + dddy *= 8D; + + count >>= 1; + if (DO_STATS) { + rdrCtx.stats.stat_rdr_curveBreak_inc.add(count); + } + } + if (--count > 0) { + x1 += dx; + dx += ddx; + ddx += dddx; + y1 += dy; + dy += ddy; + ddy += dddy; + } else { + x1 = x3; + y1 = y3; + } + + addLine(x0, y0, x1, y1); + + if (DO_STATS) { nL++; } + x0 = x1; + y0 = y1; + } + if (DO_STATS) { + rdrCtx.stats.stat_rdr_curveBreak.add(nL); + } + } + + private void addLine(double x1, double y1, double x2, double y2) { + if (DO_MONITORS) { + rdrCtx.stats.mon_rdr_addLine.start(); + } + if (DO_STATS) { + rdrCtx.stats.stat_rdr_addLine.add(1); + } + int or = 1; // orientation of the line. 1 if y increases, 0 otherwise. + if (y2 < y1) { + or = 0; + double tmp = y2; + y2 = y1; + y1 = tmp; + tmp = x2; + x2 = x1; + x1 = tmp; + } + + // convert subpixel coordinates into pixel positions (int) + + // The index of the pixel that holds the next HPC is at ceil(trueY - 0.5) + // Since y1 and y2 are biased by -0.5 in tosubpixy(), this is simply + // ceil(y1) or ceil(y2) + // upper integer (inclusive) + final int firstCrossing = FloatMath.max(FloatMath.ceil_int(y1), boundsMinY); + + // note: use boundsMaxY (last Y exclusive) to compute correct coverage + // upper integer (exclusive) + final int lastCrossing = FloatMath.min(FloatMath.ceil_int(y2), boundsMaxY); + + /* skip horizontal lines in pixel space and clip edges + out of y range [boundsMinY; boundsMaxY] */ + if (firstCrossing >= lastCrossing) { + if (DO_MONITORS) { + rdrCtx.stats.mon_rdr_addLine.stop(); + } + if (DO_STATS) { + rdrCtx.stats.stat_rdr_addLine_skip.add(1); + } + return; + } + + // edge min/max X/Y are in subpixel space (inclusive) within bounds: + // note: Use integer crossings to ensure consistent range within + // edgeBuckets / edgeBucketCounts arrays in case of NaN values (int = 0) + if (firstCrossing < edgeMinY) { + edgeMinY = firstCrossing; + } + if (lastCrossing > edgeMaxY) { + edgeMaxY = lastCrossing; + } + + // Use double-precision for improved accuracy: + final double x1d = x1; + final double y1d = y1; + final double slope = (x1d - x2) / (y1d - y2); + + if (slope >= 0.0) { // <==> x1 < x2 + if (x1 < edgeMinX) { + edgeMinX = x1; + } + if (x2 > edgeMaxX) { + edgeMaxX = x2; + } + } else { + if (x2 < edgeMinX) { + edgeMinX = x2; + } + if (x1 > edgeMaxX) { + edgeMaxX = x1; + } + } + + // local variables for performance: + final int _SIZEOF_EDGE_BYTES = SIZEOF_EDGE_BYTES; + + final OffHeapArray _edges = edges; + + // get free pointer (ie length in bytes) + final int edgePtr = _edges.used; + + // use substraction to avoid integer overflow: + if (_edges.length - edgePtr < _SIZEOF_EDGE_BYTES) { + // suppose _edges.length > _SIZEOF_EDGE_BYTES + // so doubling size is enough to add needed bytes + // note: throw IOOB if neededSize > 2Gb: + final long edgeNewSize = ArrayCacheConst.getNewLargeSize( + _edges.length, + edgePtr + _SIZEOF_EDGE_BYTES); + + if (DO_STATS) { + rdrCtx.stats.stat_rdr_edges_resizes.add(edgeNewSize); + } + _edges.resize(edgeNewSize); + } + + + final Unsafe _unsafe = OffHeapArray.UNSAFE; + final long SIZE_INT = 4L; + long addr = _edges.address + edgePtr; + + // The x value must be bumped up to its position at the next HPC we will evaluate. + // "firstcrossing" is the (sub)pixel number where the next crossing occurs + // thus, the actual coordinate of the next HPC is "firstcrossing + 0.5" + // so the Y distance we cover is "firstcrossing + 0.5 - trueY". + // Note that since y1 (and y2) are already biased by -0.5 in tosubpixy(), we have + // y1 = trueY - 0.5 + // trueY = y1 + 0.5 + // firstcrossing + 0.5 - trueY = firstcrossing + 0.5 - (y1 + 0.5) + // = firstcrossing - y1 + // The x coordinate at that HPC is then: + // x1_intercept = x1 + (firstcrossing - y1) * slope + // The next VPC is then given by: + // VPC index = ceil(x1_intercept - 0.5), or alternately + // VPC index = floor(x1_intercept - 0.5 + 1 - epsilon) + // epsilon is hard to pin down in doubleing point, but easy in fixed point, so if + // we convert to fixed point then these operations get easier: + // long x1_fixed = x1_intercept * 2^32; (fixed point 32.32 format) + // curx = next VPC = fixed_floor(x1_fixed - 2^31 + 2^32 - 1) + // = fixed_floor(x1_fixed + 2^31 - 1) + // = fixed_floor(x1_fixed + 0x7Dffffff) + // and error = fixed_fract(x1_fixed + 0x7Dffffff) + final double x1_intercept = x1d + (firstCrossing - y1d) * slope; + + // inlined scalb(x1_intercept, 32): + final long x1_fixed_biased = ((long) (POWER_2_TO_32 * x1_intercept)) + + 0x7DffffffL; + // curx: + // last bit corresponds to the orientation + _unsafe.putInt(addr, (((int) (x1_fixed_biased >> 31L)) & ALL_BUT_LSB) | or); + addr += SIZE_INT; + _unsafe.putInt(addr, ((int) x1_fixed_biased) >>> 1); + addr += SIZE_INT; + + // inlined scalb(slope, 32): + final long slope_fixed = (long) (POWER_2_TO_32 * slope); + + // last bit set to 0 to keep orientation: + _unsafe.putInt(addr, (((int) (slope_fixed >> 31L)) & ALL_BUT_LSB)); + addr += SIZE_INT; + _unsafe.putInt(addr, ((int) slope_fixed) >>> 1); + addr += SIZE_INT; + + final int[] _edgeBuckets = edgeBuckets; + final int[] _edgeBucketCounts = edgeBucketCounts; + + final int _boundsMinY = boundsMinY; + + // each bucket is a linked list. this method adds ptr to the + // start of the "bucket"th linked list. + final int bucketIdx = firstCrossing - _boundsMinY; + + // pointer from bucket + _unsafe.putInt(addr, _edgeBuckets[bucketIdx]); + addr += SIZE_INT; + // y max (inclusive) + _unsafe.putInt(addr, lastCrossing); + + // Update buckets: + // directly the edge struct "pointer" + _edgeBuckets[bucketIdx] = edgePtr; + _edgeBucketCounts[bucketIdx] += 2; // 1 << 1 + // last bit means edge end + _edgeBucketCounts[lastCrossing - _boundsMinY] |= 0x1; + + // update free pointer (ie length in bytes) + _edges.used += _SIZEOF_EDGE_BYTES; + + if (DO_MONITORS) { + rdrCtx.stats.mon_rdr_addLine.stop(); + } + } + +// END EDGE LIST +////////////////////////////////////////////////////////////////////////////// + + // Bounds of the drawing region, at subpixel precision. + private int boundsMinX, boundsMinY, boundsMaxX, boundsMaxY; + + // Current winding rule + private int windingRule; + + // Current drawing position, i.e., final point of last segment + private double x0, y0; + + // Position of most recent 'moveTo' command + private double sx0, sy0; + + // per-thread renderer context + final DRendererContext rdrCtx; + // dirty curve + private final DCurve curve; + + // clean alpha array (zero filled) + private int[] alphaLine; + + // alphaLine ref (clean) + private final IntArrayCache.Reference alphaLine_ref; + + private boolean enableBlkFlags = false; + private boolean prevUseBlkFlags = false; + + /* block flags (0|1) */ + private int[] blkFlags; + + // blkFlags ref (clean) + private final IntArrayCache.Reference blkFlags_ref; + + DRenderer(final DRendererContext rdrCtx) { + this.rdrCtx = rdrCtx; + + this.edges = rdrCtx.newOffHeapArray(INITIAL_EDGES_CAPACITY); // 96K + + this.curve = rdrCtx.curve; + + edgeBuckets_ref = rdrCtx.newCleanIntArrayRef(INITIAL_BUCKET_ARRAY); // 64K + edgeBucketCounts_ref = rdrCtx.newCleanIntArrayRef(INITIAL_BUCKET_ARRAY); // 64K + + edgeBuckets = edgeBuckets_ref.initial; + edgeBucketCounts = edgeBucketCounts_ref.initial; + + // 2048 (pixelsize) pixel large + alphaLine_ref = rdrCtx.newCleanIntArrayRef(INITIAL_AA_ARRAY); // 8K + alphaLine = alphaLine_ref.initial; + + crossings_ref = rdrCtx.newDirtyIntArrayRef(INITIAL_CROSSING_COUNT); // 2K + aux_crossings_ref = rdrCtx.newDirtyIntArrayRef(INITIAL_CROSSING_COUNT); // 2K + edgePtrs_ref = rdrCtx.newDirtyIntArrayRef(INITIAL_CROSSING_COUNT); // 2K + aux_edgePtrs_ref = rdrCtx.newDirtyIntArrayRef(INITIAL_CROSSING_COUNT); // 2K + + crossings = crossings_ref.initial; + aux_crossings = aux_crossings_ref.initial; + edgePtrs = edgePtrs_ref.initial; + aux_edgePtrs = aux_edgePtrs_ref.initial; + + blkFlags_ref = rdrCtx.newCleanIntArrayRef(INITIAL_ARRAY); // 1K = 1 tile line + blkFlags = blkFlags_ref.initial; + } + + public DRenderer init(final int pix_boundsX, final int pix_boundsY, + final int pix_boundsWidth, final int pix_boundsHeight, + final int windingRule) + { + this.windingRule = windingRule; + + // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY + this.boundsMinX = pix_boundsX << SUBPIXEL_LG_POSITIONS_X; + this.boundsMaxX = + (pix_boundsX + pix_boundsWidth) << SUBPIXEL_LG_POSITIONS_X; + this.boundsMinY = pix_boundsY << SUBPIXEL_LG_POSITIONS_Y; + this.boundsMaxY = + (pix_boundsY + pix_boundsHeight) << SUBPIXEL_LG_POSITIONS_Y; + + if (DO_LOG_BOUNDS) { + MarlinUtils.logInfo("boundsXY = [" + boundsMinX + " ... " + + boundsMaxX + "[ [" + boundsMinY + " ... " + + boundsMaxY + "["); + } + + // see addLine: ceil(boundsMaxY) => boundsMaxY + 1 + // +1 for edgeBucketCounts + final int edgeBucketsLength = (boundsMaxY - boundsMinY) + 1; + + if (edgeBucketsLength > INITIAL_BUCKET_ARRAY) { + if (DO_STATS) { + rdrCtx.stats.stat_array_renderer_edgeBuckets + .add(edgeBucketsLength); + rdrCtx.stats.stat_array_renderer_edgeBucketCounts + .add(edgeBucketsLength); + } + edgeBuckets = edgeBuckets_ref.getArray(edgeBucketsLength); + edgeBucketCounts = edgeBucketCounts_ref.getArray(edgeBucketsLength); + } + + edgeMinY = Integer.MAX_VALUE; + edgeMaxY = Integer.MIN_VALUE; + edgeMinX = Double.POSITIVE_INFINITY; + edgeMaxX = Double.NEGATIVE_INFINITY; + + // reset used mark: + edgeCount = 0; + activeEdgeMaxUsed = 0; + edges.used = 0; + + // reset bbox: + bboxX0 = 0; + bboxX1 = 0; + + return this; // fluent API + } + + /** + * Disposes this renderer and recycle it clean up before reusing this instance + */ + public void dispose() { + if (DO_STATS) { + rdrCtx.stats.stat_rdr_activeEdges.add(activeEdgeMaxUsed); + rdrCtx.stats.stat_rdr_edges.add(edges.used); + rdrCtx.stats.stat_rdr_edges_count.add(edges.used / SIZEOF_EDGE_BYTES); + rdrCtx.stats.hist_rdr_edges_count.add(edges.used / SIZEOF_EDGE_BYTES); + rdrCtx.stats.totalOffHeap += edges.length; + } + // Return arrays: + crossings = crossings_ref.putArray(crossings); + aux_crossings = aux_crossings_ref.putArray(aux_crossings); + + edgePtrs = edgePtrs_ref.putArray(edgePtrs); + aux_edgePtrs = aux_edgePtrs_ref.putArray(aux_edgePtrs); + + alphaLine = alphaLine_ref.putArray(alphaLine, 0, 0); // already zero filled + blkFlags = blkFlags_ref.putArray(blkFlags, 0, 0); // already zero filled + + if (edgeMinY != Integer.MAX_VALUE) { + // if context is maked as DIRTY: + if (rdrCtx.dirty) { + // may happen if an exception if thrown in the pipeline processing: + // clear completely buckets arrays: + buckets_minY = 0; + buckets_maxY = boundsMaxY - boundsMinY; + } + // clear only used part + edgeBuckets = edgeBuckets_ref.putArray(edgeBuckets, buckets_minY, + buckets_maxY); + edgeBucketCounts = edgeBucketCounts_ref.putArray(edgeBucketCounts, + buckets_minY, + buckets_maxY + 1); + } else { + // unused arrays + edgeBuckets = edgeBuckets_ref.putArray(edgeBuckets, 0, 0); + edgeBucketCounts = edgeBucketCounts_ref.putArray(edgeBucketCounts, 0, 0); + } + + // At last: resize back off-heap edges to initial size + if (edges.length != INITIAL_EDGES_CAPACITY) { + // note: may throw OOME: + edges.resize(INITIAL_EDGES_CAPACITY); + } + if (DO_CLEAN_DIRTY) { + // Force zero-fill dirty arrays: + edges.fill(BYTE_0); + } + if (DO_MONITORS) { + rdrCtx.stats.mon_rdr_endRendering.stop(); + } + } + + private static double tosubpixx(final double pix_x) { + return F_SUBPIXEL_POSITIONS_X * pix_x; + } + + private static double tosubpixy(final double pix_y) { + // shift y by -0.5 for fast ceil(y - 0.5): + return F_SUBPIXEL_POSITIONS_Y * pix_y - 0.5D; + } + + @Override + public void moveTo(double pix_x0, double pix_y0) { + closePath(); + final double sx = tosubpixx(pix_x0); + final double sy = tosubpixy(pix_y0); + this.sx0 = sx; + this.sy0 = sy; + this.x0 = sx; + this.y0 = sy; + } + + @Override + public void lineTo(double pix_x1, double pix_y1) { + final double x1 = tosubpixx(pix_x1); + final double y1 = tosubpixy(pix_y1); + addLine(x0, y0, x1, y1); + x0 = x1; + y0 = y1; + } + + @Override + public void curveTo(double x1, double y1, + double x2, double y2, + double x3, double y3) + { + final double xe = tosubpixx(x3); + final double ye = tosubpixy(y3); + curve.set(x0, y0, tosubpixx(x1), tosubpixy(y1), + tosubpixx(x2), tosubpixy(y2), xe, ye); + curveBreakIntoLinesAndAdd(x0, y0, curve, xe, ye); + x0 = xe; + y0 = ye; + } + + @Override + public void quadTo(double pix_x1, double pix_y1, + double pix_x2, double pix_y2) + { + final double cx1 = tosubpixx(pix_x1); + final double cy1 = tosubpixy(pix_y1); + + final double xe = tosubpixx(pix_x2); + final double ye = tosubpixy(pix_y2); + + if (USE_SUBDIVIDE_QUAD) { + subdivideQuad(0, x0, y0, cx1, cy1, xe, ye); + } else { + curve.set(x0, y0, cx1, cy1, xe, ye); + quadBreakIntoLinesAndAdd(x0, y0, curve, xe, ye); + } + x0 = xe; + y0 = ye; + } + + void subdivideQuad(final int level, + final double x0, final double y0, + final double x1, final double y1, + final double x2, final double y2) + { + if (level < SUBDIVIDE_MAX) { + + /* Test if the curve is flat enough for insertion. */ + + // use Roger Willcocks bezier flatness criterion +// var tolerance:Number = 4*tol*tol; + + final double ux = 2D * x1 - x0 - x2; + + final double uy = 2D * y1 - y0 - y2; + + if (ux * ux + uy * uy > MAX_FLAT_SQ) { +/* + AGG + double dx = x2 - x0; + double dy = y2 - y0; + final double dist = Math.abs( (x1 - x2) * dy - (y1 - y2) * dx); + // TODO: check collinearity ? +*/ + +// if (level == 0 || dist * dist > MAX_FLAT_SQ * (dx * dx + dy * dy)) { +// if (ptSegDistSq(x0, y0, x2, y2, x1, y1) > MAX_FLAT_SQ) { + final double cx01 = (x0 + x1) / 2.0D; + final double cx12 = (x1 + x2) / 2.0D; + final double cx012 = (cx01 + cx12) / 2.0D; + + final double cy01 = (y0 + y1) / 2.0D; + final double cy12 = (y1 + y2) / 2.0D; + final double cy012 = (cy01 + cy12) / 2.0D; + + subdivideQuad(level + 1, x0, y0, cx01, cy01, cx012, cy012); + subdivideQuad(level + 1, cx012, cy012, cx12, cy12, x2, y2); + return; + } + } + + addLine(x0, y0, x2, y2); + } + + public static double ptSegDistSq(double x1, double y1, + double x2, double y2, + double px, double py) + { + // Adjust vectors relative to x1,y1 + // x2,y2 becomes relative vector from x1,y1 to end of segment + x2 -= x1; + y2 -= y1; + // px,py becomes relative vector from x1,y1 to test point + px -= x1; + py -= y1; + double dotprod = px * x2 + py * y2; + double projlenSq; + if (dotprod <= 0D) { + // px,py is on the side of x1,y1 away from x2,y2 + // distance to segment is length of px,py vector + // "length of its (clipped) projection" is now 0.0 + projlenSq = 0D; + } else { + // switch to backwards vectors relative to x2,y2 + // x2,y2 are already the negative of x1,y1=>x2,y2 + // to get px,py to be the negative of px,py=>x2,y2 + // the dot product of two negated vectors is the same + // as the dot product of the two normal vectors + px = x2 - px; + py = y2 - py; + dotprod = px * x2 + py * y2; + if (dotprod <= 0D) { + // px,py is on the side of x2,y2 away from x1,y1 + // distance to segment is length of (backwards) px,py vector + // "length of its (clipped) projection" is now 0.0 + projlenSq = 0D; + } else { + // px,py is between x1,y1 and x2,y2 + // dotprod is the length of the px,py vector + // projected on the x2,y2=>x1,y1 vector times the + // length of the x2,y2=>x1,y1 vector + projlenSq = dotprod * dotprod / (x2 * x2 + y2 * y2); + } + } + // Distance to line is now the length of the relative point + // vector minus the length of its projection onto the line + // (which is zero if the projection falls outside the range + // of the line segment). + double lenSq = px * px + py * py - projlenSq; + if (lenSq < 0D) { + lenSq = 0D; + } + return lenSq; + } + + @Override + public void closePath() { + addLine(x0, y0, sx0, sy0); + x0 = sx0; + y0 = sy0; + } + + @Override + public void pathDone() { + closePath(); + + // call endRendering() to determine the boundaries: + endRendering(); + } + + private void _endRendering(final int ymin, final int ymax, + final MarlinAlphaConsumer ac) + { + if (DISABLE_RENDER) { + return; + } + + // Get X bounds as true pixel boundaries to compute correct pixel coverage: + final int bboxx0 = bbox_spminX; + final int bboxx1 = bbox_spmaxX; + + final boolean windingRuleEvenOdd = (windingRule == WIND_EVEN_ODD); + + // Useful when processing tile line by tile line + final int[] _alpha = alphaLine; + + // local vars (performance): + final OffHeapArray _edges = edges; + final int[] _edgeBuckets = edgeBuckets; + final int[] _edgeBucketCounts = edgeBucketCounts; + + int[] _crossings = this.crossings; + int[] _edgePtrs = this.edgePtrs; + + // merge sort auxiliary storage: + int[] _aux_crossings = this.aux_crossings; + int[] _aux_edgePtrs = this.aux_edgePtrs; + + // copy constants: + final long _OFF_ERROR = OFF_ERROR; + final long _OFF_BUMP_X = OFF_BUMP_X; + final long _OFF_BUMP_ERR = OFF_BUMP_ERR; + + final long _OFF_NEXT = OFF_NEXT; + final long _OFF_YMAX = OFF_YMAX; + + final int _ALL_BUT_LSB = ALL_BUT_LSB; + final int _ERR_STEP_MAX = ERR_STEP_MAX; + + // unsafe I/O: + final Unsafe _unsafe = OffHeapArray.UNSAFE; + final long addr0 = _edges.address; + long addr; + final int _SUBPIXEL_LG_POSITIONS_X = SUBPIXEL_LG_POSITIONS_X; + final int _SUBPIXEL_LG_POSITIONS_Y = SUBPIXEL_LG_POSITIONS_Y; + final int _SUBPIXEL_MASK_X = SUBPIXEL_MASK_X; + final int _SUBPIXEL_MASK_Y = SUBPIXEL_MASK_Y; + final int _SUBPIXEL_POSITIONS_X = SUBPIXEL_POSITIONS_X; + + final int _MIN_VALUE = Integer.MIN_VALUE; + final int _MAX_VALUE = Integer.MAX_VALUE; + + // Now we iterate through the scanlines. We must tell emitRow the coord + // of the first non-transparent pixel, so we must keep accumulators for + // the first and last pixels of the section of the current pixel row + // that we will emit. + // We also need to accumulate pix_bbox, but the iterator does it + // for us. We will just get the values from it once this loop is done + int minX = _MAX_VALUE; + int maxX = _MIN_VALUE; + + int y = ymin; + int bucket = y - boundsMinY; + + int numCrossings = this.edgeCount; + int edgePtrsLen = _edgePtrs.length; + int crossingsLen = _crossings.length; + int _arrayMaxUsed = activeEdgeMaxUsed; + int ptrLen = 0, newCount, ptrEnd; + + int bucketcount, i, j, ecur; + int cross, lastCross; + int x0, x1, tmp, sum, prev, curx, curxo, crorientation, err; + int pix_x, pix_xmaxm1, pix_xmax; + + int low, high, mid, prevNumCrossings; + boolean useBinarySearch; + + final int[] _blkFlags = blkFlags; + final int _BLK_SIZE_LG = BLOCK_SIZE_LG; + final int _BLK_SIZE = BLOCK_SIZE; + + final boolean _enableBlkFlagsHeuristics = ENABLE_BLOCK_FLAGS_HEURISTICS && this.enableBlkFlags; + + // Use block flags if large pixel span and few crossings: + // ie mean(distance between crossings) is high + boolean useBlkFlags = this.prevUseBlkFlags; + + final int stroking = rdrCtx.stroking; + + int lastY = -1; // last emited row + + + // Iteration on scanlines + for (; y < ymax; y++, bucket++) { + // --- from former ScanLineIterator.next() + bucketcount = _edgeBucketCounts[bucket]; + + // marker on previously sorted edges: + prevNumCrossings = numCrossings; + + // bucketCount indicates new edge / edge end: + if (bucketcount != 0) { + if (DO_STATS) { + rdrCtx.stats.stat_rdr_activeEdges_updates.add(numCrossings); + } + + // last bit set to 1 means that edges ends + if ((bucketcount & 0x1) != 0) { + // eviction in active edge list + // cache edges[] address + offset + addr = addr0 + _OFF_YMAX; + + for (i = 0, newCount = 0; i < numCrossings; i++) { + // get the pointer to the edge + ecur = _edgePtrs[i]; + // random access so use unsafe: + if (_unsafe.getInt(addr + ecur) > y) { + _edgePtrs[newCount++] = ecur; + } + } + // update marker on sorted edges minus removed edges: + prevNumCrossings = numCrossings = newCount; + } + + ptrLen = bucketcount >> 1; // number of new edge + + if (ptrLen != 0) { + if (DO_STATS) { + rdrCtx.stats.stat_rdr_activeEdges_adds.add(ptrLen); + if (ptrLen > 10) { + rdrCtx.stats.stat_rdr_activeEdges_adds_high.add(ptrLen); + } + } + ptrEnd = numCrossings + ptrLen; + + if (edgePtrsLen < ptrEnd) { + if (DO_STATS) { + rdrCtx.stats.stat_array_renderer_edgePtrs.add(ptrEnd); + } + this.edgePtrs = _edgePtrs + = edgePtrs_ref.widenArray(_edgePtrs, numCrossings, + ptrEnd); + + edgePtrsLen = _edgePtrs.length; + // Get larger auxiliary storage: + aux_edgePtrs_ref.putArray(_aux_edgePtrs); + + // use ArrayCache.getNewSize() to use the same growing + // factor than widenArray(): + if (DO_STATS) { + rdrCtx.stats.stat_array_renderer_aux_edgePtrs.add(ptrEnd); + } + this.aux_edgePtrs = _aux_edgePtrs + = aux_edgePtrs_ref.getArray( + ArrayCacheConst.getNewSize(numCrossings, ptrEnd) + ); + } + + // cache edges[] address + offset + addr = addr0 + _OFF_NEXT; + + // add new edges to active edge list: + for (ecur = _edgeBuckets[bucket]; + numCrossings < ptrEnd; numCrossings++) + { + // store the pointer to the edge + _edgePtrs[numCrossings] = ecur; + // random access so use unsafe: + ecur = _unsafe.getInt(addr + ecur); + } + + if (crossingsLen < numCrossings) { + // Get larger array: + crossings_ref.putArray(_crossings); + + if (DO_STATS) { + rdrCtx.stats.stat_array_renderer_crossings + .add(numCrossings); + } + this.crossings = _crossings + = crossings_ref.getArray(numCrossings); + + // Get larger auxiliary storage: + aux_crossings_ref.putArray(_aux_crossings); + + if (DO_STATS) { + rdrCtx.stats.stat_array_renderer_aux_crossings + .add(numCrossings); + } + this.aux_crossings = _aux_crossings + = aux_crossings_ref.getArray(numCrossings); + + crossingsLen = _crossings.length; + } + if (DO_STATS) { + // update max used mark + if (numCrossings > _arrayMaxUsed) { + _arrayMaxUsed = numCrossings; + } + } + } // ptrLen != 0 + } // bucketCount != 0 + + + if (numCrossings != 0) { + /* + * thresholds to switch to optimized merge sort + * for newly added edges + final merge pass. + */ + if ((ptrLen < 10) || (numCrossings < 40)) { + if (DO_STATS) { + rdrCtx.stats.hist_rdr_crossings.add(numCrossings); + rdrCtx.stats.hist_rdr_crossings_adds.add(ptrLen); + } + + /* + * threshold to use binary insertion sort instead of + * straight insertion sort (to reduce minimize comparisons). + */ + useBinarySearch = (numCrossings >= 20); + + // if small enough: + lastCross = _MIN_VALUE; + + for (i = 0; i < numCrossings; i++) { + // get the pointer to the edge + ecur = _edgePtrs[i]; + + /* convert subpixel coordinates into pixel + positions (int) for coming scanline */ + /* note: it is faster to always update edges even + if it is removed from AEL for coming or last scanline */ + + // random access so use unsafe: + addr = addr0 + ecur; // ecur + OFF_F_CURX + + // get current crossing: + curx = _unsafe.getInt(addr); + + // update crossing with orientation at last bit: + cross = curx; + + // Increment x using DDA (fixed point): + curx += _unsafe.getInt(addr + _OFF_BUMP_X); + + // Increment error: + err = _unsafe.getInt(addr + _OFF_ERROR) + + _unsafe.getInt(addr + _OFF_BUMP_ERR); + + // Manual carry handling: + // keep sign and carry bit only and ignore last bit (preserve orientation): + _unsafe.putInt(addr, curx - ((err >> 30) & _ALL_BUT_LSB)); + _unsafe.putInt(addr + _OFF_ERROR, (err & _ERR_STEP_MAX)); + + if (DO_STATS) { + rdrCtx.stats.stat_rdr_crossings_updates.add(numCrossings); + } + + // insertion sort of crossings: + if (cross < lastCross) { + if (DO_STATS) { + rdrCtx.stats.stat_rdr_crossings_sorts.add(i); + } + + /* use binary search for newly added edges + in crossings if arrays are large enough */ + if (useBinarySearch && (i >= prevNumCrossings)) { + if (DO_STATS) { + rdrCtx.stats.stat_rdr_crossings_bsearch.add(i); + } + low = 0; + high = i - 1; + + do { + // note: use signed shift (not >>>) for performance + // as indices are small enough to exceed Integer.MAX_VALUE + mid = (low + high) >> 1; + + if (_crossings[mid] < cross) { + low = mid + 1; + } else { + high = mid - 1; + } + } while (low <= high); + + for (j = i - 1; j >= low; j--) { + _crossings[j + 1] = _crossings[j]; + _edgePtrs [j + 1] = _edgePtrs[j]; + } + _crossings[low] = cross; + _edgePtrs [low] = ecur; + + } else { + j = i - 1; + _crossings[i] = _crossings[j]; + _edgePtrs[i] = _edgePtrs[j]; + + while ((--j >= 0) && (_crossings[j] > cross)) { + _crossings[j + 1] = _crossings[j]; + _edgePtrs [j + 1] = _edgePtrs[j]; + } + _crossings[j + 1] = cross; + _edgePtrs [j + 1] = ecur; + } + + } else { + _crossings[i] = lastCross = cross; + } + } + } else { + if (DO_STATS) { + rdrCtx.stats.stat_rdr_crossings_msorts.add(numCrossings); + rdrCtx.stats.hist_rdr_crossings_ratio + .add((1000 * ptrLen) / numCrossings); + rdrCtx.stats.hist_rdr_crossings_msorts.add(numCrossings); + rdrCtx.stats.hist_rdr_crossings_msorts_adds.add(ptrLen); + } + + // Copy sorted data in auxiliary arrays + // and perform insertion sort on almost sorted data + // (ie i < prevNumCrossings): + + lastCross = _MIN_VALUE; + + for (i = 0; i < numCrossings; i++) { + // get the pointer to the edge + ecur = _edgePtrs[i]; + + /* convert subpixel coordinates into pixel + positions (int) for coming scanline */ + /* note: it is faster to always update edges even + if it is removed from AEL for coming or last scanline */ + + // random access so use unsafe: + addr = addr0 + ecur; // ecur + OFF_F_CURX + + // get current crossing: + curx = _unsafe.getInt(addr); + + // update crossing with orientation at last bit: + cross = curx; + + // Increment x using DDA (fixed point): + curx += _unsafe.getInt(addr + _OFF_BUMP_X); + + // Increment error: + err = _unsafe.getInt(addr + _OFF_ERROR) + + _unsafe.getInt(addr + _OFF_BUMP_ERR); + + // Manual carry handling: + // keep sign and carry bit only and ignore last bit (preserve orientation): + _unsafe.putInt(addr, curx - ((err >> 30) & _ALL_BUT_LSB)); + _unsafe.putInt(addr + _OFF_ERROR, (err & _ERR_STEP_MAX)); + + if (DO_STATS) { + rdrCtx.stats.stat_rdr_crossings_updates.add(numCrossings); + } + + if (i >= prevNumCrossings) { + // simply store crossing as edgePtrs is in-place: + // will be copied and sorted efficiently by mergesort later: + _crossings[i] = cross; + + } else if (cross < lastCross) { + if (DO_STATS) { + rdrCtx.stats.stat_rdr_crossings_sorts.add(i); + } + + // (straight) insertion sort of crossings: + j = i - 1; + _aux_crossings[i] = _aux_crossings[j]; + _aux_edgePtrs[i] = _aux_edgePtrs[j]; + + while ((--j >= 0) && (_aux_crossings[j] > cross)) { + _aux_crossings[j + 1] = _aux_crossings[j]; + _aux_edgePtrs [j + 1] = _aux_edgePtrs[j]; + } + _aux_crossings[j + 1] = cross; + _aux_edgePtrs [j + 1] = ecur; + + } else { + // auxiliary storage: + _aux_crossings[i] = lastCross = cross; + _aux_edgePtrs [i] = ecur; + } + } + + // use Mergesort using auxiliary arrays (sort only right part) + MergeSort.mergeSortNoCopy(_crossings, _edgePtrs, + _aux_crossings, _aux_edgePtrs, + numCrossings, prevNumCrossings); + } + + // reset ptrLen + ptrLen = 0; + // --- from former ScanLineIterator.next() + + + /* note: bboxx0 and bboxx1 must be pixel boundaries + to have correct coverage computation */ + + // right shift on crossings to get the x-coordinate: + curxo = _crossings[0]; + x0 = curxo >> 1; + if (x0 < minX) { + minX = x0; // subpixel coordinate + } + + x1 = _crossings[numCrossings - 1] >> 1; + if (x1 > maxX) { + maxX = x1; // subpixel coordinate + } + + + // compute pixel coverages + prev = curx = x0; + // to turn {0, 1} into {-1, 1}, multiply by 2 and subtract 1. + // last bit contains orientation (0 or 1) + crorientation = ((curxo & 0x1) << 1) - 1; + + if (windingRuleEvenOdd) { + sum = crorientation; + + // Even Odd winding rule: take care of mask ie sum(orientations) + for (i = 1; i < numCrossings; i++) { + curxo = _crossings[i]; + curx = curxo >> 1; + // to turn {0, 1} into {-1, 1}, multiply by 2 and subtract 1. + // last bit contains orientation (0 or 1) + crorientation = ((curxo & 0x1) << 1) - 1; + + if ((sum & 0x1) != 0) { + // TODO: perform line clipping on left-right sides + // to avoid such bound checks: + x0 = (prev > bboxx0) ? prev : bboxx0; + + if (curx < bboxx1) { + x1 = curx; + } else { + x1 = bboxx1; + // skip right side (fast exit loop): + i = numCrossings; + } + + if (x0 < x1) { + x0 -= bboxx0; // turn x0, x1 from coords to indices + x1 -= bboxx0; // in the alpha array. + + pix_x = x0 >> _SUBPIXEL_LG_POSITIONS_X; + pix_xmaxm1 = (x1 - 1) >> _SUBPIXEL_LG_POSITIONS_X; + + if (pix_x == pix_xmaxm1) { + // Start and end in same pixel + tmp = (x1 - x0); // number of subpixels + _alpha[pix_x ] += tmp; + _alpha[pix_x + 1] -= tmp; + + if (useBlkFlags) { + // flag used blocks: + _blkFlags[pix_x >> _BLK_SIZE_LG] = 1; + _blkFlags[(pix_x + 1) >> _BLK_SIZE_LG] = 1; + } + } else { + tmp = (x0 & _SUBPIXEL_MASK_X); + _alpha[pix_x ] + += (_SUBPIXEL_POSITIONS_X - tmp); + _alpha[pix_x + 1] + += tmp; + + pix_xmax = x1 >> _SUBPIXEL_LG_POSITIONS_X; + + tmp = (x1 & _SUBPIXEL_MASK_X); + _alpha[pix_xmax ] + -= (_SUBPIXEL_POSITIONS_X - tmp); + _alpha[pix_xmax + 1] + -= tmp; + + if (useBlkFlags) { + // flag used blocks: + _blkFlags[ pix_x >> _BLK_SIZE_LG] = 1; + _blkFlags[(pix_x + 1) >> _BLK_SIZE_LG] = 1; + _blkFlags[pix_xmax >> _BLK_SIZE_LG] = 1; + _blkFlags[(pix_xmax + 1) >> _BLK_SIZE_LG] = 1; + } + } + } + } + + sum += crorientation; + prev = curx; + } + } else { + // Non-zero winding rule: optimize that case (default) + // and avoid processing intermediate crossings + for (i = 1, sum = 0;; i++) { + sum += crorientation; + + if (sum != 0) { + // prev = min(curx) + if (prev > curx) { + prev = curx; + } + } else { + // TODO: perform line clipping on left-right sides + // to avoid such bound checks: + x0 = (prev > bboxx0) ? prev : bboxx0; + + if (curx < bboxx1) { + x1 = curx; + } else { + x1 = bboxx1; + // skip right side (fast exit loop): + i = numCrossings; + } + + if (x0 < x1) { + x0 -= bboxx0; // turn x0, x1 from coords to indices + x1 -= bboxx0; // in the alpha array. + + pix_x = x0 >> _SUBPIXEL_LG_POSITIONS_X; + pix_xmaxm1 = (x1 - 1) >> _SUBPIXEL_LG_POSITIONS_X; + + if (pix_x == pix_xmaxm1) { + // Start and end in same pixel + tmp = (x1 - x0); // number of subpixels + _alpha[pix_x ] += tmp; + _alpha[pix_x + 1] -= tmp; + + if (useBlkFlags) { + // flag used blocks: + _blkFlags[pix_x >> _BLK_SIZE_LG] = 1; + _blkFlags[(pix_x + 1) >> _BLK_SIZE_LG] = 1; + } + } else { + tmp = (x0 & _SUBPIXEL_MASK_X); + _alpha[pix_x ] + += (_SUBPIXEL_POSITIONS_X - tmp); + _alpha[pix_x + 1] + += tmp; + + pix_xmax = x1 >> _SUBPIXEL_LG_POSITIONS_X; + + tmp = (x1 & _SUBPIXEL_MASK_X); + _alpha[pix_xmax ] + -= (_SUBPIXEL_POSITIONS_X - tmp); + _alpha[pix_xmax + 1] + -= tmp; + + if (useBlkFlags) { + // flag used blocks: + _blkFlags[ pix_x >> _BLK_SIZE_LG] = 1; + _blkFlags[(pix_x + 1) >> _BLK_SIZE_LG] = 1; + _blkFlags[pix_xmax >> _BLK_SIZE_LG] = 1; + _blkFlags[(pix_xmax + 1) >> _BLK_SIZE_LG] = 1; + } + } + } + prev = _MAX_VALUE; + } + + if (i == numCrossings) { + break; + } + + curxo = _crossings[i]; + curx = curxo >> 1; + // to turn {0, 1} into {-1, 1}, multiply by 2 and subtract 1. + // last bit contains orientation (0 or 1) + crorientation = ((curxo & 0x1) << 1) - 1; + } + } + } // numCrossings > 0 + + // even if this last row had no crossings, alpha will be zeroed + // from the last emitRow call. But this doesn't matter because + // maxX < minX, so no row will be emitted to the AlphaConsumer. + if ((y & _SUBPIXEL_MASK_Y) == _SUBPIXEL_MASK_Y) { + lastY = y >> _SUBPIXEL_LG_POSITIONS_Y; + + // convert subpixel to pixel coordinate within boundaries: + minX = FloatMath.max(minX, bboxx0) >> _SUBPIXEL_LG_POSITIONS_X; + maxX = FloatMath.min(maxX, bboxx1) >> _SUBPIXEL_LG_POSITIONS_X; + + if (maxX >= minX) { + // note: alpha array will be zeroed by copyAARow() + // +1 because alpha [pix_minX; pix_maxX[ + // fix range [x0; x1[ + // note: if x1=bboxx1, then alpha is written up to bboxx1+1 + // inclusive: alpha[bboxx1] ignored, alpha[bboxx1+1] == 0 + // (normally so never cleared below) + copyAARow(_alpha, lastY, minX, maxX + 1, useBlkFlags, ac); + + // speculative for next pixel row (scanline coherence): + if (_enableBlkFlagsHeuristics) { + // Use block flags if large pixel span and few crossings: + // ie mean(distance between crossings) is larger than + // 1 block size; + + // fast check width: + maxX -= minX; + + // if stroking: numCrossings /= 2 + // => shift numCrossings by 1 + // condition = (width / (numCrossings - 1)) > blockSize + useBlkFlags = (maxX > _BLK_SIZE) && (maxX > + (((numCrossings >> stroking) - 1) << _BLK_SIZE_LG)); + + if (DO_STATS) { + tmp = FloatMath.max(1, + ((numCrossings >> stroking) - 1)); + rdrCtx.stats.hist_tile_generator_encoding_dist + .add(maxX / tmp); + } + } + } else { + ac.clearAlphas(lastY); + } + minX = _MAX_VALUE; + maxX = _MIN_VALUE; + } + } // scan line iterator + + // Emit final row + y--; + y >>= _SUBPIXEL_LG_POSITIONS_Y; + + // convert subpixel to pixel coordinate within boundaries: + minX = FloatMath.max(minX, bboxx0) >> _SUBPIXEL_LG_POSITIONS_X; + maxX = FloatMath.min(maxX, bboxx1) >> _SUBPIXEL_LG_POSITIONS_X; + + if (maxX >= minX) { + // note: alpha array will be zeroed by copyAARow() + // +1 because alpha [pix_minX; pix_maxX[ + // fix range [x0; x1[ + // note: if x1=bboxx1, then alpha is written up to bboxx1+1 + // inclusive: alpha[bboxx1] ignored then cleared and + // alpha[bboxx1+1] == 0 (normally so never cleared after) + copyAARow(_alpha, y, minX, maxX + 1, useBlkFlags, ac); + } else if (y != lastY) { + ac.clearAlphas(y); + } + + // update member: + edgeCount = numCrossings; + prevUseBlkFlags = useBlkFlags; + + if (DO_STATS) { + // update max used mark + activeEdgeMaxUsed = _arrayMaxUsed; + } + } + + void endRendering() { + if (DO_MONITORS) { + rdrCtx.stats.mon_rdr_endRendering.start(); + } + if (edgeMinY == Integer.MAX_VALUE) { + return; // undefined edges bounds + } + + final int _boundsMinY = boundsMinY; + final int _boundsMaxY = boundsMaxY; + + // bounds as inclusive intervals + final int spminX = FloatMath.max(FloatMath.ceil_int(edgeMinX - 0.5D), boundsMinX); + final int spmaxX = FloatMath.min(FloatMath.ceil_int(edgeMaxX - 0.5D), boundsMaxX - 1); + + // edge Min/Max Y are already rounded to subpixels within bounds: + final int spminY = edgeMinY; + final int spmaxY; + int maxY = edgeMaxY; + + if (maxY <= _boundsMaxY - 1) { + spmaxY = maxY; + } else { + spmaxY = _boundsMaxY - 1; + maxY = _boundsMaxY; + } + buckets_minY = spminY - _boundsMinY; + buckets_maxY = maxY - _boundsMinY; + + if (DO_LOG_BOUNDS) { + MarlinUtils.logInfo("edgesXY = [" + edgeMinX + " ... " + edgeMaxX + + "][" + edgeMinY + " ... " + edgeMaxY + "]"); + MarlinUtils.logInfo("spXY = [" + spminX + " ... " + spmaxX + + "][" + spminY + " ... " + spmaxY + "]"); + } + + // test clipping for shapes out of bounds + if ((spminX > spmaxX) || (spminY > spmaxY)) { + return; + } + + // half open intervals + // inclusive: + final int pminX = spminX >> SUBPIXEL_LG_POSITIONS_X; + // exclusive: + final int pmaxX = (spmaxX + SUBPIXEL_MASK_X) >> SUBPIXEL_LG_POSITIONS_X; + // inclusive: + final int pminY = spminY >> SUBPIXEL_LG_POSITIONS_Y; + // exclusive: + final int pmaxY = (spmaxY + SUBPIXEL_MASK_Y) >> SUBPIXEL_LG_POSITIONS_Y; + + // store BBox to answer ptg.getBBox(): + initConsumer(pminX, pminY, pmaxX, pmaxY); + + // Heuristics for using block flags: + if (ENABLE_BLOCK_FLAGS) { + enableBlkFlags = this.useRLE; + prevUseBlkFlags = enableBlkFlags && !ENABLE_BLOCK_FLAGS_HEURISTICS; + + if (enableBlkFlags) { + // ensure blockFlags array is large enough: + // note: +2 to ensure enough space left at end + final int blkLen = ((pmaxX - pminX) >> BLOCK_SIZE_LG) + 2; + if (blkLen > INITIAL_ARRAY) { + blkFlags = blkFlags_ref.getArray(blkLen); + } + } + } + + // memorize the rendering bounding box: + /* note: bbox_spminX and bbox_spmaxX must be pixel boundaries + to have correct coverage computation */ + // inclusive: + bbox_spminX = pminX << SUBPIXEL_LG_POSITIONS_X; + // exclusive: + bbox_spmaxX = pmaxX << SUBPIXEL_LG_POSITIONS_X; + // inclusive: + bbox_spminY = spminY; + // exclusive: + bbox_spmaxY = FloatMath.min(spmaxY + 1, pmaxY << SUBPIXEL_LG_POSITIONS_Y); + + if (DO_LOG_BOUNDS) { + MarlinUtils.logInfo("pXY = [" + pminX + " ... " + pmaxX + + "[ [" + pminY + " ... " + pmaxY + "["); + MarlinUtils.logInfo("bbox_spXY = [" + bbox_spminX + " ... " + + bbox_spmaxX + "[ [" + bbox_spminY + " ... " + + bbox_spmaxY + "["); + } + + // Prepare alpha line: + // add 2 to better deal with the last pixel in a pixel row. + final int width = (pmaxX - pminX) + 2; + + // Useful when processing tile line by tile line + if (width > INITIAL_AA_ARRAY) { + if (DO_STATS) { + rdrCtx.stats.stat_array_renderer_alphaline.add(width); + } + alphaLine = alphaLine_ref.getArray(width); + } + } + + void initConsumer(int minx, int miny, int maxx, int maxy) + { + // assert maxy >= miny && maxx >= minx; + bboxX0 = minx; + bboxX1 = maxx; + bboxY0 = miny; + bboxY1 = maxy; + + final int width = (maxx - minx); + + if (FORCE_NO_RLE) { + useRLE = false; + } else if (FORCE_RLE) { + useRLE = true; + } else { + // heuristics: use both bbox area and complexity + // ie number of primitives: + + // fast check min width: + if (width <= RLE_MIN_WIDTH) { + useRLE = false; + } else { + useRLE = true; + } + } + } + + private int bbox_spminX, bbox_spmaxX, bbox_spminY, bbox_spmaxY; + + public void produceAlphas(final MarlinAlphaConsumer ac) { + ac.setMaxAlpha(MAX_AA_ALPHA); + + if (enableBlkFlags && !ac.supportBlockFlags()) { + // consumer does not support block flag optimization: + enableBlkFlags = false; + prevUseBlkFlags = false; + } + + if (DO_MONITORS) { + rdrCtx.stats.mon_rdr_endRendering_Y.start(); + } + + // Process all scan lines: + _endRendering(bbox_spminY, bbox_spmaxY, ac); + + if (DO_MONITORS) { + rdrCtx.stats.mon_rdr_endRendering_Y.stop(); + } + } + + void copyAARow(final int[] alphaRow, + final int pix_y, final int pix_from, final int pix_to, + final boolean useBlockFlags, + final MarlinAlphaConsumer ac) + { + if (DO_MONITORS) { + rdrCtx.stats.mon_rdr_copyAARow.start(); + } + if (DO_STATS) { + rdrCtx.stats.stat_cache_rowAA.add(pix_to - pix_from); + } + + if (useBlockFlags) { + if (DO_STATS) { + rdrCtx.stats.hist_tile_generator_encoding.add(1); + } + ac.setAndClearRelativeAlphas(blkFlags, alphaRow, pix_y, pix_from, pix_to); + } else { + if (DO_STATS) { + rdrCtx.stats.hist_tile_generator_encoding.add(0); + } + ac.setAndClearRelativeAlphas(alphaRow, pix_y, pix_from, pix_to); + } + if (DO_MONITORS) { + rdrCtx.stats.mon_rdr_copyAARow.stop(); + } + } + + // output pixel bounding box: + int bboxX0, bboxX1, bboxY0, bboxY1; + + @Override + public int getOutpixMinX() { + return bboxX0; + } + + @Override + public int getOutpixMaxX() { + return bboxX1; + } + + @Override + public int getOutpixMinY() { + return bboxY0; + } + + @Override + public int getOutpixMaxY() { + return bboxY1; + } +} --- /dev/null 2016-11-30 21:27:13.355352085 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/DRendererContext.java 2016-11-30 22:48:52.190420480 +0100 @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2015, 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.marlin; + +import java.util.concurrent.atomic.AtomicInteger; +import com.sun.util.reentrant.ReentrantContext; +import com.sun.javafx.geom.Rectangle; +import com.sun.marlin.ArrayCacheConst.CacheStats; + +/** + * This class is a renderer context dedicated to a single thread + */ +public final class DRendererContext extends ReentrantContext implements MarlinConst { + + // RendererContext creation counter + private static final AtomicInteger CTX_COUNT = new AtomicInteger(1); + + /** + * Create a new renderer context + * + * @return new RendererContext instance + */ + public static DRendererContext createContext() { + return new DRendererContext("ctx" + + Integer.toString(CTX_COUNT.getAndIncrement())); + } + + // Smallest object used as Cleaner's parent reference + private final Object cleanerObj; + // dirty flag indicating an exception occured during pipeline in pathTo() + public boolean dirty = false; + // shared data + public final float[] float6 = new float[6]; + // shared curve (dirty) (Renderer / Stroker) + final DCurve curve = new DCurve(); + // MarlinRenderingEngine.TransformingPathConsumer2D + public final DTransformingPathConsumer2D transformerPC2D; + public final DRenderer renderer; + private DRendererNoAA rendererNoAA = null; + public final DStroker stroker; + // Simplifies out collinear lines + public final DCollinearSimplifier simplifier = new DCollinearSimplifier(); + public final DDasher dasher; + // flag indicating the shape is stroked (1) or filled (0) + int stroking = 0; + +// MarlinFX specific: + // dirty bbox rectangle + public final Rectangle clip = new Rectangle(); + // dirty MaskMarlinAlphaConsumer + public MaskMarlinAlphaConsumer consumer = null; + + // Array caches: + /* clean int[] cache (zero-filled) = 5 refs */ + private final IntArrayCache cleanIntCache = new IntArrayCache(true, 5); + /* dirty int[] cache = 4 refs */ + private final IntArrayCache dirtyIntCache = new IntArrayCache(false, 4); + /* dirty double[] cache = 3 refs */ + private final DoubleArrayCache dirtyDoubleCache = new DoubleArrayCache(false, 3); + /* dirty byte[] cache = 1 ref */ + private final ByteArrayCache dirtyByteCache = new ByteArrayCache(false, 1); + + // RendererContext statistics + final RendererStats stats; + + /** + * Constructor + * + * @param name context name (debugging) + */ + DRendererContext(final String name) { + if (LOG_CREATE_CONTEXT) { + MarlinUtils.logInfo("new RendererContext = " + name); + } + this.cleanerObj = new Object(); + + // create first stats (needed by newOffHeapArray): + if (DO_STATS || DO_MONITORS) { + stats = RendererStats.createInstance(cleanerObj, name); + // push cache stats: + stats.cacheStats = new CacheStats[] { cleanIntCache.stats, + dirtyIntCache.stats, dirtyDoubleCache.stats, dirtyByteCache.stats + }; + } else { + stats = null; + } + + // MarlinRenderingEngine.TransformingPathConsumer2D + transformerPC2D = new DTransformingPathConsumer2D(); + + // Renderer: + renderer = new DRenderer(this); + + stroker = new DStroker(this); + dasher = new DDasher(this); + } + + /** + * Disposes this renderer context: + * clean up before reusing this context + */ + public void dispose() { + if (DO_STATS) { + if (stats.totalOffHeap > stats.totalOffHeapMax) { + stats.totalOffHeapMax = stats.totalOffHeap; + } + stats.totalOffHeap = 0L; + } + stroking = 0; + // if context is maked as DIRTY: + if (dirty) { + // may happen if an exception if thrown in the pipeline processing: + // force cleanup of all possible pipelined blocks (except Renderer): + + // Dasher: + this.dasher.dispose(); + // Stroker: + this.stroker.dispose(); + + // mark context as CLEAN: + dirty = false; + } + } + + public DRendererNoAA getRendererNoAA() { + if (rendererNoAA == null) { + rendererNoAA = new DRendererNoAA(this); + } + return rendererNoAA; + } + + OffHeapArray newOffHeapArray(final long initialSize) { + if (DO_STATS) { + stats.totalOffHeapInitial += initialSize; + } + return new OffHeapArray(cleanerObj, initialSize); + } + + IntArrayCache.Reference newCleanIntArrayRef(final int initialSize) { + return cleanIntCache.createRef(initialSize); + } + + IntArrayCache.Reference newDirtyIntArrayRef(final int initialSize) { + return dirtyIntCache.createRef(initialSize); + } + + DoubleArrayCache.Reference newDirtyDoubleArrayRef(final int initialSize) { + return dirtyDoubleCache.createRef(initialSize); + } + + ByteArrayCache.Reference newDirtyByteArrayRef(final int initialSize) { + return dirtyByteCache.createRef(initialSize); + } +} --- /dev/null 2016-11-30 21:27:13.355352085 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/DRendererNoAA.java 2016-11-30 22:48:52.662420517 +0100 @@ -0,0 +1,1612 @@ +/* + * Copyright (c) 2007, 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.marlin; + +import static com.sun.marlin.OffHeapArray.SIZE_INT; +import jdk.internal.misc.Unsafe; + +public final class DRendererNoAA implements DMarlinRenderer, MarlinConst { + + static final boolean DISABLE_RENDER = false; + + private static final int ALL_BUT_LSB = 0xfffffffe; + private static final int ERR_STEP_MAX = 0x7Dffffff; // = 2^31 - 1 + + private static final double POWER_2_TO_32 = 0x1.0p32; + + // 2048 (pixelSize) pixels (height) x 8 subpixels = 64K + static final int INITIAL_BUCKET_ARRAY = INITIAL_PIXEL_DIM; + + // crossing capacity = edges count / 4 ~ 1024 + static final int INITIAL_CROSSING_COUNT = INITIAL_EDGES_COUNT >> 2; + + // common to all types of input path segments. + // OFFSET as bytes + // only integer values: + public static final long OFF_CURX_OR = 0; + public static final long OFF_ERROR = OFF_CURX_OR + SIZE_INT; + public static final long OFF_BUMP_X = OFF_ERROR + SIZE_INT; + public static final long OFF_BUMP_ERR = OFF_BUMP_X + SIZE_INT; + public static final long OFF_NEXT = OFF_BUMP_ERR + SIZE_INT; + public static final long OFF_YMAX = OFF_NEXT + SIZE_INT; + + // size of one edge in bytes + public static final int SIZEOF_EDGE_BYTES = (int)(OFF_YMAX + SIZE_INT); + + // curve break into lines + // cubic error in subpixels to decrement step + private static final double CUB_DEC_ERR_SUBPIX + = 1D * (1D / 8D); // 1 pixel for typical 1x1 subpixels + // cubic error in subpixels to increment step + private static final double CUB_INC_ERR_SUBPIX + = 0.4D * (1D / 8D); // 0.4 pixel for typical 1x1 subpixels + + // cubic bind length to decrement step = 8 * error in subpixels + // multiply by 8 = error scale factor: + public static final double CUB_DEC_BND + = 8D * CUB_DEC_ERR_SUBPIX; + // cubic bind length to increment step = 8 * error in subpixels + public static final double CUB_INC_BND + = 8D * CUB_INC_ERR_SUBPIX; + + // cubic countlg + public static final int CUB_COUNT_LG = 2; + // cubic count = 2^countlg + private static final int CUB_COUNT = 1 << CUB_COUNT_LG; + // cubic count^2 = 4^countlg + private static final int CUB_COUNT_2 = 1 << (2 * CUB_COUNT_LG); + // cubic count^3 = 8^countlg + private static final int CUB_COUNT_3 = 1 << (3 * CUB_COUNT_LG); + // cubic dt = 1 / count + private static final double CUB_INV_COUNT = 1D / CUB_COUNT; + // cubic dt^2 = 1 / count^2 = 1 / 4^countlg + private static final double CUB_INV_COUNT_2 = 1D / CUB_COUNT_2; + // cubic dt^3 = 1 / count^3 = 1 / 8^countlg + private static final double CUB_INV_COUNT_3 = 1D / CUB_COUNT_3; + + // quad break into lines + // quadratic error in subpixels + private static final double QUAD_DEC_ERR_SUBPIX + = 0.5D * (1D / 8D); // 1 pixel for typical 1x1 subpixels + + // quadratic bind length to decrement step = 8 * error in subpixels + public static final double QUAD_DEC_BND + = 8D * QUAD_DEC_ERR_SUBPIX; + + public static final boolean USE_SUBDIVIDE_QUAD = false; + public static final int SUBDIVIDE_MAX = 30; + + public static final double QUAD_ERR_SUBPIX = 1D / 16D; + public static final double MAX_FLAT_SQ + = 4D * QUAD_ERR_SUBPIX * QUAD_ERR_SUBPIX; // x4 + +////////////////////////////////////////////////////////////////////////////// +// SCAN LINE +////////////////////////////////////////////////////////////////////////////// + // crossings ie subpixel edge x coordinates + private int[] crossings; + // auxiliary storage for crossings (merge sort) + private int[] aux_crossings; + + // indices into the segment pointer lists. They indicate the "active" + // sublist in the segment lists (the portion of the list that contains + // all the segments that cross the next scan line). + private int edgeCount; + private int[] edgePtrs; + // auxiliary storage for edge pointers (merge sort) + private int[] aux_edgePtrs; + + // max used for both edgePtrs and crossings (stats only) + private int activeEdgeMaxUsed; + + // crossings ref (dirty) + private final IntArrayCache.Reference crossings_ref; + // edgePtrs ref (dirty) + private final IntArrayCache.Reference edgePtrs_ref; + // merge sort initial arrays (large enough to satisfy most usages) (1024) + // aux_crossings ref (dirty) + private final IntArrayCache.Reference aux_crossings_ref; + // aux_edgePtrs ref (dirty) + private final IntArrayCache.Reference aux_edgePtrs_ref; + +////////////////////////////////////////////////////////////////////////////// +// EDGE LIST +////////////////////////////////////////////////////////////////////////////// + private int edgeMinY = Integer.MAX_VALUE; + private int edgeMaxY = Integer.MIN_VALUE; + private double edgeMinX = Double.POSITIVE_INFINITY; + private double edgeMaxX = Double.NEGATIVE_INFINITY; + + // edges [doubles|ints] stored in off-heap memory + private final OffHeapArray edges; + + private int[] edgeBuckets; + private int[] edgeBucketCounts; // 2*newedges + (1 if pruning needed) + // used range for edgeBuckets / edgeBucketCounts + private int buckets_minY; + private int buckets_maxY; + + // edgeBuckets ref (clean) + private final IntArrayCache.Reference edgeBuckets_ref; + // edgeBucketCounts ref (clean) + private final IntArrayCache.Reference edgeBucketCounts_ref; + + boolean useRLE = false; + + // Flattens using adaptive forward differencing. This only carries out + // one iteration of the AFD loop. All it does is update AFD variables (i.e. + // X0, Y0, D*[X|Y], COUNT; not variables used for computing scanline crossings). + private void quadBreakIntoLinesAndAdd(double x0, double y0, + final DCurve c, + final double x2, final double y2) + { + int count = 1; // dt = 1 / count + + // maximum(ddX|Y) = norm(dbx, dby) * dt^2 (= 1) + double maxDD = FloatMath.max(Math.abs(c.dbx), Math.abs(c.dby)); + + final double _DEC_BND = QUAD_DEC_BND; + + while (maxDD >= _DEC_BND) { + // divide step by half: + maxDD /= 4D; // error divided by 2^2 = 4 + + count <<= 1; + if (DO_STATS) { + rdrCtx.stats.stat_rdr_quadBreak_dec.add(count); + } + } + + int nL = 0; // line count + if (count > 1) { + final double icount = 1D / count; // dt + final double icount2 = icount * icount; // dt^2 + + final double ddx = c.dbx * icount2; + final double ddy = c.dby * icount2; + double dx = c.bx * icount2 + c.cx * icount; + double dy = c.by * icount2 + c.cy * icount; + + double x1, y1; + + while (--count > 0) { + x1 = x0 + dx; + dx += ddx; + y1 = y0 + dy; + dy += ddy; + + addLine(x0, y0, x1, y1); + + if (DO_STATS) { nL++; } + x0 = x1; + y0 = y1; + } + } + addLine(x0, y0, x2, y2); + + if (DO_STATS) { + rdrCtx.stats.stat_rdr_quadBreak.add(nL + 1); + } + } + + // x0, y0 and x3,y3 are the endpoints of the curve. We could compute these + // using c.xat(0),c.yat(0) and c.xat(1),c.yat(1), but this might introduce + // numerical errors, and our callers already have the exact values. + // Another alternative would be to pass all the control points, and call + // c.set here, but then too many numbers are passed around. + private void curveBreakIntoLinesAndAdd(double x0, double y0, + final DCurve c, + final double x3, final double y3) + { + int count = CUB_COUNT; + final double icount = CUB_INV_COUNT; // dt + final double icount2 = CUB_INV_COUNT_2; // dt^2 + final double icount3 = CUB_INV_COUNT_3; // dt^3 + + // the dx and dy refer to forward differencing variables, not the last + // coefficients of the "points" polynomial + double dddx, dddy, ddx, ddy, dx, dy; + dddx = 2D * c.dax * icount3; + dddy = 2D * c.day * icount3; + ddx = dddx + c.dbx * icount2; + ddy = dddy + c.dby * icount2; + dx = c.ax * icount3 + c.bx * icount2 + c.cx * icount; + dy = c.ay * icount3 + c.by * icount2 + c.cy * icount; + + // we use x0, y0 to walk the line + double x1 = x0, y1 = y0; + int nL = 0; // line count + + final double _DEC_BND = CUB_DEC_BND; + final double _INC_BND = CUB_INC_BND; + + while (count > 0) { + // divide step by half: + while (Math.abs(ddx) >= _DEC_BND || Math.abs(ddy) >= _DEC_BND) { + dddx /= 8D; + dddy /= 8D; + ddx = ddx/4D - dddx; + ddy = ddy/4D - dddy; + dx = (dx - ddx) / 2D; + dy = (dy - ddy) / 2D; + + count <<= 1; + if (DO_STATS) { + rdrCtx.stats.stat_rdr_curveBreak_dec.add(count); + } + } + + // double step: + // TODO: why use first derivative dX|Y instead of second ddX|Y ? + // both scale changes should use speed or acceleration to have the same metric. + + // can only do this on even "count" values, because we must divide count by 2 + while (count % 2 == 0 + && Math.abs(dx) <= _INC_BND && Math.abs(dy) <= _INC_BND) + { + dx = 2D * dx + ddx; + dy = 2D * dy + ddy; + ddx = 4D * (ddx + dddx); + ddy = 4D * (ddy + dddy); + dddx *= 8D; + dddy *= 8D; + + count >>= 1; + if (DO_STATS) { + rdrCtx.stats.stat_rdr_curveBreak_inc.add(count); + } + } + if (--count > 0) { + x1 += dx; + dx += ddx; + ddx += dddx; + y1 += dy; + dy += ddy; + ddy += dddy; + } else { + x1 = x3; + y1 = y3; + } + + addLine(x0, y0, x1, y1); + + if (DO_STATS) { nL++; } + x0 = x1; + y0 = y1; + } + if (DO_STATS) { + rdrCtx.stats.stat_rdr_curveBreak.add(nL); + } + } + + private void addLine(double x1, double y1, double x2, double y2) { + if (DO_MONITORS) { + rdrCtx.stats.mon_rdr_addLine.start(); + } + if (DO_STATS) { + rdrCtx.stats.stat_rdr_addLine.add(1); + } + int or = 1; // orientation of the line. 1 if y increases, 0 otherwise. + if (y2 < y1) { + or = 0; + double tmp = y2; + y2 = y1; + y1 = tmp; + tmp = x2; + x2 = x1; + x1 = tmp; + } + + // convert subpixel coordinates into pixel positions (int) + + // The index of the pixel that holds the next HPC is at ceil(trueY - 0.5) + // Since y1 and y2 are biased by -0.5 in tosubpixy(), this is simply + // ceil(y1) or ceil(y2) + // upper integer (inclusive) + final int firstCrossing = FloatMath.max(FloatMath.ceil_int(y1), boundsMinY); + + // note: use boundsMaxY (last Y exclusive) to compute correct coverage + // upper integer (exclusive) + final int lastCrossing = FloatMath.min(FloatMath.ceil_int(y2), boundsMaxY); + + /* skip horizontal lines in pixel space and clip edges + out of y range [boundsMinY; boundsMaxY] */ + if (firstCrossing >= lastCrossing) { + if (DO_MONITORS) { + rdrCtx.stats.mon_rdr_addLine.stop(); + } + if (DO_STATS) { + rdrCtx.stats.stat_rdr_addLine_skip.add(1); + } + return; + } + + // edge min/max X/Y are in subpixel space (inclusive) within bounds: + // note: Use integer crossings to ensure consistent range within + // edgeBuckets / edgeBucketCounts arrays in case of NaN values (int = 0) + if (firstCrossing < edgeMinY) { + edgeMinY = firstCrossing; + } + if (lastCrossing > edgeMaxY) { + edgeMaxY = lastCrossing; + } + + // Use double-precision for improved accuracy: + final double x1d = x1; + final double y1d = y1; + final double slope = (x1d - x2) / (y1d - y2); + + if (slope >= 0.0) { // <==> x1 < x2 + if (x1 < edgeMinX) { + edgeMinX = x1; + } + if (x2 > edgeMaxX) { + edgeMaxX = x2; + } + } else { + if (x2 < edgeMinX) { + edgeMinX = x2; + } + if (x1 > edgeMaxX) { + edgeMaxX = x1; + } + } + + // local variables for performance: + final int _SIZEOF_EDGE_BYTES = SIZEOF_EDGE_BYTES; + + final OffHeapArray _edges = edges; + + // get free pointer (ie length in bytes) + final int edgePtr = _edges.used; + + // use substraction to avoid integer overflow: + if (_edges.length - edgePtr < _SIZEOF_EDGE_BYTES) { + // suppose _edges.length > _SIZEOF_EDGE_BYTES + // so doubling size is enough to add needed bytes + // note: throw IOOB if neededSize > 2Gb: + final long edgeNewSize = ArrayCacheConst.getNewLargeSize( + _edges.length, + edgePtr + _SIZEOF_EDGE_BYTES); + + if (DO_STATS) { + rdrCtx.stats.stat_rdr_edges_resizes.add(edgeNewSize); + } + _edges.resize(edgeNewSize); + } + + + final Unsafe _unsafe = OffHeapArray.UNSAFE; + final long SIZE_INT = 4L; + long addr = _edges.address + edgePtr; + + // The x value must be bumped up to its position at the next HPC we will evaluate. + // "firstcrossing" is the (sub)pixel number where the next crossing occurs + // thus, the actual coordinate of the next HPC is "firstcrossing + 0.5" + // so the Y distance we cover is "firstcrossing + 0.5 - trueY". + // Note that since y1 (and y2) are already biased by -0.5 in tosubpixy(), we have + // y1 = trueY - 0.5 + // trueY = y1 + 0.5 + // firstcrossing + 0.5 - trueY = firstcrossing + 0.5 - (y1 + 0.5) + // = firstcrossing - y1 + // The x coordinate at that HPC is then: + // x1_intercept = x1 + (firstcrossing - y1) * slope + // The next VPC is then given by: + // VPC index = ceil(x1_intercept - 0.5), or alternately + // VPC index = floor(x1_intercept - 0.5 + 1 - epsilon) + // epsilon is hard to pin down in doubleing point, but easy in fixed point, so if + // we convert to fixed point then these operations get easier: + // long x1_fixed = x1_intercept * 2^32; (fixed point 32.32 format) + // curx = next VPC = fixed_floor(x1_fixed - 2^31 + 2^32 - 1) + // = fixed_floor(x1_fixed + 2^31 - 1) + // = fixed_floor(x1_fixed + 0x7Dffffff) + // and error = fixed_fract(x1_fixed + 0x7Dffffff) + final double x1_intercept = x1d + (firstCrossing - y1d) * slope; + + // inlined scalb(x1_intercept, 32): + final long x1_fixed_biased = ((long) (POWER_2_TO_32 * x1_intercept)) + + 0x7DffffffL; + // curx: + // last bit corresponds to the orientation + _unsafe.putInt(addr, (((int) (x1_fixed_biased >> 31L)) & ALL_BUT_LSB) | or); + addr += SIZE_INT; + _unsafe.putInt(addr, ((int) x1_fixed_biased) >>> 1); + addr += SIZE_INT; + + // inlined scalb(slope, 32): + final long slope_fixed = (long) (POWER_2_TO_32 * slope); + + // last bit set to 0 to keep orientation: + _unsafe.putInt(addr, (((int) (slope_fixed >> 31L)) & ALL_BUT_LSB)); + addr += SIZE_INT; + _unsafe.putInt(addr, ((int) slope_fixed) >>> 1); + addr += SIZE_INT; + + final int[] _edgeBuckets = edgeBuckets; + final int[] _edgeBucketCounts = edgeBucketCounts; + + final int _boundsMinY = boundsMinY; + + // each bucket is a linked list. this method adds ptr to the + // start of the "bucket"th linked list. + final int bucketIdx = firstCrossing - _boundsMinY; + + // pointer from bucket + _unsafe.putInt(addr, _edgeBuckets[bucketIdx]); + addr += SIZE_INT; + // y max (inclusive) + _unsafe.putInt(addr, lastCrossing); + + // Update buckets: + // directly the edge struct "pointer" + _edgeBuckets[bucketIdx] = edgePtr; + _edgeBucketCounts[bucketIdx] += 2; // 1 << 1 + // last bit means edge end + _edgeBucketCounts[lastCrossing - _boundsMinY] |= 0x1; + + // update free pointer (ie length in bytes) + _edges.used += _SIZEOF_EDGE_BYTES; + + if (DO_MONITORS) { + rdrCtx.stats.mon_rdr_addLine.stop(); + } + } + +// END EDGE LIST +////////////////////////////////////////////////////////////////////////////// + + // Bounds of the drawing region, at subpixel precision. + private int boundsMinX, boundsMinY, boundsMaxX, boundsMaxY; + + // Current winding rule + private int windingRule; + + // Current drawing position, i.e., final point of last segment + private double x0, y0; + + // Position of most recent 'moveTo' command + private double sx0, sy0; + + // per-thread renderer context + final DRendererContext rdrCtx; + // dirty curve + private final DCurve curve; + + // clean alpha array (zero filled) + private int[] alphaLine; + + // alphaLine ref (clean) + private final IntArrayCache.Reference alphaLine_ref; + + private boolean enableBlkFlags = false; + private boolean prevUseBlkFlags = false; + + /* block flags (0|1) */ + private int[] blkFlags; + + // blkFlags ref (clean) + private final IntArrayCache.Reference blkFlags_ref; + + DRendererNoAA(final DRendererContext rdrCtx) { + this.rdrCtx = rdrCtx; + + this.edges = rdrCtx.newOffHeapArray(INITIAL_EDGES_CAPACITY); // 96K + + this.curve = rdrCtx.curve; + + edgeBuckets_ref = rdrCtx.newCleanIntArrayRef(INITIAL_BUCKET_ARRAY); // 64K + edgeBucketCounts_ref = rdrCtx.newCleanIntArrayRef(INITIAL_BUCKET_ARRAY); // 64K + + edgeBuckets = edgeBuckets_ref.initial; + edgeBucketCounts = edgeBucketCounts_ref.initial; + + // 2048 (pixelsize) pixel large + alphaLine_ref = rdrCtx.newCleanIntArrayRef(INITIAL_AA_ARRAY); // 8K + alphaLine = alphaLine_ref.initial; + + crossings_ref = rdrCtx.newDirtyIntArrayRef(INITIAL_CROSSING_COUNT); // 2K + aux_crossings_ref = rdrCtx.newDirtyIntArrayRef(INITIAL_CROSSING_COUNT); // 2K + edgePtrs_ref = rdrCtx.newDirtyIntArrayRef(INITIAL_CROSSING_COUNT); // 2K + aux_edgePtrs_ref = rdrCtx.newDirtyIntArrayRef(INITIAL_CROSSING_COUNT); // 2K + + crossings = crossings_ref.initial; + aux_crossings = aux_crossings_ref.initial; + edgePtrs = edgePtrs_ref.initial; + aux_edgePtrs = aux_edgePtrs_ref.initial; + + blkFlags_ref = rdrCtx.newCleanIntArrayRef(INITIAL_ARRAY); // 1K = 1 tile line + blkFlags = blkFlags_ref.initial; + } + + public DRendererNoAA init(final int pix_boundsX, final int pix_boundsY, + final int pix_boundsWidth, final int pix_boundsHeight, + final int windingRule) + { + this.windingRule = windingRule; + + // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY + this.boundsMinX = pix_boundsX; + this.boundsMaxX = pix_boundsX + pix_boundsWidth; + this.boundsMinY = pix_boundsY; + this.boundsMaxY = pix_boundsY + pix_boundsHeight; + + if (DO_LOG_BOUNDS) { + MarlinUtils.logInfo("boundsXY = [" + boundsMinX + " ... " + + boundsMaxX + "[ [" + boundsMinY + " ... " + + boundsMaxY + "["); + } + + // see addLine: ceil(boundsMaxY) => boundsMaxY + 1 + // +1 for edgeBucketCounts + final int edgeBucketsLength = (boundsMaxY - boundsMinY) + 1; + + if (edgeBucketsLength > INITIAL_BUCKET_ARRAY) { + if (DO_STATS) { + rdrCtx.stats.stat_array_renderer_edgeBuckets + .add(edgeBucketsLength); + rdrCtx.stats.stat_array_renderer_edgeBucketCounts + .add(edgeBucketsLength); + } + edgeBuckets = edgeBuckets_ref.getArray(edgeBucketsLength); + edgeBucketCounts = edgeBucketCounts_ref.getArray(edgeBucketsLength); + } + + edgeMinY = Integer.MAX_VALUE; + edgeMaxY = Integer.MIN_VALUE; + edgeMinX = Double.POSITIVE_INFINITY; + edgeMaxX = Double.NEGATIVE_INFINITY; + + // reset used mark: + edgeCount = 0; + activeEdgeMaxUsed = 0; + edges.used = 0; + + // reset bbox: + bboxX0 = 0; + bboxX1 = 0; + + return this; // fluent API + } + + /** + * Disposes this renderer and recycle it clean up before reusing this instance + */ + public void dispose() { + if (DO_STATS) { + rdrCtx.stats.stat_rdr_activeEdges.add(activeEdgeMaxUsed); + rdrCtx.stats.stat_rdr_edges.add(edges.used); + rdrCtx.stats.stat_rdr_edges_count.add(edges.used / SIZEOF_EDGE_BYTES); + rdrCtx.stats.hist_rdr_edges_count.add(edges.used / SIZEOF_EDGE_BYTES); + rdrCtx.stats.totalOffHeap += edges.length; + } + // Return arrays: + crossings = crossings_ref.putArray(crossings); + aux_crossings = aux_crossings_ref.putArray(aux_crossings); + + edgePtrs = edgePtrs_ref.putArray(edgePtrs); + aux_edgePtrs = aux_edgePtrs_ref.putArray(aux_edgePtrs); + + alphaLine = alphaLine_ref.putArray(alphaLine, 0, 0); // already zero filled + blkFlags = blkFlags_ref.putArray(blkFlags, 0, 0); // already zero filled + + if (edgeMinY != Integer.MAX_VALUE) { + // if context is maked as DIRTY: + if (rdrCtx.dirty) { + // may happen if an exception if thrown in the pipeline processing: + // clear completely buckets arrays: + buckets_minY = 0; + buckets_maxY = boundsMaxY - boundsMinY; + } + // clear only used part + edgeBuckets = edgeBuckets_ref.putArray(edgeBuckets, buckets_minY, + buckets_maxY); + edgeBucketCounts = edgeBucketCounts_ref.putArray(edgeBucketCounts, + buckets_minY, + buckets_maxY + 1); + } else { + // unused arrays + edgeBuckets = edgeBuckets_ref.putArray(edgeBuckets, 0, 0); + edgeBucketCounts = edgeBucketCounts_ref.putArray(edgeBucketCounts, 0, 0); + } + + // At last: resize back off-heap edges to initial size + if (edges.length != INITIAL_EDGES_CAPACITY) { + // note: may throw OOME: + edges.resize(INITIAL_EDGES_CAPACITY); + } + if (DO_CLEAN_DIRTY) { + // Force zero-fill dirty arrays: + edges.fill(BYTE_0); + } + if (DO_MONITORS) { + rdrCtx.stats.mon_rdr_endRendering.stop(); + } + } + + private static double tosubpixx(final double pix_x) { + return pix_x; + } + + private static double tosubpixy(final double pix_y) { + // shift y by -0.5 for fast ceil(y - 0.5): + return pix_y - 0.5D; + } + + @Override + public void moveTo(double pix_x0, double pix_y0) { + closePath(); + final double sx = tosubpixx(pix_x0); + final double sy = tosubpixy(pix_y0); + this.sx0 = sx; + this.sy0 = sy; + this.x0 = sx; + this.y0 = sy; + } + + @Override + public void lineTo(double pix_x1, double pix_y1) { + final double x1 = tosubpixx(pix_x1); + final double y1 = tosubpixy(pix_y1); + addLine(x0, y0, x1, y1); + x0 = x1; + y0 = y1; + } + + @Override + public void curveTo(double x1, double y1, + double x2, double y2, + double x3, double y3) + { + final double xe = tosubpixx(x3); + final double ye = tosubpixy(y3); + curve.set(x0, y0, tosubpixx(x1), tosubpixy(y1), + tosubpixx(x2), tosubpixy(y2), xe, ye); + curveBreakIntoLinesAndAdd(x0, y0, curve, xe, ye); + x0 = xe; + y0 = ye; + } + + @Override + public void quadTo(double pix_x1, double pix_y1, + double pix_x2, double pix_y2) + { + final double cx1 = tosubpixx(pix_x1); + final double cy1 = tosubpixy(pix_y1); + + final double xe = tosubpixx(pix_x2); + final double ye = tosubpixy(pix_y2); + + if (USE_SUBDIVIDE_QUAD) { + subdivideQuad(0, x0, y0, cx1, cy1, xe, ye); + } else { + curve.set(x0, y0, cx1, cy1, xe, ye); + quadBreakIntoLinesAndAdd(x0, y0, curve, xe, ye); + } + x0 = xe; + y0 = ye; + } + + void subdivideQuad(final int level, + final double x0, final double y0, + final double x1, final double y1, + final double x2, final double y2) + { + if (level < SUBDIVIDE_MAX) { + + /* Test if the curve is flat enough for insertion. */ + + // use Roger Willcocks bezier flatness criterion +// var tolerance:Number = 4*tol*tol; + + final double ux = 2D * x1 - x0 - x2; + + final double uy = 2D * y1 - y0 - y2; + + if (ux * ux + uy * uy > MAX_FLAT_SQ) { +/* + AGG + double dx = x2 - x0; + double dy = y2 - y0; + final double dist = Math.abs( (x1 - x2) * dy - (y1 - y2) * dx); + // TODO: check collinearity ? +*/ + +// if (level == 0 || dist * dist > MAX_FLAT_SQ * (dx * dx + dy * dy)) { +// if (ptSegDistSq(x0, y0, x2, y2, x1, y1) > MAX_FLAT_SQ) { + final double cx01 = (x0 + x1) / 2.0D; + final double cx12 = (x1 + x2) / 2.0D; + final double cx012 = (cx01 + cx12) / 2.0D; + + final double cy01 = (y0 + y1) / 2.0D; + final double cy12 = (y1 + y2) / 2.0D; + final double cy012 = (cy01 + cy12) / 2.0D; + + subdivideQuad(level + 1, x0, y0, cx01, cy01, cx012, cy012); + subdivideQuad(level + 1, cx012, cy012, cx12, cy12, x2, y2); + return; + } + } + + addLine(x0, y0, x2, y2); + } + + public static double ptSegDistSq(double x1, double y1, + double x2, double y2, + double px, double py) + { + // Adjust vectors relative to x1,y1 + // x2,y2 becomes relative vector from x1,y1 to end of segment + x2 -= x1; + y2 -= y1; + // px,py becomes relative vector from x1,y1 to test point + px -= x1; + py -= y1; + double dotprod = px * x2 + py * y2; + double projlenSq; + if (dotprod <= 0D) { + // px,py is on the side of x1,y1 away from x2,y2 + // distance to segment is length of px,py vector + // "length of its (clipped) projection" is now 0.0 + projlenSq = 0D; + } else { + // switch to backwards vectors relative to x2,y2 + // x2,y2 are already the negative of x1,y1=>x2,y2 + // to get px,py to be the negative of px,py=>x2,y2 + // the dot product of two negated vectors is the same + // as the dot product of the two normal vectors + px = x2 - px; + py = y2 - py; + dotprod = px * x2 + py * y2; + if (dotprod <= 0D) { + // px,py is on the side of x2,y2 away from x1,y1 + // distance to segment is length of (backwards) px,py vector + // "length of its (clipped) projection" is now 0.0 + projlenSq = 0D; + } else { + // px,py is between x1,y1 and x2,y2 + // dotprod is the length of the px,py vector + // projected on the x2,y2=>x1,y1 vector times the + // length of the x2,y2=>x1,y1 vector + projlenSq = dotprod * dotprod / (x2 * x2 + y2 * y2); + } + } + // Distance to line is now the length of the relative point + // vector minus the length of its projection onto the line + // (which is zero if the projection falls outside the range + // of the line segment). + double lenSq = px * px + py * py - projlenSq; + if (lenSq < 0D) { + lenSq = 0D; + } + return lenSq; + } + + @Override + public void closePath() { + addLine(x0, y0, sx0, sy0); + x0 = sx0; + y0 = sy0; + } + + @Override + public void pathDone() { + closePath(); + + // call endRendering() to determine the boundaries: + endRendering(); + } + + private void _endRendering(final int ymin, final int ymax, + final MarlinAlphaConsumer ac) + { + if (DISABLE_RENDER) { + return; + } + + // Get X bounds as true pixel boundaries to compute correct pixel coverage: + final int bboxx0 = bbox_spminX; + final int bboxx1 = bbox_spmaxX; + + final boolean windingRuleEvenOdd = (windingRule == WIND_EVEN_ODD); + + // Useful when processing tile line by tile line + final int[] _alpha = alphaLine; + + // local vars (performance): + final OffHeapArray _edges = edges; + final int[] _edgeBuckets = edgeBuckets; + final int[] _edgeBucketCounts = edgeBucketCounts; + + int[] _crossings = this.crossings; + int[] _edgePtrs = this.edgePtrs; + + // merge sort auxiliary storage: + int[] _aux_crossings = this.aux_crossings; + int[] _aux_edgePtrs = this.aux_edgePtrs; + + // copy constants: + final long _OFF_ERROR = OFF_ERROR; + final long _OFF_BUMP_X = OFF_BUMP_X; + final long _OFF_BUMP_ERR = OFF_BUMP_ERR; + + final long _OFF_NEXT = OFF_NEXT; + final long _OFF_YMAX = OFF_YMAX; + + final int _ALL_BUT_LSB = ALL_BUT_LSB; + final int _ERR_STEP_MAX = ERR_STEP_MAX; + + // unsafe I/O: + final Unsafe _unsafe = OffHeapArray.UNSAFE; + final long addr0 = _edges.address; + long addr; + + final int _MIN_VALUE = Integer.MIN_VALUE; + final int _MAX_VALUE = Integer.MAX_VALUE; + + // Now we iterate through the scanlines. We must tell emitRow the coord + // of the first non-transparent pixel, so we must keep accumulators for + // the first and last pixels of the section of the current pixel row + // that we will emit. + // We also need to accumulate pix_bbox, but the iterator does it + // for us. We will just get the values from it once this loop is done + int minX = _MAX_VALUE; + int maxX = _MIN_VALUE; + + int y = ymin; + int bucket = y - boundsMinY; + + int numCrossings = this.edgeCount; + int edgePtrsLen = _edgePtrs.length; + int crossingsLen = _crossings.length; + int _arrayMaxUsed = activeEdgeMaxUsed; + int ptrLen = 0, newCount, ptrEnd; + + int bucketcount, i, j, ecur; + int cross, lastCross; + int x0, x1, tmp, sum, prev, curx, curxo, crorientation, err; + + int low, high, mid, prevNumCrossings; + boolean useBinarySearch; + + final int[] _blkFlags = blkFlags; + final int _BLK_SIZE_LG = BLOCK_SIZE_LG; + final int _BLK_SIZE = BLOCK_SIZE; + + final boolean _enableBlkFlagsHeuristics = ENABLE_BLOCK_FLAGS_HEURISTICS && this.enableBlkFlags; + + // Use block flags if large pixel span and few crossings: + // ie mean(distance between crossings) is high + boolean useBlkFlags = this.prevUseBlkFlags; + + final int stroking = rdrCtx.stroking; + + int lastY = -1; // last emited row + + + // Iteration on scanlines + for (; y < ymax; y++, bucket++) { + // --- from former ScanLineIterator.next() + bucketcount = _edgeBucketCounts[bucket]; + + // marker on previously sorted edges: + prevNumCrossings = numCrossings; + + // bucketCount indicates new edge / edge end: + if (bucketcount != 0) { + if (DO_STATS) { + rdrCtx.stats.stat_rdr_activeEdges_updates.add(numCrossings); + } + + // last bit set to 1 means that edges ends + if ((bucketcount & 0x1) != 0) { + // eviction in active edge list + // cache edges[] address + offset + addr = addr0 + _OFF_YMAX; + + for (i = 0, newCount = 0; i < numCrossings; i++) { + // get the pointer to the edge + ecur = _edgePtrs[i]; + // random access so use unsafe: + if (_unsafe.getInt(addr + ecur) > y) { + _edgePtrs[newCount++] = ecur; + } + } + // update marker on sorted edges minus removed edges: + prevNumCrossings = numCrossings = newCount; + } + + ptrLen = bucketcount >> 1; // number of new edge + + if (ptrLen != 0) { + if (DO_STATS) { + rdrCtx.stats.stat_rdr_activeEdges_adds.add(ptrLen); + if (ptrLen > 10) { + rdrCtx.stats.stat_rdr_activeEdges_adds_high.add(ptrLen); + } + } + ptrEnd = numCrossings + ptrLen; + + if (edgePtrsLen < ptrEnd) { + if (DO_STATS) { + rdrCtx.stats.stat_array_renderer_edgePtrs.add(ptrEnd); + } + this.edgePtrs = _edgePtrs + = edgePtrs_ref.widenArray(_edgePtrs, numCrossings, + ptrEnd); + + edgePtrsLen = _edgePtrs.length; + // Get larger auxiliary storage: + aux_edgePtrs_ref.putArray(_aux_edgePtrs); + + // use ArrayCache.getNewSize() to use the same growing + // factor than widenArray(): + if (DO_STATS) { + rdrCtx.stats.stat_array_renderer_aux_edgePtrs.add(ptrEnd); + } + this.aux_edgePtrs = _aux_edgePtrs + = aux_edgePtrs_ref.getArray( + ArrayCacheConst.getNewSize(numCrossings, ptrEnd) + ); + } + + // cache edges[] address + offset + addr = addr0 + _OFF_NEXT; + + // add new edges to active edge list: + for (ecur = _edgeBuckets[bucket]; + numCrossings < ptrEnd; numCrossings++) + { + // store the pointer to the edge + _edgePtrs[numCrossings] = ecur; + // random access so use unsafe: + ecur = _unsafe.getInt(addr + ecur); + } + + if (crossingsLen < numCrossings) { + // Get larger array: + crossings_ref.putArray(_crossings); + + if (DO_STATS) { + rdrCtx.stats.stat_array_renderer_crossings + .add(numCrossings); + } + this.crossings = _crossings + = crossings_ref.getArray(numCrossings); + + // Get larger auxiliary storage: + aux_crossings_ref.putArray(_aux_crossings); + + if (DO_STATS) { + rdrCtx.stats.stat_array_renderer_aux_crossings + .add(numCrossings); + } + this.aux_crossings = _aux_crossings + = aux_crossings_ref.getArray(numCrossings); + + crossingsLen = _crossings.length; + } + if (DO_STATS) { + // update max used mark + if (numCrossings > _arrayMaxUsed) { + _arrayMaxUsed = numCrossings; + } + } + } // ptrLen != 0 + } // bucketCount != 0 + + + if (numCrossings != 0) { + /* + * thresholds to switch to optimized merge sort + * for newly added edges + final merge pass. + */ + if ((ptrLen < 10) || (numCrossings < 40)) { + if (DO_STATS) { + rdrCtx.stats.hist_rdr_crossings.add(numCrossings); + rdrCtx.stats.hist_rdr_crossings_adds.add(ptrLen); + } + + /* + * threshold to use binary insertion sort instead of + * straight insertion sort (to reduce minimize comparisons). + */ + useBinarySearch = (numCrossings >= 20); + + // if small enough: + lastCross = _MIN_VALUE; + + for (i = 0; i < numCrossings; i++) { + // get the pointer to the edge + ecur = _edgePtrs[i]; + + /* convert subpixel coordinates into pixel + positions (int) for coming scanline */ + /* note: it is faster to always update edges even + if it is removed from AEL for coming or last scanline */ + + // random access so use unsafe: + addr = addr0 + ecur; // ecur + OFF_F_CURX + + // get current crossing: + curx = _unsafe.getInt(addr); + + // update crossing with orientation at last bit: + cross = curx; + + // Increment x using DDA (fixed point): + curx += _unsafe.getInt(addr + _OFF_BUMP_X); + + // Increment error: + err = _unsafe.getInt(addr + _OFF_ERROR) + + _unsafe.getInt(addr + _OFF_BUMP_ERR); + + // Manual carry handling: + // keep sign and carry bit only and ignore last bit (preserve orientation): + _unsafe.putInt(addr, curx - ((err >> 30) & _ALL_BUT_LSB)); + _unsafe.putInt(addr + _OFF_ERROR, (err & _ERR_STEP_MAX)); + + if (DO_STATS) { + rdrCtx.stats.stat_rdr_crossings_updates.add(numCrossings); + } + + // insertion sort of crossings: + if (cross < lastCross) { + if (DO_STATS) { + rdrCtx.stats.stat_rdr_crossings_sorts.add(i); + } + + /* use binary search for newly added edges + in crossings if arrays are large enough */ + if (useBinarySearch && (i >= prevNumCrossings)) { + if (DO_STATS) { + rdrCtx.stats.stat_rdr_crossings_bsearch.add(i); + } + low = 0; + high = i - 1; + + do { + // note: use signed shift (not >>>) for performance + // as indices are small enough to exceed Integer.MAX_VALUE + mid = (low + high) >> 1; + + if (_crossings[mid] < cross) { + low = mid + 1; + } else { + high = mid - 1; + } + } while (low <= high); + + for (j = i - 1; j >= low; j--) { + _crossings[j + 1] = _crossings[j]; + _edgePtrs [j + 1] = _edgePtrs[j]; + } + _crossings[low] = cross; + _edgePtrs [low] = ecur; + + } else { + j = i - 1; + _crossings[i] = _crossings[j]; + _edgePtrs[i] = _edgePtrs[j]; + + while ((--j >= 0) && (_crossings[j] > cross)) { + _crossings[j + 1] = _crossings[j]; + _edgePtrs [j + 1] = _edgePtrs[j]; + } + _crossings[j + 1] = cross; + _edgePtrs [j + 1] = ecur; + } + + } else { + _crossings[i] = lastCross = cross; + } + } + } else { + if (DO_STATS) { + rdrCtx.stats.stat_rdr_crossings_msorts.add(numCrossings); + rdrCtx.stats.hist_rdr_crossings_ratio + .add((1000 * ptrLen) / numCrossings); + rdrCtx.stats.hist_rdr_crossings_msorts.add(numCrossings); + rdrCtx.stats.hist_rdr_crossings_msorts_adds.add(ptrLen); + } + + // Copy sorted data in auxiliary arrays + // and perform insertion sort on almost sorted data + // (ie i < prevNumCrossings): + + lastCross = _MIN_VALUE; + + for (i = 0; i < numCrossings; i++) { + // get the pointer to the edge + ecur = _edgePtrs[i]; + + /* convert subpixel coordinates into pixel + positions (int) for coming scanline */ + /* note: it is faster to always update edges even + if it is removed from AEL for coming or last scanline */ + + // random access so use unsafe: + addr = addr0 + ecur; // ecur + OFF_F_CURX + + // get current crossing: + curx = _unsafe.getInt(addr); + + // update crossing with orientation at last bit: + cross = curx; + + // Increment x using DDA (fixed point): + curx += _unsafe.getInt(addr + _OFF_BUMP_X); + + // Increment error: + err = _unsafe.getInt(addr + _OFF_ERROR) + + _unsafe.getInt(addr + _OFF_BUMP_ERR); + + // Manual carry handling: + // keep sign and carry bit only and ignore last bit (preserve orientation): + _unsafe.putInt(addr, curx - ((err >> 30) & _ALL_BUT_LSB)); + _unsafe.putInt(addr + _OFF_ERROR, (err & _ERR_STEP_MAX)); + + if (DO_STATS) { + rdrCtx.stats.stat_rdr_crossings_updates.add(numCrossings); + } + + if (i >= prevNumCrossings) { + // simply store crossing as edgePtrs is in-place: + // will be copied and sorted efficiently by mergesort later: + _crossings[i] = cross; + + } else if (cross < lastCross) { + if (DO_STATS) { + rdrCtx.stats.stat_rdr_crossings_sorts.add(i); + } + + // (straight) insertion sort of crossings: + j = i - 1; + _aux_crossings[i] = _aux_crossings[j]; + _aux_edgePtrs[i] = _aux_edgePtrs[j]; + + while ((--j >= 0) && (_aux_crossings[j] > cross)) { + _aux_crossings[j + 1] = _aux_crossings[j]; + _aux_edgePtrs [j + 1] = _aux_edgePtrs[j]; + } + _aux_crossings[j + 1] = cross; + _aux_edgePtrs [j + 1] = ecur; + + } else { + // auxiliary storage: + _aux_crossings[i] = lastCross = cross; + _aux_edgePtrs [i] = ecur; + } + } + + // use Mergesort using auxiliary arrays (sort only right part) + MergeSort.mergeSortNoCopy(_crossings, _edgePtrs, + _aux_crossings, _aux_edgePtrs, + numCrossings, prevNumCrossings); + } + + // reset ptrLen + ptrLen = 0; + // --- from former ScanLineIterator.next() + + + /* note: bboxx0 and bboxx1 must be pixel boundaries + to have correct coverage computation */ + + // right shift on crossings to get the x-coordinate: + curxo = _crossings[0]; + x0 = curxo >> 1; + if (x0 < minX) { + minX = x0; // subpixel coordinate + } + + x1 = _crossings[numCrossings - 1] >> 1; + if (x1 > maxX) { + maxX = x1; // subpixel coordinate + } + + + // compute pixel coverages + prev = curx = x0; + // to turn {0, 1} into {-1, 1}, multiply by 2 and subtract 1. + // last bit contains orientation (0 or 1) + crorientation = ((curxo & 0x1) << 1) - 1; + + if (windingRuleEvenOdd) { + sum = crorientation; + + // Even Odd winding rule: take care of mask ie sum(orientations) + for (i = 1; i < numCrossings; i++) { + curxo = _crossings[i]; + curx = curxo >> 1; + // to turn {0, 1} into {-1, 1}, multiply by 2 and subtract 1. + // last bit contains orientation (0 or 1) + crorientation = ((curxo & 0x1) << 1) - 1; + + if ((sum & 0x1) != 0) { + // TODO: perform line clipping on left-right sides + // to avoid such bound checks: + x0 = (prev > bboxx0) ? prev : bboxx0; + + if (curx < bboxx1) { + x1 = curx; + } else { + x1 = bboxx1; + // skip right side (fast exit loop): + i = numCrossings; + } + + if (x0 < x1) { + x0 -= bboxx0; // turn x0, x1 from coords to indices + x1 -= bboxx0; // in the alpha array. + + _alpha[x0] += 1; + _alpha[x1] -= 1; + + if (useBlkFlags) { + // flag used blocks: + _blkFlags[x0 >> _BLK_SIZE_LG] = 1; + _blkFlags[x1 >> _BLK_SIZE_LG] = 1; + } + } + } + + sum += crorientation; + prev = curx; + } + } else { + // Non-zero winding rule: optimize that case (default) + // and avoid processing intermediate crossings + for (i = 1, sum = 0;; i++) { + sum += crorientation; + + if (sum != 0) { + // prev = min(curx) + if (prev > curx) { + prev = curx; + } + } else { + // TODO: perform line clipping on left-right sides + // to avoid such bound checks: + x0 = (prev > bboxx0) ? prev : bboxx0; + + if (curx < bboxx1) { + x1 = curx; + } else { + x1 = bboxx1; + // skip right side (fast exit loop): + i = numCrossings; + } + + if (x0 < x1) { + x0 -= bboxx0; // turn x0, x1 from coords to indices + x1 -= bboxx0; // in the alpha array. + + _alpha[x0] += 1; + _alpha[x1] -= 1; + + if (useBlkFlags) { + // flag used blocks: + _blkFlags[x0 >> _BLK_SIZE_LG] = 1; + _blkFlags[x1 >> _BLK_SIZE_LG] = 1; + } + } + prev = _MAX_VALUE; + } + + if (i == numCrossings) { + break; + } + + curxo = _crossings[i]; + curx = curxo >> 1; + // to turn {0, 1} into {-1, 1}, multiply by 2 and subtract 1. + // last bit contains orientation (0 or 1) + crorientation = ((curxo & 0x1) << 1) - 1; + } + } + } // numCrossings > 0 + + // even if this last row had no crossings, alpha will be zeroed + // from the last emitRow call. But this doesn't matter because + // maxX < minX, so no row will be emitted to the AlphaConsumer. + if (true) { + lastY = y; + + // convert subpixel to pixel coordinate within boundaries: + minX = FloatMath.max(minX, bboxx0); + maxX = FloatMath.min(maxX, bboxx1); + + if (maxX >= minX) { + // note: alpha array will be zeroed by copyAARow() + // +1 because alpha [pix_minX; pix_maxX[ + // fix range [x0; x1[ + // note: if x1=bboxx1, then alpha is written up to bboxx1+1 + // inclusive: alpha[bboxx1] ignored, alpha[bboxx1+1] == 0 + // (normally so never cleared below) + copyAARow(_alpha, lastY, minX, maxX + 1, useBlkFlags, ac); + + // speculative for next pixel row (scanline coherence): + if (_enableBlkFlagsHeuristics) { + // Use block flags if large pixel span and few crossings: + // ie mean(distance between crossings) is larger than + // 1 block size; + + // fast check width: + maxX -= minX; + + // if stroking: numCrossings /= 2 + // => shift numCrossings by 1 + // condition = (width / (numCrossings - 1)) > blockSize + useBlkFlags = (maxX > _BLK_SIZE) && (maxX > + (((numCrossings >> stroking) - 1) << _BLK_SIZE_LG)); + + if (DO_STATS) { + tmp = FloatMath.max(1, + ((numCrossings >> stroking) - 1)); + rdrCtx.stats.hist_tile_generator_encoding_dist + .add(maxX / tmp); + } + } + } else { + ac.clearAlphas(lastY); + } + minX = _MAX_VALUE; + maxX = _MIN_VALUE; + } + } // scan line iterator + + // Emit final row + y--; + + // convert subpixel to pixel coordinate within boundaries: + minX = FloatMath.max(minX, bboxx0); + maxX = FloatMath.min(maxX, bboxx1); + + if (maxX >= minX) { + // note: alpha array will be zeroed by copyAARow() + // +1 because alpha [pix_minX; pix_maxX[ + // fix range [x0; x1[ + // note: if x1=bboxx1, then alpha is written up to bboxx1+1 + // inclusive: alpha[bboxx1] ignored then cleared and + // alpha[bboxx1+1] == 0 (normally so never cleared after) + copyAARow(_alpha, y, minX, maxX + 1, useBlkFlags, ac); + } else if (y != lastY) { + ac.clearAlphas(y); + } + + // update member: + edgeCount = numCrossings; + prevUseBlkFlags = useBlkFlags; + + if (DO_STATS) { + // update max used mark + activeEdgeMaxUsed = _arrayMaxUsed; + } + } + + void endRendering() { + if (DO_MONITORS) { + rdrCtx.stats.mon_rdr_endRendering.start(); + } + if (edgeMinY == Integer.MAX_VALUE) { + return; // undefined edges bounds + } + + final int _boundsMinY = boundsMinY; + final int _boundsMaxY = boundsMaxY; + + // bounds as inclusive intervals + final int spminX = FloatMath.max(FloatMath.ceil_int(edgeMinX - 0.5D), boundsMinX); + final int spmaxX = FloatMath.min(FloatMath.ceil_int(edgeMaxX - 0.5D), boundsMaxX - 1); + + // edge Min/Max Y are already rounded to subpixels within bounds: + final int spminY = edgeMinY; + final int spmaxY; + int maxY = edgeMaxY; + + if (maxY <= _boundsMaxY - 1) { + spmaxY = maxY; + } else { + spmaxY = _boundsMaxY - 1; + maxY = _boundsMaxY; + } + buckets_minY = spminY - _boundsMinY; + buckets_maxY = maxY - _boundsMinY; + + if (DO_LOG_BOUNDS) { + MarlinUtils.logInfo("edgesXY = [" + edgeMinX + " ... " + edgeMaxX + + "][" + edgeMinY + " ... " + edgeMaxY + "]"); + MarlinUtils.logInfo("spXY = [" + spminX + " ... " + spmaxX + + "][" + spminY + " ... " + spmaxY + "]"); + } + + // test clipping for shapes out of bounds + if ((spminX > spmaxX) || (spminY > spmaxY)) { + return; + } + + // half open intervals + // inclusive: + final int pminX = spminX; + // exclusive: + final int pmaxX = spmaxX + 1; // +1 to ensure proper upper bound + // inclusive: + final int pminY = spminY; + // exclusive: + final int pmaxY = spmaxY + 1; // +1 to ensure proper upper bound + + // store BBox to answer ptg.getBBox(): + initConsumer(pminX, pminY, pmaxX, pmaxY); + + // Heuristics for using block flags: + if (ENABLE_BLOCK_FLAGS) { + enableBlkFlags = this.useRLE; + prevUseBlkFlags = enableBlkFlags && !ENABLE_BLOCK_FLAGS_HEURISTICS; + + if (enableBlkFlags) { + // ensure blockFlags array is large enough: + // note: +2 to ensure enough space left at end + final int blkLen = ((pmaxX - pminX) >> BLOCK_SIZE_LG) + 2; + if (blkLen > INITIAL_ARRAY) { + blkFlags = blkFlags_ref.getArray(blkLen); + } + } + } + + // memorize the rendering bounding box: + /* note: bbox_spminX and bbox_spmaxX must be pixel boundaries + to have correct coverage computation */ + // inclusive: + bbox_spminX = pminX; + // exclusive: + bbox_spmaxX = pmaxX; + // inclusive: + bbox_spminY = pminY; + // exclusive: + bbox_spmaxY = pmaxY; + + if (DO_LOG_BOUNDS) { + MarlinUtils.logInfo("pXY = [" + pminX + " ... " + pmaxX + + "[ [" + pminY + " ... " + pmaxY + "["); + MarlinUtils.logInfo("bbox_spXY = [" + bbox_spminX + " ... " + + bbox_spmaxX + "[ [" + bbox_spminY + " ... " + + bbox_spmaxY + "["); + } + + // Prepare alpha line: + // add 2 to better deal with the last pixel in a pixel row. + final int width = (pmaxX - pminX) + 2; + + // Useful when processing tile line by tile line + if (width > INITIAL_AA_ARRAY) { + if (DO_STATS) { + rdrCtx.stats.stat_array_renderer_alphaline.add(width); + } + alphaLine = alphaLine_ref.getArray(width); + } + } + + void initConsumer(int minx, int miny, int maxx, int maxy) + { + // assert maxy >= miny && maxx >= minx; + bboxX0 = minx; + bboxX1 = maxx; + bboxY0 = miny; + bboxY1 = maxy; + + final int width = (maxx - minx); + + if (FORCE_NO_RLE) { + useRLE = false; + } else if (FORCE_RLE) { + useRLE = true; + } else { + // heuristics: use both bbox area and complexity + // ie number of primitives: + + // fast check min width: + if (width <= RLE_MIN_WIDTH) { + useRLE = false; + } else { + useRLE = true; + } + } + } + + private int bbox_spminX, bbox_spmaxX, bbox_spminY, bbox_spmaxY; + + public void produceAlphas(final MarlinAlphaConsumer ac) { + ac.setMaxAlpha(1); + + if (enableBlkFlags && !ac.supportBlockFlags()) { + // consumer does not support block flag optimization: + enableBlkFlags = false; + prevUseBlkFlags = false; + } + + if (DO_MONITORS) { + rdrCtx.stats.mon_rdr_endRendering_Y.start(); + } + + // Process all scan lines: + _endRendering(bbox_spminY, bbox_spmaxY, ac); + + if (DO_MONITORS) { + rdrCtx.stats.mon_rdr_endRendering_Y.stop(); + } + } + + void copyAARow(final int[] alphaRow, + final int pix_y, final int pix_from, final int pix_to, + final boolean useBlockFlags, + final MarlinAlphaConsumer ac) + { + if (DO_MONITORS) { + rdrCtx.stats.mon_rdr_copyAARow.start(); + } + if (DO_STATS) { + rdrCtx.stats.stat_cache_rowAA.add(pix_to - pix_from); + } + + if (useBlockFlags) { + if (DO_STATS) { + rdrCtx.stats.hist_tile_generator_encoding.add(1); + } + ac.setAndClearRelativeAlphas(blkFlags, alphaRow, pix_y, pix_from, pix_to); + } else { + if (DO_STATS) { + rdrCtx.stats.hist_tile_generator_encoding.add(0); + } + ac.setAndClearRelativeAlphas(alphaRow, pix_y, pix_from, pix_to); + } + if (DO_MONITORS) { + rdrCtx.stats.mon_rdr_copyAARow.stop(); + } + } + + // output pixel bounding box: + int bboxX0, bboxX1, bboxY0, bboxY1; + + @Override + public int getOutpixMinX() { + return bboxX0; + } + + @Override + public int getOutpixMaxX() { + return bboxX1; + } + + @Override + public int getOutpixMinY() { + return bboxY0; + } + + @Override + public int getOutpixMaxY() { + return bboxY1; + } +} --- /dev/null 2016-11-30 21:27:13.355352085 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/DStroker.java 2016-11-30 22:48:53.146420554 +0100 @@ -0,0 +1,1435 @@ +/* + * Copyright (c) 2007, 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.marlin; + +import java.util.Arrays; + + + +// TODO: some of the arithmetic here is too verbose and prone to hard to +// debug typos. We should consider making a small Point/Vector class that +// has methods like plus(Point), minus(Point), dot(Point), cross(Point)and such +public final class DStroker implements DPathConsumer2D, MarlinConst { + + private static final int MOVE_TO = 0; + private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad + private static final int CLOSE = 2; + + /** + * Constant value for join style. + */ + public static final int JOIN_MITER = 0; + + /** + * Constant value for join style. + */ + public static final int JOIN_ROUND = 1; + + /** + * Constant value for join style. + */ + public static final int JOIN_BEVEL = 2; + + /** + * Constant value for end cap style. + */ + public static final int CAP_BUTT = 0; + + /** + * Constant value for end cap style. + */ + public static final int CAP_ROUND = 1; + + /** + * Constant value for end cap style. + */ + public static final int CAP_SQUARE = 2; + + // pisces used to use fixed point arithmetic with 16 decimal digits. I + // didn't want to change the values of the constant below when I converted + // it to doubleing point, so that's why the divisions by 2^16 are there. + private static final double ROUND_JOIN_THRESHOLD = 1000/65536D; + + private static final double C = 0.5522847498307933D; + + private static final int MAX_N_CURVES = 11; + + private DPathConsumer2D out; + + private int capStyle; + private int joinStyle; + + private double lineWidth2; + private double invHalfLineWidth2Sq; + + private final double[] offset0 = new double[2]; + private final double[] offset1 = new double[2]; + private final double[] offset2 = new double[2]; + private final double[] miter = new double[2]; + private double miterLimitSq; + + private int prev; + + // The starting point of the path, and the slope there. + private double sx0, sy0, sdx, sdy; + // the current point and the slope there. + private double cx0, cy0, cdx, cdy; // c stands for current + // vectors that when added to (sx0,sy0) and (cx0,cy0) respectively yield the + // first and last points on the left parallel path. Since this path is + // parallel, it's slope at any point is parallel to the slope of the + // original path (thought they may have different directions), so these + // could be computed from sdx,sdy and cdx,cdy (and vice versa), but that + // would be error prone and hard to read, so we keep these anyway. + private double smx, smy, cmx, cmy; + + private final PolyStack reverse; + + // This is where the curve to be processed is put. We give it + // enough room to store all curves. + private final double[] middle = new double[MAX_N_CURVES * 8]; + private final double[] lp = new double[8]; + private final double[] rp = new double[8]; + private final double[] subdivTs = new double[MAX_N_CURVES - 1]; + + // per-thread renderer context + final DRendererContext rdrCtx; + + // dirty curve + final DCurve curve; + + /** + * Constructs a DStroker. + * @param rdrCtx per-thread renderer context + */ + DStroker(final DRendererContext rdrCtx) { + this.rdrCtx = rdrCtx; + + this.reverse = new PolyStack(rdrCtx); + this.curve = rdrCtx.curve; + } + + /** + * Inits the DStroker. + * + * @param pc2d an output DPathConsumer2D. + * @param lineWidth the desired line width in pixels + * @param capStyle the desired end cap style, one of + * CAP_BUTT, CAP_ROUND or + * CAP_SQUARE. + * @param joinStyle the desired line join style, one of + * JOIN_MITER, JOIN_ROUND or + * JOIN_BEVEL. + * @param miterLimit the desired miter limit + * @return this instance + */ + public DStroker init(DPathConsumer2D pc2d, + double lineWidth, + int capStyle, + int joinStyle, + double miterLimit) + { + this.out = pc2d; + + this.lineWidth2 = lineWidth / 2D; + this.invHalfLineWidth2Sq = 1D / (2D * lineWidth2 * lineWidth2); + this.capStyle = capStyle; + this.joinStyle = joinStyle; + + double limit = miterLimit * lineWidth2; + this.miterLimitSq = limit * limit; + + this.prev = CLOSE; + + rdrCtx.stroking = 1; + + return this; // fluent API + } + + /** + * Disposes this stroker: + * clean up before reusing this instance + */ + void dispose() { + reverse.dispose(); + + if (DO_CLEAN_DIRTY) { + // Force zero-fill dirty arrays: + Arrays.fill(offset0, 0D); + Arrays.fill(offset1, 0D); + Arrays.fill(offset2, 0D); + Arrays.fill(miter, 0D); + Arrays.fill(middle, 0D); + Arrays.fill(lp, 0D); + Arrays.fill(rp, 0D); + Arrays.fill(subdivTs, 0D); + } + } + + private static void computeOffset(final double lx, final double ly, + final double w, final double[] m) + { + double len = lx*lx + ly*ly; + if (len == 0D) { + m[0] = 0D; + m[1] = 0D; + } else { + len = Math.sqrt(len); + m[0] = (ly * w) / len; + m[1] = -(lx * w) / len; + } + } + + // Returns true if the vectors (dx1, dy1) and (dx2, dy2) are + // clockwise (if dx1,dy1 needs to be rotated clockwise to close + // the smallest angle between it and dx2,dy2). + // This is equivalent to detecting whether a point q is on the right side + // of a line passing through points p1, p2 where p2 = p1+(dx1,dy1) and + // q = p2+(dx2,dy2), which is the same as saying p1, p2, q are in a + // clockwise order. + // NOTE: "clockwise" here assumes coordinates with 0,0 at the bottom left. + private static boolean isCW(final double dx1, final double dy1, + final double dx2, final double dy2) + { + return dx1 * dy2 <= dy1 * dx2; + } + + private void drawRoundJoin(double x, double y, + double omx, double omy, double mx, double my, + boolean rev, + double threshold) + { + if ((omx == 0D && omy == 0D) || (mx == 0D && my == 0D)) { + return; + } + + double domx = omx - mx; + double domy = omy - my; + double len = domx*domx + domy*domy; + if (len < threshold) { + return; + } + + if (rev) { + omx = -omx; + omy = -omy; + mx = -mx; + my = -my; + } + drawRoundJoin(x, y, omx, omy, mx, my, rev); + } + + private void drawRoundJoin(double cx, double cy, + double omx, double omy, + double mx, double my, + boolean rev) + { + // The sign of the dot product of mx,my and omx,omy is equal to the + // the sign of the cosine of ext + // (ext is the angle between omx,omy and mx,my). + final double cosext = omx * mx + omy * my; + // If it is >=0, we know that abs(ext) is <= 90 degrees, so we only + // need 1 curve to approximate the circle section that joins omx,omy + // and mx,my. + final int numDCurves = (cosext >= 0D) ? 1 : 2; + + switch (numDCurves) { + case 1: + drawBezApproxForArc(cx, cy, omx, omy, mx, my, rev); + break; + case 2: + // we need to split the arc into 2 arcs spanning the same angle. + // The point we want will be one of the 2 intersections of the + // perpendicular bisector of the chord (omx,omy)->(mx,my) and the + // circle. We could find this by scaling the vector + // (omx+mx, omy+my)/2 so that it has length=lineWidth2 (and thus lies + // on the circle), but that can have numerical problems when the angle + // between omx,omy and mx,my is close to 180 degrees. So we compute a + // normal of (omx,omy)-(mx,my). This will be the direction of the + // perpendicular bisector. To get one of the intersections, we just scale + // this vector that its length is lineWidth2 (this works because the + // perpendicular bisector goes through the origin). This scaling doesn't + // have numerical problems because we know that lineWidth2 divided by + // this normal's length is at least 0.5 and at most sqrt(2)/2 (because + // we know the angle of the arc is > 90 degrees). + double nx = my - omy, ny = omx - mx; + double nlen = Math.sqrt(nx*nx + ny*ny); + double scale = lineWidth2/nlen; + double mmx = nx * scale, mmy = ny * scale; + + // if (isCW(omx, omy, mx, my) != isCW(mmx, mmy, mx, my)) then we've + // computed the wrong intersection so we get the other one. + // The test above is equivalent to if (rev). + if (rev) { + mmx = -mmx; + mmy = -mmy; + } + drawBezApproxForArc(cx, cy, omx, omy, mmx, mmy, rev); + drawBezApproxForArc(cx, cy, mmx, mmy, mx, my, rev); + break; + default: + } + } + + // the input arc defined by omx,omy and mx,my must span <= 90 degrees. + private void drawBezApproxForArc(final double cx, final double cy, + final double omx, final double omy, + final double mx, final double my, + boolean rev) + { + final double cosext2 = (omx * mx + omy * my) * invHalfLineWidth2Sq; + + // check round off errors producing cos(ext) > 1 and a NaN below + // cos(ext) == 1 implies colinear segments and an empty join anyway + if (cosext2 >= 0.5D) { + // just return to avoid generating a flat curve: + return; + } + + // cv is the length of P1-P0 and P2-P3 divided by the radius of the arc + // (so, cv assumes the arc has radius 1). P0, P1, P2, P3 are the points that + // define the bezier curve we're computing. + // It is computed using the constraints that P1-P0 and P3-P2 are parallel + // to the arc tangents at the endpoints, and that |P1-P0|=|P3-P2|. + double cv = ((4.0 / 3.0) * Math.sqrt(0.5 - cosext2) / + (1.0 + Math.sqrt(cosext2 + 0.5))); + // if clockwise, we need to negate cv. + if (rev) { // rev is equivalent to isCW(omx, omy, mx, my) + cv = -cv; + } + final double x1 = cx + omx; + final double y1 = cy + omy; + final double x2 = x1 - cv * omy; + final double y2 = y1 + cv * omx; + + final double x4 = cx + mx; + final double y4 = cy + my; + final double x3 = x4 + cv * my; + final double y3 = y4 - cv * mx; + + emitDCurveTo(x1, y1, x2, y2, x3, y3, x4, y4, rev); + } + + private void drawRoundCap(double cx, double cy, double mx, double my) { + final double Cmx = C * mx; + final double Cmy = C * my; + emitDCurveTo(cx + mx - Cmy, cy + my + Cmx, + cx - my + Cmx, cy + mx + Cmy, + cx - my, cy + mx); + emitDCurveTo(cx - my - Cmx, cy + mx - Cmy, + cx - mx - Cmy, cy - my + Cmx, + cx - mx, cy - my); + } + + // Return the intersection point of the lines (x0, y0) -> (x1, y1) + // and (x0p, y0p) -> (x1p, y1p) in m[0] and m[1] + private static void computeMiter(final double x0, final double y0, + final double x1, final double y1, + final double x0p, final double y0p, + final double x1p, final double y1p, + final double[] m, int off) + { + double x10 = x1 - x0; + double y10 = y1 - y0; + double x10p = x1p - x0p; + double y10p = y1p - y0p; + + // if this is 0, the lines are parallel. If they go in the + // same direction, there is no intersection so m[off] and + // m[off+1] will contain infinity, so no miter will be drawn. + // If they go in the same direction that means that the start of the + // current segment and the end of the previous segment have the same + // tangent, in which case this method won't even be involved in + // miter drawing because it won't be called by drawMiter (because + // (mx == omx && my == omy) will be true, and drawMiter will return + // immediately). + double den = x10*y10p - x10p*y10; + double t = x10p*(y0-y0p) - y10p*(x0-x0p); + t /= den; + m[off++] = x0 + t*x10; + m[off] = y0 + t*y10; + } + + // Return the intersection point of the lines (x0, y0) -> (x1, y1) + // and (x0p, y0p) -> (x1p, y1p) in m[0] and m[1] + private static void safecomputeMiter(final double x0, final double y0, + final double x1, final double y1, + final double x0p, final double y0p, + final double x1p, final double y1p, + final double[] m, int off) + { + double x10 = x1 - x0; + double y10 = y1 - y0; + double x10p = x1p - x0p; + double y10p = y1p - y0p; + + // if this is 0, the lines are parallel. If they go in the + // same direction, there is no intersection so m[off] and + // m[off+1] will contain infinity, so no miter will be drawn. + // If they go in the same direction that means that the start of the + // current segment and the end of the previous segment have the same + // tangent, in which case this method won't even be involved in + // miter drawing because it won't be called by drawMiter (because + // (mx == omx && my == omy) will be true, and drawMiter will return + // immediately). + double den = x10*y10p - x10p*y10; + if (den == 0D) { + m[off++] = (x0 + x0p) / 2D; + m[off] = (y0 + y0p) / 2D; + return; + } + double t = x10p*(y0-y0p) - y10p*(x0-x0p); + t /= den; + m[off++] = x0 + t*x10; + m[off] = y0 + t*y10; + } + + private void drawMiter(final double pdx, final double pdy, + final double x0, final double y0, + final double dx, final double dy, + double omx, double omy, double mx, double my, + boolean rev) + { + if ((mx == omx && my == omy) || + (pdx == 0D && pdy == 0D) || + (dx == 0D && dy == 0D)) + { + return; + } + + if (rev) { + omx = -omx; + omy = -omy; + mx = -mx; + my = -my; + } + + computeMiter((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy, + (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my, + miter, 0); + + final double miterX = miter[0]; + final double miterY = miter[1]; + double lenSq = (miterX-x0)*(miterX-x0) + (miterY-y0)*(miterY-y0); + + // If the lines are parallel, lenSq will be either NaN or +inf + // (actually, I'm not sure if the latter is possible. The important + // thing is that -inf is not possible, because lenSq is a square). + // For both of those values, the comparison below will fail and + // no miter will be drawn, which is correct. + if (lenSq < miterLimitSq) { + emitLineTo(miterX, miterY, rev); + } + } + + @Override + public void moveTo(double x0, double y0) { + if (prev == DRAWING_OP_TO) { + finish(); + } + this.sx0 = this.cx0 = x0; + this.sy0 = this.cy0 = y0; + this.cdx = this.sdx = 1D; + this.cdy = this.sdy = 0D; + this.prev = MOVE_TO; + } + + @Override + public void lineTo(double x1, double y1) { + double dx = x1 - cx0; + double dy = y1 - cy0; + if (dx == 0D && dy == 0D) { + dx = 1D; + } + computeOffset(dx, dy, lineWidth2, offset0); + final double mx = offset0[0]; + final double my = offset0[1]; + + drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my); + + emitLineTo(cx0 + mx, cy0 + my); + emitLineTo( x1 + mx, y1 + my); + + emitLineToRev(cx0 - mx, cy0 - my); + emitLineToRev( x1 - mx, y1 - my); + + this.cmx = mx; + this.cmy = my; + this.cdx = dx; + this.cdy = dy; + this.cx0 = x1; + this.cy0 = y1; + this.prev = DRAWING_OP_TO; + } + + @Override + public void closePath() { + if (prev != DRAWING_OP_TO) { + if (prev == CLOSE) { + return; + } + emitMoveTo(cx0, cy0 - lineWidth2); + this.cmx = this.smx = 0D; + this.cmy = this.smy = -lineWidth2; + this.cdx = this.sdx = 1D; + this.cdy = this.sdy = 0D; + finish(); + return; + } + + if (cx0 != sx0 || cy0 != sy0) { + lineTo(sx0, sy0); + } + + drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy); + + emitLineTo(sx0 + smx, sy0 + smy); + + emitMoveTo(sx0 - smx, sy0 - smy); + emitReverse(); + + this.prev = CLOSE; + emitClose(); + } + + private void emitReverse() { + reverse.popAll(out); + } + + @Override + public void pathDone() { + if (prev == DRAWING_OP_TO) { + finish(); + } + + out.pathDone(); + + // this shouldn't matter since this object won't be used + // after the call to this method. + this.prev = CLOSE; + + // Dispose this instance: + dispose(); + } + + private void finish() { + if (capStyle == CAP_ROUND) { + drawRoundCap(cx0, cy0, cmx, cmy); + } else if (capStyle == CAP_SQUARE) { + emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy); + emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy); + } + + emitReverse(); + + if (capStyle == CAP_ROUND) { + drawRoundCap(sx0, sy0, -smx, -smy); + } else if (capStyle == CAP_SQUARE) { + emitLineTo(sx0 + smy - smx, sy0 - smx - smy); + emitLineTo(sx0 + smy + smx, sy0 - smx + smy); + } + + emitClose(); + } + + private void emitMoveTo(final double x0, final double y0) { + out.moveTo(x0, y0); + } + + private void emitLineTo(final double x1, final double y1) { + out.lineTo(x1, y1); + } + + private void emitLineToRev(final double x1, final double y1) { + reverse.pushLine(x1, y1); + } + + private void emitLineTo(final double x1, final double y1, + final boolean rev) + { + if (rev) { + emitLineToRev(x1, y1); + } else { + emitLineTo(x1, y1); + } + } + + private void emitQuadTo(final double x1, final double y1, + final double x2, final double y2) + { + out.quadTo(x1, y1, x2, y2); + } + + private void emitQuadToRev(final double x0, final double y0, + final double x1, final double y1) + { + reverse.pushQuad(x0, y0, x1, y1); + } + + private void emitDCurveTo(final double x1, final double y1, + final double x2, final double y2, + final double x3, final double y3) + { + out.curveTo(x1, y1, x2, y2, x3, y3); + } + + private void emitDCurveToRev(final double x0, final double y0, + final double x1, final double y1, + final double x2, final double y2) + { + reverse.pushCubic(x0, y0, x1, y1, x2, y2); + } + + private void emitDCurveTo(final double x0, final double y0, + final double x1, final double y1, + final double x2, final double y2, + final double x3, final double y3, final boolean rev) + { + if (rev) { + reverse.pushCubic(x0, y0, x1, y1, x2, y2); + } else { + out.curveTo(x1, y1, x2, y2, x3, y3); + } + } + + private void emitClose() { + out.closePath(); + } + + private void drawJoin(double pdx, double pdy, + double x0, double y0, + double dx, double dy, + double omx, double omy, + double mx, double my) + { + if (prev != DRAWING_OP_TO) { + emitMoveTo(x0 + mx, y0 + my); + this.sdx = dx; + this.sdy = dy; + this.smx = mx; + this.smy = my; + } else { + boolean cw = isCW(pdx, pdy, dx, dy); + if (joinStyle == JOIN_MITER) { + drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw); + } else if (joinStyle == JOIN_ROUND) { + drawRoundJoin(x0, y0, + omx, omy, + mx, my, cw, + ROUND_JOIN_THRESHOLD); + } + emitLineTo(x0, y0, !cw); + } + prev = DRAWING_OP_TO; + } + + private static boolean within(final double x1, final double y1, + final double x2, final double y2, + final double ERR) + { + assert ERR > 0 : ""; + // compare taxicab distance. ERR will always be small, so using + // true distance won't give much benefit + return (DHelpers.within(x1, x2, ERR) && // we want to avoid calling Math.abs + DHelpers.within(y1, y2, ERR)); // this is just as good. + } + + private void getLineOffsets(double x1, double y1, + double x2, double y2, + double[] left, double[] right) { + computeOffset(x2 - x1, y2 - y1, lineWidth2, offset0); + final double mx = offset0[0]; + final double my = offset0[1]; + left[0] = x1 + mx; + left[1] = y1 + my; + left[2] = x2 + mx; + left[3] = y2 + my; + right[0] = x1 - mx; + right[1] = y1 - my; + right[2] = x2 - mx; + right[3] = y2 - my; + } + + private int computeOffsetCubic(double[] pts, final int off, + double[] leftOff, double[] rightOff) + { + // if p1=p2 or p3=p4 it means that the derivative at the endpoint + // vanishes, which creates problems with computeOffset. Usually + // this happens when this stroker object is trying to winden + // a curve with a cusp. What happens is that curveTo splits + // the input curve at the cusp, and passes it to this function. + // because of inaccuracies in the splitting, we consider points + // equal if they're very close to each other. + final double x1 = pts[off + 0], y1 = pts[off + 1]; + final double x2 = pts[off + 2], y2 = pts[off + 3]; + final double x3 = pts[off + 4], y3 = pts[off + 5]; + final double x4 = pts[off + 6], y4 = pts[off + 7]; + + double dx4 = x4 - x3; + double dy4 = y4 - y3; + double dx1 = x2 - x1; + double dy1 = y2 - y1; + + // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4, + // in which case ignore if p1 == p2 + final boolean p1eqp2 = within(x1,y1,x2,y2, 6D * Math.ulp(y2)); + final boolean p3eqp4 = within(x3,y3,x4,y4, 6D * Math.ulp(y4)); + if (p1eqp2 && p3eqp4) { + getLineOffsets(x1, y1, x4, y4, leftOff, rightOff); + return 4; + } else if (p1eqp2) { + dx1 = x3 - x1; + dy1 = y3 - y1; + } else if (p3eqp4) { + dx4 = x4 - x2; + dy4 = y4 - y2; + } + + // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line + double dotsq = (dx1 * dx4 + dy1 * dy4); + dotsq *= dotsq; + double l1sq = dx1 * dx1 + dy1 * dy1, l4sq = dx4 * dx4 + dy4 * dy4; + if (DHelpers.within(dotsq, l1sq * l4sq, 4D * Math.ulp(dotsq))) { + getLineOffsets(x1, y1, x4, y4, leftOff, rightOff); + return 4; + } + +// What we're trying to do in this function is to approximate an ideal +// offset curve (call it I) of the input curve B using a bezier curve Bp. +// The constraints I use to get the equations are: +// +// 1. The computed curve Bp should go through I(0) and I(1). These are +// x1p, y1p, x4p, y4p, which are p1p and p4p. We still need to find +// 4 variables: the x and y components of p2p and p3p (i.e. x2p, y2p, x3p, y3p). +// +// 2. Bp should have slope equal in absolute value to I at the endpoints. So, +// (by the way, the operator || in the comments below means "aligned with". +// It is defined on vectors, so when we say I'(0) || Bp'(0) we mean that +// vectors I'(0) and Bp'(0) are aligned, which is the same as saying +// that the tangent lines of I and Bp at 0 are parallel. Mathematically +// this means (I'(t) || Bp'(t)) <==> (I'(t) = c * Bp'(t)) where c is some +// nonzero constant.) +// I'(0) || Bp'(0) and I'(1) || Bp'(1). Obviously, I'(0) || B'(0) and +// I'(1) || B'(1); therefore, Bp'(0) || B'(0) and Bp'(1) || B'(1). +// We know that Bp'(0) || (p2p-p1p) and Bp'(1) || (p4p-p3p) and the same +// is true for any bezier curve; therefore, we get the equations +// (1) p2p = c1 * (p2-p1) + p1p +// (2) p3p = c2 * (p4-p3) + p4p +// We know p1p, p4p, p2, p1, p3, and p4; therefore, this reduces the number +// of unknowns from 4 to 2 (i.e. just c1 and c2). +// To eliminate these 2 unknowns we use the following constraint: +// +// 3. Bp(0.5) == I(0.5). Bp(0.5)=(x,y) and I(0.5)=(xi,yi), and I should note +// that I(0.5) is *the only* reason for computing dxm,dym. This gives us +// (3) Bp(0.5) = (p1p + 3 * (p2p + p3p) + p4p)/8, which is equivalent to +// (4) p2p + p3p = (Bp(0.5)*8 - p1p - p4p) / 3 +// We can substitute (1) and (2) from above into (4) and we get: +// (5) c1*(p2-p1) + c2*(p4-p3) = (Bp(0.5)*8 - p1p - p4p)/3 - p1p - p4p +// which is equivalent to +// (6) c1*(p2-p1) + c2*(p4-p3) = (4/3) * (Bp(0.5) * 2 - p1p - p4p) +// +// The right side of this is a 2D vector, and we know I(0.5), which gives us +// Bp(0.5), which gives us the value of the right side. +// The left side is just a matrix vector multiplication in disguise. It is +// +// [x2-x1, x4-x3][c1] +// [y2-y1, y4-y3][c2] +// which, is equal to +// [dx1, dx4][c1] +// [dy1, dy4][c2] +// At this point we are left with a simple linear system and we solve it by +// getting the inverse of the matrix above. Then we use [c1,c2] to compute +// p2p and p3p. + + double x = (x1 + 3D * (x2 + x3) + x4) / 8D; + double y = (y1 + 3D * (y2 + y3) + y4) / 8D; + // (dxm,dym) is some tangent of B at t=0.5. This means it's equal to + // c*B'(0.5) for some constant c. + double dxm = x3 + x4 - x1 - x2, dym = y3 + y4 - y1 - y2; + + // this computes the offsets at t=0, 0.5, 1, using the property that + // for any bezier curve the vectors p2-p1 and p4-p3 are parallel to + // the (dx/dt, dy/dt) vectors at the endpoints. + computeOffset(dx1, dy1, lineWidth2, offset0); + computeOffset(dxm, dym, lineWidth2, offset1); + computeOffset(dx4, dy4, lineWidth2, offset2); + double x1p = x1 + offset0[0]; // start + double y1p = y1 + offset0[1]; // point + double xi = x + offset1[0]; // interpolation + double yi = y + offset1[1]; // point + double x4p = x4 + offset2[0]; // end + double y4p = y4 + offset2[1]; // point + + double invdet43 = 4D / (3D * (dx1 * dy4 - dy1 * dx4)); + + double two_pi_m_p1_m_p4x = 2D * xi - x1p - x4p; + double two_pi_m_p1_m_p4y = 2D * yi - y1p - y4p; + double c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y); + double c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x); + + double x2p, y2p, x3p, y3p; + x2p = x1p + c1*dx1; + y2p = y1p + c1*dy1; + x3p = x4p + c2*dx4; + y3p = y4p + c2*dy4; + + leftOff[0] = x1p; leftOff[1] = y1p; + leftOff[2] = x2p; leftOff[3] = y2p; + leftOff[4] = x3p; leftOff[5] = y3p; + leftOff[6] = x4p; leftOff[7] = y4p; + + x1p = x1 - offset0[0]; y1p = y1 - offset0[1]; + xi = xi - 2D * offset1[0]; yi = yi - 2D * offset1[1]; + x4p = x4 - offset2[0]; y4p = y4 - offset2[1]; + + two_pi_m_p1_m_p4x = 2D * xi - x1p - x4p; + two_pi_m_p1_m_p4y = 2D * yi - y1p - y4p; + c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y); + c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x); + + x2p = x1p + c1*dx1; + y2p = y1p + c1*dy1; + x3p = x4p + c2*dx4; + y3p = y4p + c2*dy4; + + rightOff[0] = x1p; rightOff[1] = y1p; + rightOff[2] = x2p; rightOff[3] = y2p; + rightOff[4] = x3p; rightOff[5] = y3p; + rightOff[6] = x4p; rightOff[7] = y4p; + return 8; + } + + // compute offset curves using bezier spline through t=0.5 (i.e. + // ComputedDCurve(0.5) == IdealParallelDCurve(0.5)) + // return the kind of curve in the right and left arrays. + private int computeOffsetQuad(double[] pts, final int off, + double[] leftOff, double[] rightOff) + { + final double x1 = pts[off + 0], y1 = pts[off + 1]; + final double x2 = pts[off + 2], y2 = pts[off + 3]; + final double x3 = pts[off + 4], y3 = pts[off + 5]; + + final double dx3 = x3 - x2; + final double dy3 = y3 - y2; + final double dx1 = x2 - x1; + final double dy1 = y2 - y1; + + // if p1=p2 or p3=p4 it means that the derivative at the endpoint + // vanishes, which creates problems with computeOffset. Usually + // this happens when this stroker object is trying to winden + // a curve with a cusp. What happens is that curveTo splits + // the input curve at the cusp, and passes it to this function. + // because of inaccuracies in the splitting, we consider points + // equal if they're very close to each other. + + // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4, + // in which case ignore. + final boolean p1eqp2 = within(x1,y1,x2,y2, 6D * Math.ulp(y2)); + final boolean p2eqp3 = within(x2,y2,x3,y3, 6D * Math.ulp(y3)); + if (p1eqp2 || p2eqp3) { + getLineOffsets(x1, y1, x3, y3, leftOff, rightOff); + return 4; + } + + // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line + double dotsq = (dx1 * dx3 + dy1 * dy3); + dotsq *= dotsq; + double l1sq = dx1 * dx1 + dy1 * dy1, l3sq = dx3 * dx3 + dy3 * dy3; + if (DHelpers.within(dotsq, l1sq * l3sq, 4D * Math.ulp(dotsq))) { + getLineOffsets(x1, y1, x3, y3, leftOff, rightOff); + return 4; + } + + // this computes the offsets at t=0, 0.5, 1, using the property that + // for any bezier curve the vectors p2-p1 and p4-p3 are parallel to + // the (dx/dt, dy/dt) vectors at the endpoints. + computeOffset(dx1, dy1, lineWidth2, offset0); + computeOffset(dx3, dy3, lineWidth2, offset1); + + double x1p = x1 + offset0[0]; // start + double y1p = y1 + offset0[1]; // point + double x3p = x3 + offset1[0]; // end + double y3p = y3 + offset1[1]; // point + safecomputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, leftOff, 2); + leftOff[0] = x1p; leftOff[1] = y1p; + leftOff[4] = x3p; leftOff[5] = y3p; + + x1p = x1 - offset0[0]; y1p = y1 - offset0[1]; + x3p = x3 - offset1[0]; y3p = y3 - offset1[1]; + safecomputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, rightOff, 2); + rightOff[0] = x1p; rightOff[1] = y1p; + rightOff[4] = x3p; rightOff[5] = y3p; + return 6; + } + + // If this class is compiled with ecj, then Hotspot crashes when OSR + // compiling this function. See bugs 7004570 and 6675699 + // TODO: until those are fixed, we should work around that by + // manually inlining this into curveTo and quadTo. +/******************************* WORKAROUND ********************************** + private void somethingTo(final int type) { + // need these so we can update the state at the end of this method + final double xf = middle[type-2], yf = middle[type-1]; + double dxs = middle[2] - middle[0]; + double dys = middle[3] - middle[1]; + double dxf = middle[type - 2] - middle[type - 4]; + double dyf = middle[type - 1] - middle[type - 3]; + switch(type) { + case 6: + if ((dxs == 0D && dys == 0D) || + (dxf == 0D && dyf == 0D)) { + dxs = dxf = middle[4] - middle[0]; + dys = dyf = middle[5] - middle[1]; + } + break; + case 8: + boolean p1eqp2 = (dxs == 0D && dys == 0D); + boolean p3eqp4 = (dxf == 0D && dyf == 0D); + if (p1eqp2) { + dxs = middle[4] - middle[0]; + dys = middle[5] - middle[1]; + if (dxs == 0D && dys == 0D) { + dxs = middle[6] - middle[0]; + dys = middle[7] - middle[1]; + } + } + if (p3eqp4) { + dxf = middle[6] - middle[2]; + dyf = middle[7] - middle[3]; + if (dxf == 0D && dyf == 0D) { + dxf = middle[6] - middle[0]; + dyf = middle[7] - middle[1]; + } + } + } + if (dxs == 0D && dys == 0D) { + // this happens iff the "curve" is just a point + lineTo(middle[0], middle[1]); + return; + } + // if these vectors are too small, normalize them, to avoid future + // precision problems. + if (Math.abs(dxs) < 0.1D && Math.abs(dys) < 0.1D) { + double len = Math.sqrt(dxs*dxs + dys*dys); + dxs /= len; + dys /= len; + } + if (Math.abs(dxf) < 0.1D && Math.abs(dyf) < 0.1D) { + double len = Math.sqrt(dxf*dxf + dyf*dyf); + dxf /= len; + dyf /= len; + } + + computeOffset(dxs, dys, lineWidth2, offset0); + final double mx = offset0[0]; + final double my = offset0[1]; + drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, mx, my); + + int nSplits = findSubdivPoints(curve, middle, subdivTs, type, lineWidth2); + + int kind = 0; + BreakPtrIterator it = curve.breakPtsAtTs(middle, type, subdivTs, nSplits); + while(it.hasNext()) { + int curDCurveOff = it.next(); + + switch (type) { + case 8: + kind = computeOffsetCubic(middle, curDCurveOff, lp, rp); + break; + case 6: + kind = computeOffsetQuad(middle, curDCurveOff, lp, rp); + break; + } + emitLineTo(lp[0], lp[1]); + switch(kind) { + case 8: + emitDCurveTo(lp[2], lp[3], lp[4], lp[5], lp[6], lp[7]); + emitDCurveToRev(rp[0], rp[1], rp[2], rp[3], rp[4], rp[5]); + break; + case 6: + emitQuadTo(lp[2], lp[3], lp[4], lp[5]); + emitQuadToRev(rp[0], rp[1], rp[2], rp[3]); + break; + case 4: + emitLineTo(lp[2], lp[3]); + emitLineTo(rp[0], rp[1], true); + break; + } + emitLineTo(rp[kind - 2], rp[kind - 1], true); + } + + this.cmx = (lp[kind - 2] - rp[kind - 2]) / 2; + this.cmy = (lp[kind - 1] - rp[kind - 1]) / 2; + this.cdx = dxf; + this.cdy = dyf; + this.cx0 = xf; + this.cy0 = yf; + this.prev = DRAWING_OP_TO; + } +****************************** END WORKAROUND *******************************/ + + // finds values of t where the curve in pts should be subdivided in order + // to get good offset curves a distance of w away from the middle curve. + // Stores the points in ts, and returns how many of them there were. + private static int findSubdivPoints(final DCurve c, double[] pts, double[] ts, + final int type, final double w) + { + final double x12 = pts[2] - pts[0]; + final double y12 = pts[3] - pts[1]; + // if the curve is already parallel to either axis we gain nothing + // from rotating it. + if (y12 != 0D && x12 != 0D) { + // we rotate it so that the first vector in the control polygon is + // parallel to the x-axis. This will ensure that rotated quarter + // circles won't be subdivided. + final double hypot = Math.sqrt(x12 * x12 + y12 * y12); + final double cos = x12 / hypot; + final double sin = y12 / hypot; + final double x1 = cos * pts[0] + sin * pts[1]; + final double y1 = cos * pts[1] - sin * pts[0]; + final double x2 = cos * pts[2] + sin * pts[3]; + final double y2 = cos * pts[3] - sin * pts[2]; + final double x3 = cos * pts[4] + sin * pts[5]; + final double y3 = cos * pts[5] - sin * pts[4]; + + switch(type) { + case 8: + final double x4 = cos * pts[6] + sin * pts[7]; + final double y4 = cos * pts[7] - sin * pts[6]; + c.set(x1, y1, x2, y2, x3, y3, x4, y4); + break; + case 6: + c.set(x1, y1, x2, y2, x3, y3); + break; + default: + } + } else { + c.set(pts, type); + } + + int ret = 0; + // we subdivide at values of t such that the remaining rotated + // curves are monotonic in x and y. + ret += c.dxRoots(ts, ret); + ret += c.dyRoots(ts, ret); + // subdivide at inflection points. + if (type == 8) { + // quadratic curves can't have inflection points + ret += c.infPoints(ts, ret); + } + + // now we must subdivide at points where one of the offset curves will have + // a cusp. This happens at ts where the radius of curvature is equal to w. + ret += c.rootsOfROCMinusW(ts, ret, w, 0.0001D); + + ret = DHelpers.filterOutNotInAB(ts, 0, ret, 0.0001D, 0.9999D); + DHelpers.isort(ts, 0, ret); + return ret; + } + + @Override public void curveTo(double x1, double y1, + double x2, double y2, + double x3, double y3) + { + final double[] mid = middle; + + mid[0] = cx0; mid[1] = cy0; + mid[2] = x1; mid[3] = y1; + mid[4] = x2; mid[5] = y2; + mid[6] = x3; mid[7] = y3; + + // inlined version of somethingTo(8); + // See the TODO on somethingTo + + // need these so we can update the state at the end of this method + final double xf = mid[6], yf = mid[7]; + double dxs = mid[2] - mid[0]; + double dys = mid[3] - mid[1]; + double dxf = mid[6] - mid[4]; + double dyf = mid[7] - mid[5]; + + boolean p1eqp2 = (dxs == 0D && dys == 0D); + boolean p3eqp4 = (dxf == 0D && dyf == 0D); + if (p1eqp2) { + dxs = mid[4] - mid[0]; + dys = mid[5] - mid[1]; + if (dxs == 0D && dys == 0D) { + dxs = mid[6] - mid[0]; + dys = mid[7] - mid[1]; + } + } + if (p3eqp4) { + dxf = mid[6] - mid[2]; + dyf = mid[7] - mid[3]; + if (dxf == 0D && dyf == 0D) { + dxf = mid[6] - mid[0]; + dyf = mid[7] - mid[1]; + } + } + if (dxs == 0D && dys == 0D) { + // this happens if the "curve" is just a point + lineTo(mid[0], mid[1]); + return; + } + + // if these vectors are too small, normalize them, to avoid future + // precision problems. + if (Math.abs(dxs) < 0.1D && Math.abs(dys) < 0.1D) { + double len = Math.sqrt(dxs*dxs + dys*dys); + dxs /= len; + dys /= len; + } + if (Math.abs(dxf) < 0.1D && Math.abs(dyf) < 0.1D) { + double len = Math.sqrt(dxf*dxf + dyf*dyf); + dxf /= len; + dyf /= len; + } + + computeOffset(dxs, dys, lineWidth2, offset0); + drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]); + + final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2); + + double prevT = 0D; + for (int i = 0, off = 0; i < nSplits; i++, off += 6) { + final double t = subdivTs[i]; + DHelpers.subdivideCubicAt((t - prevT) / (1D - prevT), + mid, off, mid, off, mid, off + 6); + prevT = t; + } + + final double[] l = lp; + final double[] r = rp; + + int kind = 0; + for (int i = 0, off = 0; i <= nSplits; i++, off += 6) { + kind = computeOffsetCubic(mid, off, l, r); + + emitLineTo(l[0], l[1]); + + switch(kind) { + case 8: + emitDCurveTo(l[2], l[3], l[4], l[5], l[6], l[7]); + emitDCurveToRev(r[0], r[1], r[2], r[3], r[4], r[5]); + break; + case 4: + emitLineTo(l[2], l[3]); + emitLineToRev(r[0], r[1]); + break; + default: + } + emitLineToRev(r[kind - 2], r[kind - 1]); + } + + this.cmx = (l[kind - 2] - r[kind - 2]) / 2D; + this.cmy = (l[kind - 1] - r[kind - 1]) / 2D; + this.cdx = dxf; + this.cdy = dyf; + this.cx0 = xf; + this.cy0 = yf; + this.prev = DRAWING_OP_TO; + } + + @Override public void quadTo(double x1, double y1, double x2, double y2) { + final double[] mid = middle; + + mid[0] = cx0; mid[1] = cy0; + mid[2] = x1; mid[3] = y1; + mid[4] = x2; mid[5] = y2; + + // inlined version of somethingTo(8); + // See the TODO on somethingTo + + // need these so we can update the state at the end of this method + final double xf = mid[4], yf = mid[5]; + double dxs = mid[2] - mid[0]; + double dys = mid[3] - mid[1]; + double dxf = mid[4] - mid[2]; + double dyf = mid[5] - mid[3]; + if ((dxs == 0D && dys == 0D) || (dxf == 0D && dyf == 0D)) { + dxs = dxf = mid[4] - mid[0]; + dys = dyf = mid[5] - mid[1]; + } + if (dxs == 0D && dys == 0D) { + // this happens if the "curve" is just a point + lineTo(mid[0], mid[1]); + return; + } + // if these vectors are too small, normalize them, to avoid future + // precision problems. + if (Math.abs(dxs) < 0.1D && Math.abs(dys) < 0.1D) { + double len = Math.sqrt(dxs*dxs + dys*dys); + dxs /= len; + dys /= len; + } + if (Math.abs(dxf) < 0.1D && Math.abs(dyf) < 0.1D) { + double len = Math.sqrt(dxf*dxf + dyf*dyf); + dxf /= len; + dyf /= len; + } + + computeOffset(dxs, dys, lineWidth2, offset0); + drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]); + + int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2); + + double prevt = 0D; + for (int i = 0, off = 0; i < nSplits; i++, off += 4) { + final double t = subdivTs[i]; + DHelpers.subdivideQuadAt((t - prevt) / (1D - prevt), + mid, off, mid, off, mid, off + 4); + prevt = t; + } + + final double[] l = lp; + final double[] r = rp; + + int kind = 0; + for (int i = 0, off = 0; i <= nSplits; i++, off += 4) { + kind = computeOffsetQuad(mid, off, l, r); + + emitLineTo(l[0], l[1]); + + switch(kind) { + case 6: + emitQuadTo(l[2], l[3], l[4], l[5]); + emitQuadToRev(r[0], r[1], r[2], r[3]); + break; + case 4: + emitLineTo(l[2], l[3]); + emitLineToRev(r[0], r[1]); + break; + default: + } + emitLineToRev(r[kind - 2], r[kind - 1]); + } + + this.cmx = (l[kind - 2] - r[kind - 2]) / 2D; + this.cmy = (l[kind - 1] - r[kind - 1]) / 2D; + this.cdx = dxf; + this.cdy = dyf; + this.cx0 = xf; + this.cy0 = yf; + this.prev = DRAWING_OP_TO; + } + + // a stack of polynomial curves where each curve shares endpoints with + // adjacent ones. + static final class PolyStack { + private static final byte TYPE_LINETO = (byte) 0; + private static final byte TYPE_QUADTO = (byte) 1; + private static final byte TYPE_CUBICTO = (byte) 2; + + // curves capacity = edges count (8192) = edges x 2 (coords) + private static final int INITIAL_CURVES_COUNT = INITIAL_EDGES_COUNT << 1; + + // types capacity = edges count (4096) + private static final int INITIAL_TYPES_COUNT = INITIAL_EDGES_COUNT; + + double[] curves; + int end; + byte[] curveTypes; + int numDCurves; + + // per-thread renderer context + final DRendererContext rdrCtx; + + // curves ref (dirty) + final DoubleArrayCache.Reference curves_ref; + // curveTypes ref (dirty) + final ByteArrayCache.Reference curveTypes_ref; + + // used marks (stats only) + int curveTypesUseMark; + int curvesUseMark; + + /** + * Constructor + * @param rdrCtx per-thread renderer context + */ + PolyStack(final DRendererContext rdrCtx) { + this.rdrCtx = rdrCtx; + + curves_ref = rdrCtx.newDirtyDoubleArrayRef(INITIAL_CURVES_COUNT); // 32K + curves = curves_ref.initial; + + curveTypes_ref = rdrCtx.newDirtyByteArrayRef(INITIAL_TYPES_COUNT); // 4K + curveTypes = curveTypes_ref.initial; + numDCurves = 0; + end = 0; + + if (DO_STATS) { + curveTypesUseMark = 0; + curvesUseMark = 0; + } + } + + /** + * Disposes this PolyStack: + * clean up before reusing this instance + */ + void dispose() { + end = 0; + numDCurves = 0; + + if (DO_STATS) { + rdrCtx.stats.stat_rdr_poly_stack_types.add(curveTypesUseMark); + rdrCtx.stats.stat_rdr_poly_stack_curves.add(curvesUseMark); + rdrCtx.stats.hist_rdr_poly_stack_curves.add(curvesUseMark); + + // reset marks + curveTypesUseMark = 0; + curvesUseMark = 0; + } + + // Return arrays: + // curves and curveTypes are kept dirty + curves = curves_ref.putArray(curves); + curveTypes = curveTypes_ref.putArray(curveTypes); + } + + private void ensureSpace(final int n) { + // use substraction to avoid integer overflow: + if (curves.length - end < n) { + if (DO_STATS) { + rdrCtx.stats.stat_array_stroker_polystack_curves + .add(end + n); + } + curves = curves_ref.widenArray(curves, end, end + n); + } + if (curveTypes.length <= numDCurves) { + if (DO_STATS) { + rdrCtx.stats.stat_array_stroker_polystack_curveTypes + .add(numDCurves + 1); + } + curveTypes = curveTypes_ref.widenArray(curveTypes, + numDCurves, + numDCurves + 1); + } + } + + void pushCubic(double x0, double y0, + double x1, double y1, + double x2, double y2) + { + ensureSpace(6); + curveTypes[numDCurves++] = TYPE_CUBICTO; + // we reverse the coordinate order to make popping easier + final double[] _curves = curves; + int e = end; + _curves[e++] = x2; _curves[e++] = y2; + _curves[e++] = x1; _curves[e++] = y1; + _curves[e++] = x0; _curves[e++] = y0; + end = e; + } + + void pushQuad(double x0, double y0, + double x1, double y1) + { + ensureSpace(4); + curveTypes[numDCurves++] = TYPE_QUADTO; + final double[] _curves = curves; + int e = end; + _curves[e++] = x1; _curves[e++] = y1; + _curves[e++] = x0; _curves[e++] = y0; + end = e; + } + + void pushLine(double x, double y) { + ensureSpace(2); + curveTypes[numDCurves++] = TYPE_LINETO; + curves[end++] = x; curves[end++] = y; + } + + void popAll(DPathConsumer2D io) { + if (DO_STATS) { + // update used marks: + if (numDCurves > curveTypesUseMark) { + curveTypesUseMark = numDCurves; + } + if (end > curvesUseMark) { + curvesUseMark = end; + } + } + final byte[] _curveTypes = curveTypes; + final double[] _curves = curves; + int nc = numDCurves; + int e = end; + + while (nc != 0) { + switch(_curveTypes[--nc]) { + case TYPE_LINETO: + e -= 2; + io.lineTo(_curves[e], _curves[e+1]); + continue; + case TYPE_QUADTO: + e -= 4; + io.quadTo(_curves[e+0], _curves[e+1], + _curves[e+2], _curves[e+3]); + continue; + case TYPE_CUBICTO: + e -= 6; + io.curveTo(_curves[e+0], _curves[e+1], + _curves[e+2], _curves[e+3], + _curves[e+4], _curves[e+5]); + continue; + default: + } + } + numDCurves = 0; + end = 0; + } + + @Override + public String toString() { + String ret = ""; + int nc = numDCurves; + int last = end; + int len; + while (nc != 0) { + switch(curveTypes[--nc]) { + case TYPE_LINETO: + len = 2; + ret += "line: "; + break; + case TYPE_QUADTO: + len = 4; + ret += "quad: "; + break; + case TYPE_CUBICTO: + len = 6; + ret += "cubic: "; + break; + default: + len = 0; + } + last -= len; + ret += Arrays.toString(Arrays.copyOfRange(curves, last, last+len)) + + "\n"; + } + return ret; + } + } +} --- /dev/null 2016-11-30 21:27:13.355352085 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/DTransformingPathConsumer2D.java 2016-11-30 22:48:53.622420591 +0100 @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2007, 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.marlin; + + +import com.sun.javafx.geom.transform.BaseTransform; + +public final class DTransformingPathConsumer2D { + + DTransformingPathConsumer2D() { + // used by DRendererContext + } + + // recycled DPathConsumer2D instances from deltaTransformConsumer() + private final DeltaScaleFilter dt_DeltaScaleFilter = new DeltaScaleFilter(); + private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter(); + + public DPathConsumer2D deltaTransformConsumer(DPathConsumer2D out, + BaseTransform at) + { + if (at == null) { + return out; + } + double mxx = at.getMxx(); + double mxy = at.getMxy(); + double myx = at.getMyx(); + double myy = at.getMyy(); + + if (mxy == 0D && myx == 0D) { + if (mxx == 1D && myy == 1D) { + return out; + } else { + return dt_DeltaScaleFilter.init(out, mxx, myy); + } + } else { + return dt_DeltaTransformFilter.init(out, mxx, mxy, myx, myy); + } + } + + // recycled DPathConsumer2D instances from inverseDeltaTransformConsumer() + private final DeltaScaleFilter iv_DeltaScaleFilter = new DeltaScaleFilter(); + private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter(); + + public DPathConsumer2D inverseDeltaTransformConsumer(DPathConsumer2D out, + BaseTransform at) + { + if (at == null) { + return out; + } + double mxx = at.getMxx(); + double mxy = at.getMxy(); + double myx = at.getMyx(); + double myy = at.getMyy(); + + if (mxy == 0D && myx == 0D) { + if (mxx == 1D && myy == 1D) { + return out; + } else { + return iv_DeltaScaleFilter.init(out, 1.0D/mxx, 1.0D/myy); + } + } else { + double det = mxx * myy - mxy * myx; + return iv_DeltaTransformFilter.init(out, + myy / det, + -mxy / det, + -myx / det, + mxx / det); + } + } + + + static final class DeltaScaleFilter implements DPathConsumer2D { + private DPathConsumer2D out; + private double sx, sy; + + DeltaScaleFilter() {} + + DeltaScaleFilter init(DPathConsumer2D out, + double mxx, double myy) + { + this.out = out; + sx = mxx; + sy = myy; + return this; // fluent API + } + + @Override + public void moveTo(double x0, double y0) { + out.moveTo(x0 * sx, y0 * sy); + } + + @Override + public void lineTo(double x1, double y1) { + out.lineTo(x1 * sx, y1 * sy); + } + + @Override + public void quadTo(double x1, double y1, + double x2, double y2) + { + out.quadTo(x1 * sx, y1 * sy, + x2 * sx, y2 * sy); + } + + @Override + public void curveTo(double x1, double y1, + double x2, double y2, + double x3, double y3) + { + out.curveTo(x1 * sx, y1 * sy, + x2 * sx, y2 * sy, + x3 * sx, y3 * sy); + } + + @Override + public void closePath() { + out.closePath(); + } + + @Override + public void pathDone() { + out.pathDone(); + } + } + + static final class DeltaTransformFilter implements DPathConsumer2D { + private DPathConsumer2D out; + private double mxx, mxy, myx, myy; + + DeltaTransformFilter() {} + + DeltaTransformFilter init(DPathConsumer2D out, + double mxx, double mxy, + double myx, double myy) + { + this.out = out; + this.mxx = mxx; + this.mxy = mxy; + this.myx = myx; + this.myy = myy; + return this; // fluent API + } + + @Override + public void moveTo(double x0, double y0) { + out.moveTo(x0 * mxx + y0 * mxy, + x0 * myx + y0 * myy); + } + + @Override + public void lineTo(double x1, double y1) { + out.lineTo(x1 * mxx + y1 * mxy, + x1 * myx + y1 * myy); + } + + @Override + public void quadTo(double x1, double y1, + double x2, double y2) + { + out.quadTo(x1 * mxx + y1 * mxy, + x1 * myx + y1 * myy, + x2 * mxx + y2 * mxy, + x2 * myx + y2 * myy); + } + + @Override + public void curveTo(double x1, double y1, + double x2, double y2, + double x3, double y3) + { + out.curveTo(x1 * mxx + y1 * mxy, + x1 * myx + y1 * myy, + x2 * mxx + y2 * mxy, + x2 * myx + y2 * myy, + x3 * mxx + y3 * mxy, + x3 * myx + y3 * myy); + } + + @Override + public void closePath() { + out.closePath(); + } + + @Override + public void pathDone() { + out.pathDone(); + } + } +} --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/FloatArrayCache.java 2016-11-30 22:48:54.326420646 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/FloatArrayCache.java 2016-11-30 22:48:54.098420629 +0100 @@ -46,7 +46,7 @@ // % sed -e 's/(b\yte)[ ]*//g' -e 's/b\yte/int/g' -e 's/B\yte/Int/g' < B\yteArrayCache.java > IntArrayCache.java // % sed -e 's/(b\yte)[ ]*/(float) /g' -e 's/b\yte/float/g' -e 's/B\yte/Float/g' < B\yteArrayCache.java > FloatArrayCache.java -final class FloatArrayCache implements MarlinConst { +public final class FloatArrayCache implements MarlinConst { final boolean clean; private final int bucketCapacity; @@ -246,8 +246,8 @@ } } - static void check(final float[] array, final int fromIndex, - final int toIndex, final float value) + public static void check(final float[] array, final int fromIndex, + final int toIndex, final float value) { if (DO_CHECKS) { // check zero on full array: --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/FloatMath.java 2016-11-30 22:48:54.798420683 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/FloatMath.java 2016-11-30 22:48:54.570420666 +0100 @@ -44,6 +44,11 @@ return (a >= b) ? a : b; } + static double max(final double a, final double b) { + // no NaN handling + return (a >= b) ? a : b; + } + public static int max(final int a, final int b) { return (a >= b) ? a : b; } @@ -73,6 +78,26 @@ } /** + * Faster alternative to ceil(double) optimized for the integer domain + * and supporting NaN and +/-Infinity. + * + * @param a a value. + * @return the largest (closest to positive infinity) integer value + * that less than or equal to the argument and is equal to a mathematical + * integer. + */ + public static int ceil_int(final double a) { + final int intpart = (int) a; + + if (a <= intpart + || (CHECK_OVERFLOW && intpart == Integer.MAX_VALUE) + || CHECK_NAN && Double.isNaN(a)) { + return intpart; + } + return intpart + 1; + } + + /** * Faster alternative to floor(float) optimized for the integer domain * and supporting NaN and +/-Infinity. * @@ -90,5 +115,25 @@ return intpart; } return intpart - 1; + } + + /** + * Faster alternative to floor(double) optimized for the integer domain + * and supporting NaN and +/-Infinity. + * + * @param a a value. + * @return the largest (closest to positive infinity) floating-point value + * that less than or equal to the argument and is equal to a mathematical + * integer. + */ + public static int floor_int(final double a) { + final int intpart = (int) a; + + if (a >= intpart + || (CHECK_OVERFLOW && intpart == Integer.MIN_VALUE) + || CHECK_NAN && Double.isNaN(a)) { + return intpart; + } + return intpart - 1; } } --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/IntArrayCache.java 2016-11-30 22:48:55.270420720 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/IntArrayCache.java 2016-11-30 22:48:55.042420701 +0100 @@ -236,8 +236,8 @@ return (int[]) OffHeapArray.UNSAFE.allocateUninitializedArray(int.class, length); } - public static void fill(final int[] array, final int fromIndex, - final int toIndex, final int value) + static void fill(final int[] array, final int fromIndex, + final int toIndex, final int value) { // clear array data: Arrays.fill(array, fromIndex, toIndex, value); --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/MarlinRenderingEngine.java 2016-11-30 22:48:55.742420757 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/MarlinRenderingEngine.java 2016-11-30 22:48:55.514420738 +0100 @@ -204,6 +204,9 @@ logInfo("CUB_INC_BND = " + Renderer.CUB_INC_BND); logInfo("QUAD_DEC_BND = " + Renderer.QUAD_DEC_BND); + logInfo("USE_SUBDIVIDE_QUAD = " + RendererNoAA.USE_SUBDIVIDE_QUAD); + logInfo("QUAD_ERR_SUBPIX = " + RendererNoAA.QUAD_ERR_SUBPIX); + logInfo("INITIAL_EDGES_CAPACITY = " + MarlinConst.INITIAL_EDGES_CAPACITY); logInfo("INITIAL_CROSSING_COUNT = " --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/MaskMarlinAlphaConsumer.java 2016-11-30 22:48:56.214420794 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/MaskMarlinAlphaConsumer.java 2016-11-30 22:48:55.986420775 +0100 @@ -248,6 +248,8 @@ // traverse flagged blocks: final int blkW = (from >> _BLK_SIZE_LG); final int blkE = (ato >> _BLK_SIZE_LG) + 1; + // ensure last block flag = 0 to process final block: + blkFlags[blkE] = 0; // Perform run-length encoding and store results in the piscesCache int curAlpha = 0; @@ -290,7 +292,6 @@ i = cx; } else { val = _unsafe.getByte(addr_alpha + curAlpha); - do { out[off + i] = val; i++; @@ -306,12 +307,13 @@ } // Process remaining span: - val = _unsafe.getByte(addr_alpha + curAlpha); - - do { - out[off + i] = val; - i++; - } while (i < ato); + if (curAlpha != 0) { + val = _unsafe.getByte(addr_alpha + curAlpha); + while (i < ato) { + out[off + i] = val; + i++; + } + } } else { int i = 0; @@ -345,7 +347,6 @@ // fill span: if (cx != i) { val = _unsafe.getByte(addr_alpha + curAlpha); - do { out[off + i] = val; i++; @@ -360,12 +361,13 @@ } // Process remaining span: - val = _unsafe.getByte(addr_alpha + curAlpha); - - do { - out[off + i] = val; - i++; - } while (i < ato); + if (curAlpha != 0) { + val = _unsafe.getByte(addr_alpha + curAlpha); + while (i < ato) { + out[off + i] = val; + i++; + } + } while (i < w) { out[off + i] = 0; --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/Renderer.java 2016-11-30 22:48:56.690420830 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/Renderer.java 2016-11-30 22:48:56.462420813 +0100 @@ -105,6 +105,13 @@ public static final float QUAD_DEC_BND = 8f * QUAD_DEC_ERR_SUBPIX; + public static final boolean USE_SUBDIVIDE_QUAD = false; + public static final int SUBDIVIDE_MAX = 20; + + public static final float QUAD_ERR_SUBPIX = 1f / 16f; + public static final float MAX_FLAT_SQ + = 4f * QUAD_ERR_SUBPIX * QUAD_ERR_SUBPIX; // x4 + ////////////////////////////////////////////////////////////////////////////// // SCAN LINE ////////////////////////////////////////////////////////////////////////////// @@ -694,15 +701,120 @@ } @Override - public void quadTo(float x1, float y1, float x2, float y2) { - final float xe = tosubpixx(x2); - final float ye = tosubpixy(y2); - curve.set(x0, y0, tosubpixx(x1), tosubpixy(y1), xe, ye); - quadBreakIntoLinesAndAdd(x0, y0, curve, xe, ye); + public void quadTo(float pix_x1, float pix_y1, + float pix_x2, float pix_y2) + { + final float cx1 = tosubpixx(pix_x1); + final float cy1 = tosubpixy(pix_y1); + + final float xe = tosubpixx(pix_x2); + final float ye = tosubpixy(pix_y2); + + if (USE_SUBDIVIDE_QUAD) { + subdivideQuad(0, x0, y0, cx1, cy1, xe, ye); + } else { + curve.set(x0, y0, cx1, cy1, xe, ye); + quadBreakIntoLinesAndAdd(x0, y0, curve, xe, ye); + } x0 = xe; y0 = ye; } + void subdivideQuad(final int level, + final float x0, final float y0, + final float x1, final float y1, + final float x2, final float y2) + { + if (level < SUBDIVIDE_MAX) { + + /* Test if the curve is flat enough for insertion. */ + + // use Roger Willcocks bezier flatness criterion +// var tolerance:Number = 4*tol*tol; + + final float ux = 2f * x1 - x0 - x2; + + final float uy = 2f * y1 - y0 - y2; + + if (ux * ux + uy * uy > MAX_FLAT_SQ) { +/* + AGG + float dx = x2 - x0; + float dy = y2 - y0; + final float dist = Math.abs( (x1 - x2) * dy - (y1 - y2) * dx); + // TODO: check collinearity ? +*/ + +// if (level == 0 || dist * dist > MAX_FLAT_SQ * (dx * dx + dy * dy)) { +// if (ptSegDistSq(x0, y0, x2, y2, x1, y1) > MAX_FLAT_SQ) { + final float cx01 = (x0 + x1) / 2.0f; + final float cx12 = (x1 + x2) / 2.0f; + final float cx012 = (cx01 + cx12) / 2.0f; + + final float cy01 = (y0 + y1) / 2.0f; + final float cy12 = (y1 + y2) / 2.0f; + final float cy012 = (cy01 + cy12) / 2.0f; + + subdivideQuad(level + 1, x0, y0, cx01, cy01, cx012, cy012); + subdivideQuad(level + 1, cx012, cy012, cx12, cy12, x2, y2); + return; + } + } + + addLine(x0, y0, x2, y2); + } + + public static float ptSegDistSq(float x1, float y1, + float x2, float y2, + float px, float py) + { + // Adjust vectors relative to x1,y1 + // x2,y2 becomes relative vector from x1,y1 to end of segment + x2 -= x1; + y2 -= y1; + // px,py becomes relative vector from x1,y1 to test point + px -= x1; + py -= y1; + float dotprod = px * x2 + py * y2; + float projlenSq; + if (dotprod <= 0f) { + // px,py is on the side of x1,y1 away from x2,y2 + // distance to segment is length of px,py vector + // "length of its (clipped) projection" is now 0.0 + projlenSq = 0f; + } else { + // switch to backwards vectors relative to x2,y2 + // x2,y2 are already the negative of x1,y1=>x2,y2 + // to get px,py to be the negative of px,py=>x2,y2 + // the dot product of two negated vectors is the same + // as the dot product of the two normal vectors + px = x2 - px; + py = y2 - py; + dotprod = px * x2 + py * y2; + if (dotprod <= 0f) { + // px,py is on the side of x2,y2 away from x1,y1 + // distance to segment is length of (backwards) px,py vector + // "length of its (clipped) projection" is now 0.0 + projlenSq = 0f; + } else { + // px,py is between x1,y1 and x2,y2 + // dotprod is the length of the px,py vector + // projected on the x2,y2=>x1,y1 vector times the + // length of the x2,y2=>x1,y1 vector + projlenSq = dotprod * dotprod / (x2 * x2 + y2 * y2); + } + } + // Distance to line is now the length of the relative point + // vector minus the length of its projection onto the line + // (which is zero if the projection falls outside the range + // of the line segment). + float lenSq = px * px + py * py - projlenSq; + if (lenSq < 0f) { + lenSq = 0f; + } + return lenSq; + } + @Override public void closePath() { addLine(x0, y0, sx0, sy0); --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/RendererContext.java 2016-11-30 22:48:57.182420868 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/RendererContext.java 2016-11-30 22:48:56.954420851 +0100 @@ -25,8 +25,6 @@ package com.sun.marlin; -import java.awt.geom.Path2D; -import java.lang.ref.WeakReference; import java.util.concurrent.atomic.AtomicInteger; import com.sun.util.reentrant.ReentrantContext; import com.sun.javafx.geom.Rectangle; @@ -60,8 +58,6 @@ final Curve curve = new Curve(); // MarlinRenderingEngine.TransformingPathConsumer2D public final TransformingPathConsumer2D transformerPC2D; - // recycled Path2D instance (weak) - private WeakReference refPath2D = null; public final Renderer renderer; private RendererNoAA rendererNoAA = null; public final Stroker stroker; @@ -149,23 +145,6 @@ } } - Path2D.Float getPath2D() { - // resolve reference: - Path2D.Float p2d - = (refPath2D != null) ? refPath2D.get() : null; - - // create a new Path2D ? - if (p2d == null) { - p2d = new Path2D.Float(Path2D.WIND_NON_ZERO, INITIAL_EDGES_COUNT); // 32K - - // update weak reference: - refPath2D = new WeakReference(p2d); - } - // reset the path anyway: - p2d.reset(); - return p2d; - } - public RendererNoAA getRendererNoAA() { if (rendererNoAA == null) { rendererNoAA = new RendererNoAA(this); --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/RendererNoAA.java 2016-11-30 22:48:57.654420905 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/RendererNoAA.java 2016-11-30 22:48:57.426420888 +0100 @@ -90,12 +90,19 @@ // quad break into lines // quadratic error in subpixels private static final float QUAD_DEC_ERR_SUBPIX - = 1f * (1f / 8f); // 1 pixel for typical 1x1 subpixels + = 0.5f * (1f / 8f); // 1 pixel for typical 1x1 subpixels // quadratic bind length to decrement step = 8 * error in subpixels public static final float QUAD_DEC_BND = 8f * QUAD_DEC_ERR_SUBPIX; + public static final boolean USE_SUBDIVIDE_QUAD = false; + public static final int SUBDIVIDE_MAX = 30; + + public static final float QUAD_ERR_SUBPIX = 1f / 16f; + public static final float MAX_FLAT_SQ + = 4f * QUAD_ERR_SUBPIX * QUAD_ERR_SUBPIX; // x4 + ////////////////////////////////////////////////////////////////////////////// // SCAN LINE ////////////////////////////////////////////////////////////////////////////// @@ -683,15 +690,120 @@ } @Override - public void quadTo(float x1, float y1, float x2, float y2) { - final float xe = tosubpixx(x2); - final float ye = tosubpixy(y2); - curve.set(x0, y0, tosubpixx(x1), tosubpixy(y1), xe, ye); - quadBreakIntoLinesAndAdd(x0, y0, curve, xe, ye); + public void quadTo(float pix_x1, float pix_y1, + float pix_x2, float pix_y2) + { + final float cx1 = tosubpixx(pix_x1); + final float cy1 = tosubpixy(pix_y1); + + final float xe = tosubpixx(pix_x2); + final float ye = tosubpixy(pix_y2); + + if (USE_SUBDIVIDE_QUAD) { + subdivideQuad(0, x0, y0, cx1, cy1, xe, ye); + } else { + curve.set(x0, y0, cx1, cy1, xe, ye); + quadBreakIntoLinesAndAdd(x0, y0, curve, xe, ye); + } x0 = xe; y0 = ye; } + void subdivideQuad(final int level, + final float x0, final float y0, + final float x1, final float y1, + final float x2, final float y2) + { + if (level < SUBDIVIDE_MAX) { + + /* Test if the curve is flat enough for insertion. */ + + // use Roger Willcocks bezier flatness criterion +// var tolerance:Number = 4*tol*tol; + + final float ux = 2f * x1 - x0 - x2; + + final float uy = 2f * y1 - y0 - y2; + + if (ux * ux + uy * uy > MAX_FLAT_SQ) { +/* + AGG + float dx = x2 - x0; + float dy = y2 - y0; + final float dist = Math.abs( (x1 - x2) * dy - (y1 - y2) * dx); + // TODO: check collinearity ? +*/ + +// if (level == 0 || dist * dist > MAX_FLAT_SQ * (dx * dx + dy * dy)) { +// if (ptSegDistSq(x0, y0, x2, y2, x1, y1) > MAX_FLAT_SQ) { + final float cx01 = (x0 + x1) / 2.0f; + final float cx12 = (x1 + x2) / 2.0f; + final float cx012 = (cx01 + cx12) / 2.0f; + + final float cy01 = (y0 + y1) / 2.0f; + final float cy12 = (y1 + y2) / 2.0f; + final float cy012 = (cy01 + cy12) / 2.0f; + + subdivideQuad(level + 1, x0, y0, cx01, cy01, cx012, cy012); + subdivideQuad(level + 1, cx012, cy012, cx12, cy12, x2, y2); + return; + } + } + + addLine(x0, y0, x2, y2); + } + + public static float ptSegDistSq(float x1, float y1, + float x2, float y2, + float px, float py) + { + // Adjust vectors relative to x1,y1 + // x2,y2 becomes relative vector from x1,y1 to end of segment + x2 -= x1; + y2 -= y1; + // px,py becomes relative vector from x1,y1 to test point + px -= x1; + py -= y1; + float dotprod = px * x2 + py * y2; + float projlenSq; + if (dotprod <= 0f) { + // px,py is on the side of x1,y1 away from x2,y2 + // distance to segment is length of px,py vector + // "length of its (clipped) projection" is now 0.0 + projlenSq = 0f; + } else { + // switch to backwards vectors relative to x2,y2 + // x2,y2 are already the negative of x1,y1=>x2,y2 + // to get px,py to be the negative of px,py=>x2,y2 + // the dot product of two negated vectors is the same + // as the dot product of the two normal vectors + px = x2 - px; + py = y2 - py; + dotprod = px * x2 + py * y2; + if (dotprod <= 0f) { + // px,py is on the side of x2,y2 away from x1,y1 + // distance to segment is length of (backwards) px,py vector + // "length of its (clipped) projection" is now 0.0 + projlenSq = 0f; + } else { + // px,py is between x1,y1 and x2,y2 + // dotprod is the length of the px,py vector + // projected on the x2,y2=>x1,y1 vector times the + // length of the x2,y2=>x1,y1 vector + projlenSq = dotprod * dotprod / (x2 * x2 + y2 * y2); + } + } + // Distance to line is now the length of the relative point + // vector minus the length of its projection onto the line + // (which is zero if the projection falls outside the range + // of the line segment). + float lenSq = px * px + py * py - projlenSq; + if (lenSq < 0f) { + lenSq = 0f; + } + return lenSq; + } + @Override public void closePath() { addLine(x0, y0, sx0, sy0); --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/TransformingPathConsumer2D.java 2016-11-30 22:48:58.142420943 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/TransformingPathConsumer2D.java 2016-11-30 22:48:57.918420926 +0100 @@ -26,7 +26,6 @@ package com.sun.marlin; import com.sun.javafx.geom.PathConsumer2D; -import com.sun.javafx.geom.Path2D; import com.sun.javafx.geom.transform.BaseTransform; public final class TransformingPathConsumer2D { @@ -35,14 +34,6 @@ // used by RendererContext } - // recycled PathConsumer2D instance from wrapPath2d() - private final Path2DWrapper wp_Path2DWrapper = new Path2DWrapper(); - - PathConsumer2D wrapPath2d(Path2D p2d) - { - return wp_Path2DWrapper.init(p2d); - } - // recycled PathConsumer2D instances from deltaTransformConsumer() private final DeltaScaleFilter dt_DeltaScaleFilter = new DeltaScaleFilter(); private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter(); @@ -218,46 +209,4 @@ out.pathDone(); } } - - static final class Path2DWrapper implements PathConsumer2D { - private Path2D p2d; - - Path2DWrapper() {} - - Path2DWrapper init(Path2D p2d) { - this.p2d = p2d; - return this; - } - - @Override - public void moveTo(float x0, float y0) { - p2d.moveTo(x0, y0); - } - - @Override - public void lineTo(float x1, float y1) { - p2d.lineTo(x1, y1); - } - - @Override - public void closePath() { - p2d.closePath(); - } - - @Override - public void pathDone() {} - - @Override - public void curveTo(float x1, float y1, - float x2, float y2, - float x3, float y3) - { - p2d.curveTo(x1, y1, x2, y2, x3, y3); - } - - @Override - public void quadTo(float x1, float y1, float x2, float y2) { - p2d.quadTo(x1, y1, x2, y2); - } - } } --- old/modules/javafx.graphics/src/main/java/com/sun/prism/impl/PrismSettings.java 2016-11-30 22:48:58.614420980 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/prism/impl/PrismSettings.java 2016-11-30 22:48:58.386420963 +0100 @@ -56,6 +56,7 @@ public static final int prismStatFrequency; public static final boolean doNativePisces; public static final boolean useMarlinRasterizer; + public static final boolean useMarlinRasterizerDP; public static final String refType; public static final boolean forceRepaint; public static final boolean noFallback; @@ -215,7 +216,8 @@ tryOrder = Collections.unmodifiableList(Arrays.asList(tryOrderArr)); - useMarlinRasterizer = getBoolean(systemProperties, "prism.marlinrasterizer", false); + useMarlinRasterizer = getBoolean(systemProperties, "prism.marlinrasterizer", false); + useMarlinRasterizerDP = getBoolean(systemProperties, "prism.marlin.double", false); if (useMarlinRasterizer) { doNativePisces = false; } else { @@ -262,7 +264,8 @@ } System.out.println(""); if (useMarlinRasterizer) { - System.out.println("Using Marlin rasterizer"); + String prectype = (useMarlinRasterizerDP ? "double" : "float"); + System.out.println("Using Marlin rasterizer (" + prectype +')'); } else { String piscestype = (doNativePisces ? "native" : "java"); System.out.println("Using " + piscestype + "-based Pisces rasterizer"); --- /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; + } +} --- /dev/null 2016-11-30 21:27:13.355352085 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/prism/impl/shape/DMarlinRasterizer.java 2016-11-30 22:48:59.338421036 +0100 @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2010, 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.Path2D; +import com.sun.javafx.geom.RectBounds; +import com.sun.javafx.geom.Rectangle; +import com.sun.javafx.geom.Shape; +import com.sun.javafx.geom.transform.BaseTransform; +import com.sun.marlin.DMarlinRenderer; +import com.sun.marlin.DMarlinRenderingEngine; +import com.sun.marlin.MaskMarlinAlphaConsumer; +import com.sun.marlin.DRendererContext; +import com.sun.prism.BasicStroke; +import com.sun.prism.impl.PrismSettings; + +/** + * Thread-safe Marlin rasterizer (TL or CLQ storage) + */ +public final class DMarlinRasterizer implements ShapeRasterizer { + private static final MaskData EMPTY_MASK = MaskData.create(new byte[1], 0, 0, 1, 1); + + @Override + public MaskData getMaskData(Shape shape, + BasicStroke stroke, + RectBounds xformBounds, + BaseTransform xform, + boolean close, boolean antialiasedShape) + { + if (stroke != null && stroke.getType() != BasicStroke.TYPE_CENTERED) { + // RT-27427 + // TODO: Optimize the combinatorial strokes for simple + // shapes and/or teach the rasterizer to be able to + // do a "differential fill" between two shapes. + // Note that most simple shapes will use a more optimized path + // than this method for the INNER/OUTER strokes anyway. + shape = stroke.createStrokedShape(shape); + stroke = null; + } + if (xformBounds == null) { + if (stroke != null) { + // Note that all places that pass null for xformbounds also + // pass null for stroke so that the following is not typically + // executed, but just here as a safety net. + shape = stroke.createStrokedShape(shape); + stroke = null; + } + + xformBounds = new RectBounds(); + //TODO: Need to verify that this is a safe cast ... (RT-27427) + xformBounds = (RectBounds) xform.transform(shape.getBounds(), xformBounds); + } + if (xformBounds.isEmpty()) { + return EMPTY_MASK; + } + + final DRendererContext rdrCtx = DMarlinRenderingEngine.getRendererContext(); + DMarlinRenderer renderer = null; + try { + final Rectangle rclip = rdrCtx.clip; + rclip.setBounds(xformBounds); + + if (shape instanceof Path2D) { + renderer = DMarlinPrismUtils.setupRenderer(rdrCtx, (Path2D) shape, stroke, xform, rclip, + antialiasedShape); + } + if (renderer == null) { + renderer = DMarlinPrismUtils.setupRenderer(rdrCtx, shape, stroke, xform, rclip, + antialiasedShape); + } + final int outpix_xmin = renderer.getOutpixMinX(); + final int outpix_xmax = renderer.getOutpixMaxX(); + final int outpix_ymin = renderer.getOutpixMinY(); + final int outpix_ymax = renderer.getOutpixMaxY(); + final int w = outpix_xmax - outpix_xmin; + final int h = outpix_ymax - outpix_ymin; + if ((w <= 0) || (h <= 0)) { + return EMPTY_MASK; + } + + MaskMarlinAlphaConsumer consumer = rdrCtx.consumer; + if (consumer == null || (w * h) > consumer.getAlphaLength()) { + final int csize = (w * h + 0xfff) & (~0xfff); + rdrCtx.consumer = consumer = new MaskMarlinAlphaConsumer(csize); + if (PrismSettings.verbose) { + System.out.println("new alphas with length = " + csize); + } + } + consumer.setBoundsNoClone(outpix_xmin, outpix_ymin, w, h); + renderer.produceAlphas(consumer); + return consumer.getMaskData(); + } finally { + if (renderer != null) { + renderer.dispose(); + } + // recycle the RendererContext instance + DMarlinRenderingEngine.returnRendererContext(rdrCtx); + } + } +} --- old/modules/javafx.graphics/src/main/java/com/sun/prism/impl/shape/ShapeUtil.java 2016-11-30 22:49:00.038421092 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/prism/impl/shape/ShapeUtil.java 2016-11-30 22:48:59.810421073 +0100 @@ -36,7 +36,11 @@ private static final ShapeRasterizer shapeRasterizer; static { if (PrismSettings.useMarlinRasterizer) { - shapeRasterizer = new MarlinRasterizer(); + if (PrismSettings.useMarlinRasterizerDP) { + shapeRasterizer = new DMarlinRasterizer(); + } else { + shapeRasterizer = new MarlinRasterizer(); + } } else if (PrismSettings.doNativePisces) { shapeRasterizer = new NativePiscesRasterizer(); } else { --- old/modules/javafx.graphics/src/main/java/com/sun/prism/sw/SWContext.java 2016-11-30 22:49:00.514421129 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/prism/sw/SWContext.java 2016-11-30 22:49:00.286421111 +0100 @@ -29,6 +29,9 @@ import com.sun.javafx.geom.Rectangle; import com.sun.javafx.geom.Shape; import com.sun.javafx.geom.transform.BaseTransform; +import com.sun.marlin.DMarlinRenderer; +import com.sun.marlin.DMarlinRenderingEngine; +import com.sun.marlin.DRendererContext; import com.sun.marlin.IntArrayCache; import com.sun.marlin.MarlinAlphaConsumer; import com.sun.marlin.MarlinConst; @@ -42,6 +45,8 @@ import com.sun.prism.ResourceFactory; import com.sun.prism.Texture; import com.sun.prism.impl.PrismSettings; +import com.sun.prism.impl.shape.DMarlinPrismUtils; +import com.sun.prism.impl.shape.DMarlinRasterizer; import com.sun.prism.impl.shape.MarlinPrismUtils; import com.sun.prism.impl.shape.MaskData; import com.sun.prism.impl.shape.OpenPiscesPrismUtils; @@ -177,103 +182,157 @@ @Override public void dispose() { } + } - private static final class DirectRTMarlinAlphaConsumer implements MarlinAlphaConsumer { - private byte alpha_map[]; - private int x; - private int y; - private int w; - private int h; - private int rowNum; + static final class DirectRTMarlinAlphaConsumer implements MarlinAlphaConsumer { + private byte alpha_map[]; + private int x; + private int y; + private int w; + private int h; + private int rowNum; + + private PiscesRenderer pr; + + public void initConsumer(int x, int y, int w, int h, PiscesRenderer pr) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + rowNum = 0; + this.pr = pr; + } - private PiscesRenderer pr; + @Override + public int getOriginX() { + return x; + } - public void initConsumer(int x, int y, int w, int h, PiscesRenderer pr) { - this.x = x; - this.y = y; - this.w = w; - this.h = h; - rowNum = 0; - this.pr = pr; - } + @Override + public int getOriginY() { + return y; + } - @Override - public int getOriginX() { - return x; - } + @Override + public int getWidth() { + return w; + } - @Override - public int getOriginY() { - return y; - } + @Override + public int getHeight() { + return h; + } - @Override - public int getWidth() { - return w; + @Override + public void setMaxAlpha(int maxalpha) { + if ((alpha_map == null) || (alpha_map.length != maxalpha+1)) { + alpha_map = new byte[maxalpha+1]; + for (int i = 0; i <= maxalpha; i++) { + alpha_map[i] = (byte) ((i*255 + maxalpha/2)/maxalpha); + } } + } - @Override - public int getHeight() { - return h; - } + @Override + public boolean supportBlockFlags() { + return false; + } - @Override - public void setMaxAlpha(int maxalpha) { - if ((alpha_map == null) || (alpha_map.length != maxalpha+1)) { - alpha_map = new byte[maxalpha+1]; - for (int i = 0; i <= maxalpha; i++) { - alpha_map[i] = (byte) ((i*255 + maxalpha/2)/maxalpha); - } - } - } + @Override + public void clearAlphas(final int pix_y) { + // noop + } + + @Override + public void setAndClearRelativeAlphas(final int[] alphaDeltas, final int pix_y, + final int pix_from, final int pix_to) + { + // use x instead of pix_from as it cause artefacts: + // note: it would be more efficient to skip all those empty pixels [x to pix_from[ + // but the native implementation must be fixed too. +// pr.emitAndClearAlphaRow(alpha_map, alphaDeltas, pix_y, pix_from, pix_to, rowNum); + pr.emitAndClearAlphaRow(alpha_map, alphaDeltas, pix_y, x, pix_to, rowNum); + rowNum++; - @Override - public boolean supportBlockFlags() { - return false; + // clear properly the end of the alphaDeltas: + final int to = pix_to - x; + if (to <= w) { + alphaDeltas[to] = 0; + } else { + alphaDeltas[w] = 0; } - @Override - public void clearAlphas(final int pix_y) { - // noop + if (MarlinConst.DO_CHECKS) { + IntArrayCache.check(alphaDeltas, pix_from - x, to + 1, 0); } + } - @Override - public void setAndClearRelativeAlphas(final int[] alphaDeltas, final int pix_y, - final int pix_from, final int pix_to) - { - // use x instead of pix_from as it cause artefacts: - // note: it would be more efficient to skip all those empty pixels [x to pix_from[ - // but the native implementation must be fixed too. -// pr.emitAndClearAlphaRow(alpha_map, alphaDeltas, pix_y, pix_from, pix_to, rowNum); - pr.emitAndClearAlphaRow(alpha_map, alphaDeltas, pix_y, x, pix_to, rowNum); - rowNum++; + @Override + public void setAndClearRelativeAlphas(final int[] blkFlags, final int[] alphaDeltas, final int pix_y, + final int pix_from, final int pix_to) + { + throw new UnsupportedOperationException(); + } + } - // clear properly the end of the alphaDeltas: - final int to = pix_to - x; - if (to <= w) { - alphaDeltas[to] = 0; - } else { - alphaDeltas[w] = 0; - } + static final class DMarlinShapeRenderer implements ShapeRenderer { + private final DirectRTMarlinAlphaConsumer alphaConsumer = new DirectRTMarlinAlphaConsumer(); - if (MarlinConst.DO_CHECKS) { - IntArrayCache.check(alphaDeltas, pix_from - x, to + 1, 0); - } + @Override + public void renderShape(PiscesRenderer pr, Shape shape, BasicStroke stroke, BaseTransform tr, Rectangle clip, boolean antialiasedShape) { + if (stroke != null && stroke.getType() != BasicStroke.TYPE_CENTERED) { + // RT-27427 + // TODO: Optimize the combinatorial strokes for simple + // shapes and/or teach the rasterizer to be able to + // do a "differential fill" between two shapes. + // Note that most simple shapes will use a more optimized path + // than this method for the INNER/OUTER strokes anyway. + shape = stroke.createStrokedShape(shape); + stroke = null; } - - @Override - public void setAndClearRelativeAlphas(final int[] blkFlags, final int[] alphaDeltas, final int pix_y, - final int pix_from, final int pix_to) - { - throw new UnsupportedOperationException(); + final DRendererContext rdrCtx = DMarlinRenderingEngine.getRendererContext(); + DMarlinRenderer renderer = null; + try { + if (shape instanceof Path2D) { + renderer = DMarlinPrismUtils.setupRenderer(rdrCtx, (Path2D) shape, stroke, tr, clip, + antialiasedShape); + } + if (renderer == null) { + renderer = DMarlinPrismUtils.setupRenderer(rdrCtx, shape, stroke, tr, clip, + antialiasedShape); + } + final int outpix_xmin = renderer.getOutpixMinX(); + final int outpix_xmax = renderer.getOutpixMaxX(); + final int outpix_ymin = renderer.getOutpixMinY(); + final int outpix_ymax = renderer.getOutpixMaxY(); + final int w = outpix_xmax - outpix_xmin; + final int h = outpix_ymax - outpix_ymin; + if ((w <= 0) || (h <= 0)) { + return; + } + alphaConsumer.initConsumer(outpix_xmin, outpix_ymin, w, h, pr); + renderer.produceAlphas(alphaConsumer); + } finally { + if (renderer != null) { + renderer.dispose(); + } + // recycle the RendererContext instance + DMarlinRenderingEngine.returnRendererContext(rdrCtx); } } + + @Override + public void dispose() { } } SWContext(ResourceFactory factory) { this.factory = factory; if (PrismSettings.useMarlinRasterizer) { - this.shapeRenderer = new MarlinShapeRenderer(); + if (PrismSettings.useMarlinRasterizerDP) { + this.shapeRenderer = new DMarlinShapeRenderer(); + } else { + this.shapeRenderer = new MarlinShapeRenderer(); + } } else if (PrismSettings.doNativePisces) { this.shapeRenderer = new NativeShapeRenderer(); } else { --- /dev/null 2016-11-30 21:27:13.355352085 +0100 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/conv.sh 2016-11-30 22:49:00.766421148 +0100 @@ -0,0 +1,15 @@ +FILES="CollinearSimplifier Curve Dasher Renderer RendererNoAA Stroker TransformingPathConsumer2D" + +for f in $FILES +do + echo "Processing $f" + CL="s/$f/D$f/g" + sed -e $CL -e 's/import com.sun.javafx.geom.PathConsumer2D;//g' -e 's/PathConsumer2D/DPathConsumer2D/g' -e 's/DTransformingDPathConsumer2D/DTransformingPathConsumer2D/g' -e 's/(float)//g' -e 's/float/double/g' -e 's/Float/Double/g' -e 's/DoubleMath/FloatMath/g' -e 's/\([0-9]*\.\?[0-9]\+\)f/\1D/g' -e 's/Curve/DCurve/g' -e 's/DDCurve/DCurve/g' -e 's/Helpers/DHelpers/g' -e 's/DDHelpers/DHelpers/g' -e 's/MarlinRenderer/DMarlinRenderer/g' -e 's/RendererContext/DRendererContext/g' -e 's/DDRendererContext/DRendererContext/g' -e 's/MarlinDRenderer/DMarlinRenderer/g' < $f.java > D$f.java +done + +# Only discard within(float) in Helper + echo "Processing Helpers" + sed -e 's/import com.sun.javafx.geom.PathConsumer2D;//g' -e 's/PathConsumer2D/DPathConsumer2D/g' -e 's/DTransformingDPathConsumer2D/DTransformingPathConsumer2D/g' -e 's/static boolean within(final float x/static boolean withinUNUSED(final float x/g' -e 's/(float)//g' -e 's/float/double/g' -e 's/Float/Double/g' -e 's/DoubleMath/FloatMath/g' -e 's/\([0-9]*\.\?[0-9]\+\)f/\1D/g' -e 's/Curve/DCurve/g' -e 's/DDCurve/DCurve/g' -e 's/Helpers/DHelpers/g' -e 's/MarlinRenderer/DMarlinRenderer/g' -e 's/RendererContext/DRendererContext/g' -e 's/DDRendererContext/DRendererContext/g' -e 's/MarlinDRenderer/DMarlinRenderer/g' < Helpers.java > DHelpers.java + +# \([-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)f +