--- old/modules/javafx.graphics/src/main/java/com/sun/marlin/ByteArrayCache.java 2018-06-08 16:12:44.647686607 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/ByteArrayCache.java 2018-06-08 16:12:44.487686609 +0200 @@ -99,7 +99,7 @@ Reference(final ByteArrayCache cache, final int initialSize) { this.cache = cache; this.clean = cache.clean; - this.initial = createArray(initialSize, clean); + this.initial = createArray(initialSize); if (DO_STATS) { cache.stats.totalInitial += initialSize; } @@ -116,7 +116,7 @@ logInfo(getLogPrefix(clean) + "ByteArrayCache: " + "getArray[oversize]: length=\t" + length); } - return createArray(length, clean); + return createArray(length); } byte[] widenArray(final byte[] array, final int usedSize, @@ -202,7 +202,7 @@ if (DO_STATS) { stats.createOp++; } - return createArray(arraySize, clean); + return createArray(arraySize); } void putArray(final byte[] array) @@ -229,7 +229,7 @@ } } - static byte[] createArray(final int length, final boolean clean) { + static byte[] createArray(final int length) { return new byte[length]; } --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/Curve.java 2018-06-08 16:12:44.991686602 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/Curve.java 2018-06-08 16:12:44.823686605 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,86 +33,94 @@ Curve() { } - void set(float[] points, int type) { - switch(type) { - case 8: + void set(final float[] points, final int type) { + // if instead of switch (perf + most probable cases first) + if (type == 8) { set(points[0], points[1], points[2], points[3], points[4], points[5], points[6], points[7]); - return; - case 6: + } else if (type == 4) { + set(points[0], points[1], + points[2], points[3]); + } else { set(points[0], points[1], points[2], points[3], points[4], points[5]); - return; - default: - throw new InternalError("Curves can only be cubic or quadratic"); } } - void set(float x1, float y1, - float x2, float y2, - float x3, float y3, - float x4, float y4) + void set(final float x1, final float y1, + final float x2, final float y2, + final float x3, final float y3, + final float x4, final float y4) { final float dx32 = 3.0f * (x3 - x2); final float dy32 = 3.0f * (y3 - y2); final float dx21 = 3.0f * (x2 - x1); final float dy21 = 3.0f * (y2 - y1); - ax = (x4 - x1) - dx32; + ax = (x4 - x1) - dx32; // A = P3 - P0 - 3 (P2 - P1) = (P3 - P0) + 3 (P1 - P2) ay = (y4 - y1) - dy32; - bx = (dx32 - dx21); + bx = (dx32 - dx21); // B = 3 (P2 - P1) - 3(P1 - P0) = 3 (P2 + P0) - 6 P1 by = (dy32 - dy21); - cx = dx21; + cx = dx21; // C = 3 (P1 - P0) cy = dy21; - dx = x1; + dx = x1; // D = P0 dy = y1; - dax = 3.0f * ax; day = 3.0f * ay; - dbx = 2.0f * bx; dby = 2.0f * by; + dax = 3.0f * ax; + day = 3.0f * ay; + dbx = 2.0f * bx; + dby = 2.0f * by; } - void set(float x1, float y1, - float x2, float y2, - float x3, float y3) + void set(final float x1, final float y1, + final float x2, final float y2, + final float x3, final float y3) { final float dx21 = (x2 - x1); final float dy21 = (y2 - y1); - ax = 0.0f; ay = 0.0f; - bx = (x3 - x2) - dx21; + ax = 0.0f; // A = 0 + ay = 0.0f; + bx = (x3 - x2) - dx21; // B = P3 - P0 - 2 P2 by = (y3 - y2) - dy21; - cx = 2.0f * dx21; + cx = 2.0f * dx21; // C = 2 (P2 - P1) cy = 2.0f * dy21; - dx = x1; + dx = x1; // D = P1 dy = y1; - dax = 0.0f; day = 0.0f; - dbx = 2.0f * bx; dby = 2.0f * by; - } - - float xat(float t) { - return t * (t * (t * ax + bx) + cx) + dx; - } - float yat(float t) { - return t * (t * (t * ay + by) + cy) + dy; - } - - float dxat(float t) { - return t * (t * dax + dbx) + cx; + dax = 0.0f; + day = 0.0f; + dbx = 2.0f * bx; + dby = 2.0f * by; } - float dyat(float t) { - return t * (t * day + dby) + cy; + void set(final float x1, final float y1, + final float x2, final float y2) + { + final float dx21 = (x2 - x1); + final float dy21 = (y2 - y1); + ax = 0.0f; // A = 0 + ay = 0.0f; + bx = 0.0f; // B = 0 + by = 0.0f; + cx = dx21; // C = (P2 - P1) + cy = dy21; + dx = x1; // D = P1 + dy = y1; + dax = 0.0f; + day = 0.0f; + dbx = 0.0f; + dby = 0.0f; } - int dxRoots(float[] roots, int off) { + int dxRoots(final float[] roots, final int off) { return Helpers.quadraticRoots(dax, dbx, cx, roots, off); } - int dyRoots(float[] roots, int off) { + int dyRoots(final float[] roots, final int off) { return Helpers.quadraticRoots(day, dby, cy, roots, off); } - int infPoints(float[] pts, int off) { + int infPoints(final float[] pts, final 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. @@ -123,19 +131,30 @@ return Helpers.quadraticRoots(a, b, c, pts, off); } + int xPoints(final float[] ts, final int off, final float x) + { + return Helpers.cubicRootsInAB(ax, bx, cx, dx - x, ts, off, 0.0f, 1.0f); + } + + int yPoints(final float[] ts, final int off, final float y) + { + return Helpers.cubicRootsInAB(ay, by, cy, dy - y, ts, off, 0.0f, 1.0f); + } + // 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(float[] pts, int off) { + private int perpendiculardfddf(final float[] pts, final 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 float a = 2.0f * (dax*dax + day*day); - final float b = 3.0f * (dax*dbx + day*dby); - final float c = 2.0f * (dax*cx + day*cy) + dbx*dbx + dby*dby; - final float d = dbx*cx + dby*cy; + final float a = 2.0f * (dax * dax + day * day); + final float b = 3.0f * (dax * dbx + day * dby); + final float c = 2.0f * (dax * cx + day * cy) + dbx * dbx + dby * dby; + final float d = dbx * cx + dby * cy; + return Helpers.cubicRootsInAB(a, b, c, d, pts, off, 0.0f, 1.0f); } @@ -152,22 +171,24 @@ // 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(float[] roots, int off, final float w, final float err) { + int rootsOfROCMinusW(final float[] roots, final int off, final float w2, final float 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); - float t0 = 0.0f, ft0 = ROCsq(t0) - w*w; - roots[off + numPerpdfddf] = 1.0f; // always check interval end points - numPerpdfddf++; - for (int i = off; i < off + numPerpdfddf; i++) { - float t1 = roots[i], ft1 = ROCsq(t1) - w*w; + final int end = off + perpendiculardfddf(roots, off); + roots[end] = 1.0f; // always check interval end points + + float t0 = 0.0f, ft0 = ROCsq(t0) - w2; + + for (int i = off; i <= end; i++) { + float t1 = roots[i], ft1 = ROCsq(t1) - w2; if (ft0 == 0.0f) { roots[ret++] = t0; } else if (ft1 * ft0 < 0.0f) { // 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); + roots[ret++] = falsePositionROCsqMinusX(t0, t1, w2, err); } t0 = t1; ft0 = ft1; @@ -176,9 +197,9 @@ return ret - off; } - private static float eliminateInf(float x) { + private static float eliminateInf(final float x) { return (x == Float.POSITIVE_INFINITY ? Float.MAX_VALUE : - (x == Float.NEGATIVE_INFINITY ? Float.MIN_VALUE : x)); + (x == Float.NEGATIVE_INFINITY ? Float.MIN_VALUE : x)); } // A slight modification of the false position algorithm on wikipedia. @@ -188,17 +209,18 @@ // expressions make it into the language), depending on how closures // and turn out. Same goes for the newton's method // algorithm in Helpers.java - private float falsePositionROCsqMinusX(float x0, float x1, - final float x, final float err) + private float falsePositionROCsqMinusX(final float t0, final float t1, + final float w2, final float err) { final int iterLimit = 100; int side = 0; - float t = x1, ft = eliminateInf(ROCsq(t) - x); - float s = x0, fs = eliminateInf(ROCsq(s) - x); + float t = t1, ft = eliminateInf(ROCsq(t) - w2); + float s = t0, fs = eliminateInf(ROCsq(s) - w2); float 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; + fr = ROCsq(r) - w2; if (sameSign(fr, ft)) { ft = fr; t = r; if (side < 0) { @@ -207,7 +229,7 @@ } else { side = -1; } - } else if (fr * fs > 0) { + } else if (fr * fs > 0.0f) { fs = fr; s = r; if (side > 0) { ft /= (1 << side); @@ -222,7 +244,7 @@ return r; } - private static boolean sameSign(float x, float y) { + private static boolean sameSign(final float x, final float y) { // another way is to test if x*y > 0. This is bad for small x, y. return (x < 0.0f && y < 0.0f) || (x > 0.0f && y > 0.0f); } @@ -230,14 +252,13 @@ // returns the radius of curvature squared at t of this curve // see http://en.wikipedia.org/wiki/Radius_of_curvature_(applications) private float ROCsq(final float t) { - // dx=xat(t) and dy=yat(t). These calls have been inlined for efficiency final float dx = t * (t * dax + dbx) + cx; final float dy = t * (t * day + dby) + cy; final float ddx = 2.0f * dax * t + dbx; final float ddy = 2.0f * day * t + dby; - final float dx2dy2 = dx*dx + dy*dy; - final float ddx2ddy2 = ddx*ddx + ddy*ddy; - final float ddxdxddydy = ddx*dx + ddy*dy; - return dx2dy2*((dx2dy2*dx2dy2) / (dx2dy2 * ddx2ddy2 - ddxdxddydy*ddxdxddydy)); + final float dx2dy2 = dx * dx + dy * dy; + final float ddx2ddy2 = ddx * ddx + ddy * ddy; + final float ddxdxddydy = ddx * dx + ddy * dy; + return dx2dy2 * ((dx2dy2 * dx2dy2) / (dx2dy2 * ddx2ddy2 - ddxdxddydy * ddxdxddydy)); } } --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/DCurve.java 2018-06-08 16:12:45.335686598 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/DCurve.java 2018-06-08 16:12:45.171686600 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,86 +33,94 @@ DCurve() { } - void set(double[] points, int type) { - switch(type) { - case 8: + void set(final double[] points, final int type) { + // if instead of switch (perf + most probable cases first) + if (type == 8) { set(points[0], points[1], points[2], points[3], points[4], points[5], points[6], points[7]); - return; - case 6: + } else if (type == 4) { + set(points[0], points[1], + points[2], points[3]); + } else { set(points[0], points[1], points[2], points[3], points[4], points[5]); - return; - default: - throw new InternalError("Curves can only be cubic or quadratic"); } } - void set(double x1, double y1, - double x2, double y2, - double x3, double y3, - double x4, double y4) + void set(final double x1, final double y1, + final double x2, final double y2, + final double x3, final double y3, + final double x4, final double y4) { final double dx32 = 3.0d * (x3 - x2); final double dy32 = 3.0d * (y3 - y2); final double dx21 = 3.0d * (x2 - x1); final double dy21 = 3.0d * (y2 - y1); - ax = (x4 - x1) - dx32; + ax = (x4 - x1) - dx32; // A = P3 - P0 - 3 (P2 - P1) = (P3 - P0) + 3 (P1 - P2) ay = (y4 - y1) - dy32; - bx = (dx32 - dx21); + bx = (dx32 - dx21); // B = 3 (P2 - P1) - 3(P1 - P0) = 3 (P2 + P0) - 6 P1 by = (dy32 - dy21); - cx = dx21; + cx = dx21; // C = 3 (P1 - P0) cy = dy21; - dx = x1; + dx = x1; // D = P0 dy = y1; - dax = 3.0d * ax; day = 3.0d * ay; - dbx = 2.0d * bx; dby = 2.0d * by; + dax = 3.0d * ax; + day = 3.0d * ay; + dbx = 2.0d * bx; + dby = 2.0d * by; } - void set(double x1, double y1, - double x2, double y2, - double x3, double y3) + void set(final double x1, final double y1, + final double x2, final double y2, + final double x3, final double y3) { final double dx21 = (x2 - x1); final double dy21 = (y2 - y1); - ax = 0.0d; ay = 0.0d; - bx = (x3 - x2) - dx21; + ax = 0.0d; // A = 0 + ay = 0.0d; + bx = (x3 - x2) - dx21; // B = P3 - P0 - 2 P2 by = (y3 - y2) - dy21; - cx = 2.0d * dx21; + cx = 2.0d * dx21; // C = 2 (P2 - P1) cy = 2.0d * dy21; - dx = x1; + dx = x1; // D = P1 dy = y1; - dax = 0.0d; day = 0.0d; - dbx = 2.0d * bx; dby = 2.0d * 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; + dax = 0.0d; + day = 0.0d; + dbx = 2.0d * bx; + dby = 2.0d * by; } - double dyat(double t) { - return t * (t * day + dby) + cy; + void set(final double x1, final double y1, + final double x2, final double y2) + { + final double dx21 = (x2 - x1); + final double dy21 = (y2 - y1); + ax = 0.0d; // A = 0 + ay = 0.0d; + bx = 0.0d; // B = 0 + by = 0.0d; + cx = dx21; // C = (P2 - P1) + cy = dy21; + dx = x1; // D = P1 + dy = y1; + dax = 0.0d; + day = 0.0d; + dbx = 0.0d; + dby = 0.0d; } - int dxRoots(double[] roots, int off) { + int dxRoots(final double[] roots, final int off) { return DHelpers.quadraticRoots(dax, dbx, cx, roots, off); } - int dyRoots(double[] roots, int off) { + int dyRoots(final double[] roots, final int off) { return DHelpers.quadraticRoots(day, dby, cy, roots, off); } - int infPoints(double[] pts, int off) { + int infPoints(final double[] pts, final 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. @@ -123,19 +131,30 @@ return DHelpers.quadraticRoots(a, b, c, pts, off); } + int xPoints(final double[] ts, final int off, final double x) + { + return DHelpers.cubicRootsInAB(ax, bx, cx, dx - x, ts, off, 0.0d, 1.0d); + } + + int yPoints(final double[] ts, final int off, final double y) + { + return DHelpers.cubicRootsInAB(ay, by, cy, dy - y, ts, off, 0.0d, 1.0d); + } + // 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) { + private int perpendiculardfddf(final double[] pts, final 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 = 2.0d * (dax*dax + day*day); - final double b = 3.0d * (dax*dbx + day*dby); - final double c = 2.0d * (dax*cx + day*cy) + dbx*dbx + dby*dby; - final double d = dbx*cx + dby*cy; + final double a = 2.0d * (dax * dax + day * day); + final double b = 3.0d * (dax * dbx + day * dby); + final double c = 2.0d * (dax * cx + day * cy) + dbx * dbx + dby * dby; + final double d = dbx * cx + dby * cy; + return DHelpers.cubicRootsInAB(a, b, c, d, pts, off, 0.0d, 1.0d); } @@ -152,22 +171,24 @@ // 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) { + int rootsOfROCMinusW(final double[] roots, final int off, final double w2, 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.0d, ft0 = ROCsq(t0) - w*w; - roots[off + numPerpdfddf] = 1.0d; // always check interval end points - numPerpdfddf++; - for (int i = off; i < off + numPerpdfddf; i++) { - double t1 = roots[i], ft1 = ROCsq(t1) - w*w; + final int end = off + perpendiculardfddf(roots, off); + roots[end] = 1.0d; // always check interval end points + + double t0 = 0.0d, ft0 = ROCsq(t0) - w2; + + for (int i = off; i <= end; i++) { + double t1 = roots[i], ft1 = ROCsq(t1) - w2; if (ft0 == 0.0d) { roots[ret++] = t0; } else if (ft1 * ft0 < 0.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); + roots[ret++] = falsePositionROCsqMinusX(t0, t1, w2, err); } t0 = t1; ft0 = ft1; @@ -176,9 +197,9 @@ return ret - off; } - private static double eliminateInf(double x) { + private static double eliminateInf(final double x) { return (x == Double.POSITIVE_INFINITY ? Double.MAX_VALUE : - (x == Double.NEGATIVE_INFINITY ? Double.MIN_VALUE : x)); + (x == Double.NEGATIVE_INFINITY ? Double.MIN_VALUE : x)); } // A slight modification of the false position algorithm on wikipedia. @@ -188,17 +209,18 @@ // 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) + private double falsePositionROCsqMinusX(final double t0, final double t1, + final double w2, 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 t = t1, ft = eliminateInf(ROCsq(t) - w2); + double s = t0, fs = eliminateInf(ROCsq(s) - w2); 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; + fr = ROCsq(r) - w2; if (sameSign(fr, ft)) { ft = fr; t = r; if (side < 0) { @@ -207,7 +229,7 @@ } else { side = -1; } - } else if (fr * fs > 0) { + } else if (fr * fs > 0.0d) { fs = fr; s = r; if (side > 0) { ft /= (1 << side); @@ -222,7 +244,7 @@ return r; } - private static boolean sameSign(double x, double y) { + private static boolean sameSign(final double x, final double y) { // another way is to test if x*y > 0. This is bad for small x, y. return (x < 0.0d && y < 0.0d) || (x > 0.0d && y > 0.0d); } @@ -230,14 +252,13 @@ // 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 = 2.0d * dax * t + dbx; final double ddy = 2.0d * 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)); + 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)); } } --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/DDasher.java 2018-06-08 16:12:45.671686593 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/DDasher.java 2018-06-08 16:12:45.511686595 +0200 @@ -26,6 +26,8 @@ package com.sun.marlin; import java.util.Arrays; +import com.sun.marlin.DTransformingPathConsumer2D.CurveBasicMonotonizer; +import com.sun.marlin.DTransformingPathConsumer2D.CurveClipSplitter; /** * The DDasher class takes a series of linear commands @@ -40,8 +42,9 @@ */ public final class DDasher implements DPathConsumer2D, MarlinConst { - static final int REC_LIMIT = 4; - static final double ERR = 0.01d; + /* huge circle with radius ~ 2E9 only needs 12 subdivision levels */ + static final int REC_LIMIT = 16; + static final double CURVE_LEN_ERR = MarlinProperties.getCurveLengthError(); // 0.01 initial static final double MIN_T_INC = 1.0d / (1 << REC_LIMIT); // More than 24 bits of mantissa means we can no longer accurately @@ -63,8 +66,10 @@ private boolean dashOn; private double phase; - private double sx, sy; - private double x0, y0; + // The starting point of the path + private double sx0, sy0; + // the current point + private double cx0, cy0; // temporary storage for the current curve private final double[] curCurvepts; @@ -75,11 +80,34 @@ // flag to recycle dash array copy boolean recycleDashes; + // 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; + // dashes ref (dirty) final DoubleArrayCache.Reference dashes_ref; // firstSegmentsBuffer ref (dirty) final DoubleArrayCache.Reference firstSegmentsBuffer_ref; + // Bounds of the drawing region, at pixel precision. + private double[] clipRect; + + // the outcode of the current point + private int cOutCode = 0; + + private boolean subdivide = DO_CLIP_SUBDIVIDER; + + private final LengthIterator li = new LengthIterator(); + + private final CurveClipSplitter curveSplitter; + + private double cycleLen; + private boolean outside; + private double totalSkipLen; + /** * Constructs a DDasher. * @param rdrCtx per-thread renderer context @@ -95,6 +123,8 @@ // we need curCurvepts to be able to contain 2 curves because when // dashing curves, we need to subdivide it curCurvepts = new double[8 * 2]; + + this.curveSplitter = rdrCtx.curveClipSplitter; } /** @@ -121,6 +151,8 @@ for (int i = 0; i < dashLen; i++) { sum += dash[i]; } + this.cycleLen = sum; + double cycles = phase / sum; if (phase < 0.0d) { if (-cycles >= MAX_CYCLES) { @@ -169,6 +201,12 @@ this.recycleDashes = recycleDashes; + if (rdrCtx.doClip) { + this.clipRect = rdrCtx.clipRect; + } else { + this.clipRect = null; + this.cOutCode = 0; + } return this; // fluent API } @@ -206,33 +244,42 @@ @Override public void moveTo(final double x0, final double y0) { if (firstSegidx != 0) { - out.moveTo(sx, sy); + out.moveTo(sx0, sy0); emitFirstSegments(); } - needsMoveTo = true; + this.needsMoveTo = true; this.idx = startIdx; this.dashOn = this.startDashOn; this.phase = this.startPhase; - this.sx = x0; - this.sy = y0; - this.x0 = x0; - this.y0 = y0; + this.cx0 = x0; + this.cy0 = y0; + + // update starting point: + this.sx0 = x0; + this.sy0 = y0; this.starting = true; + + if (clipRect != null) { + final int outcode = DHelpers.outcode(x0, y0, clipRect); + this.cOutCode = outcode; + this.outside = false; + this.totalSkipLen = 0.0d; + } } private void emitSeg(double[] buf, int off, int type) { switch (type) { + case 4: + out.lineTo(buf[off], buf[off + 1]); + return; case 8: - out.curveTo(buf[off+0], buf[off+1], - buf[off+2], buf[off+3], - buf[off+4], buf[off+5]); + out.curveTo(buf[off ], 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]); + out.quadTo(buf[off ], buf[off + 1], + buf[off + 2], buf[off + 3]); return; default: } @@ -248,12 +295,6 @@ } 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) private void goTo(final double[] pts, final int off, final int type, @@ -269,7 +310,7 @@ } else { if (needsMoveTo) { needsMoveTo = false; - out.moveTo(x0, y0); + out.moveTo(cx0, cy0); } emitSeg(pts, off, type); } @@ -280,8 +321,8 @@ } needsMoveTo = true; } - this.x0 = x; - this.y0 = y; + this.cx0 = x; + this.cy0 = y; } private void goTo_starting(final double[] pts, final int off, final int type) { @@ -307,10 +348,56 @@ @Override public void lineTo(final double x1, final double y1) { - final double dx = x1 - x0; - final double dy = y1 - y0; + final int outcode0 = this.cOutCode; + + if (clipRect != null) { + final int outcode1 = DHelpers.outcode(x1, y1, clipRect); + + // Should clip + final int orCode = (outcode0 | outcode1); + + if (orCode != 0) { + final int sideCode = outcode0 & outcode1; + + // basic rejection criteria: + if (sideCode == 0) { + // ovelap clip: + if (subdivide) { + // avoid reentrance + subdivide = false; + // subdivide curve => callback with subdivided parts: + boolean ret = curveSplitter.splitLine(cx0, cy0, x1, y1, + orCode, this); + // reentrance is done: + subdivide = true; + if (ret) { + return; + } + } + // already subdivided so render it + } else { + this.cOutCode = outcode1; + skipLineTo(x1, y1); + return; + } + } + + this.cOutCode = outcode1; + + if (this.outside) { + this.outside = false; + // Adjust current index, phase & dash: + skipLen(); + } + } + _lineTo(x1, y1); + } + + private void _lineTo(final double x1, final double y1) { + final double dx = x1 - cx0; + final double dy = y1 - cy0; - double len = dx*dx + dy*dy; + double len = dx * dx + dy * dy; if (len == 0.0d) { return; } @@ -329,8 +416,7 @@ boolean _dashOn = dashOn; double _phase = phase; - double leftInThisDashSegment; - double d, dashdx, dashdy, p; + double leftInThisDashSegment, d; while (true) { d = _dash[_idx]; @@ -351,24 +437,15 @@ _idx = (_idx + 1) % _dashLen; _dashOn = !_dashOn; } - - // Save local state: - idx = _idx; - dashOn = _dashOn; - phase = _phase; - return; + break; } - dashdx = d * cx; - dashdy = d * cy; - if (_phase == 0.0d) { - _curCurvepts[0] = x0 + dashdx; - _curCurvepts[1] = y0 + dashdy; + _curCurvepts[0] = cx0 + d * cx; + _curCurvepts[1] = cy0 + d * cy; } else { - p = leftInThisDashSegment / d; - _curCurvepts[0] = x0 + p * dashdx; - _curCurvepts[1] = y0 + p * dashdy; + _curCurvepts[0] = cx0 + leftInThisDashSegment * cx; + _curCurvepts[1] = cy0 + leftInThisDashSegment * cy; } goTo(_curCurvepts, 0, 4, _dashOn); @@ -379,19 +456,95 @@ _dashOn = !_dashOn; _phase = 0.0d; } + // Save local state: + idx = _idx; + dashOn = _dashOn; + phase = _phase; } - // shared instance in DDasher - private final LengthIterator li = new LengthIterator(); + private void skipLineTo(final double x1, final double y1) { + final double dx = x1 - cx0; + final double dy = y1 - cy0; + + double len = dx * dx + dy * dy; + if (len != 0.0d) { + len = Math.sqrt(len); + } + + // Accumulate skipped length: + this.outside = true; + this.totalSkipLen += len; + + // Fix initial move: + this.needsMoveTo = true; + this.starting = false; + + this.cx0 = x1; + this.cy0 = y1; + } + + public void skipLen() { + double len = this.totalSkipLen; + this.totalSkipLen = 0.0d; + + final double[] _dash = dash; + final int _dashLen = this.dashLen; + + int _idx = idx; + boolean _dashOn = dashOn; + double _phase = phase; + + // -2 to ensure having 2 iterations of the post-loop + // to compensate the remaining phase + final long fullcycles = (long)Math.floor(len / cycleLen) - 2L; + + if (fullcycles > 0L) { + len -= cycleLen * fullcycles; + + final long iterations = fullcycles * _dashLen; + _idx = (int) (iterations + _idx) % _dashLen; + _dashOn = (iterations + (_dashOn ? 1L : 0L) & 1L) == 1L; + } + + double leftInThisDashSegment, d; + + while (true) { + d = _dash[_idx]; + leftInThisDashSegment = d - _phase; + + if (len <= leftInThisDashSegment) { + // Advance phase within current dash segment + _phase += len; + + // TODO: compare double values using epsilon: + if (len == leftInThisDashSegment) { + _phase = 0.0d; + _idx = (_idx + 1) % _dashLen; + _dashOn = !_dashOn; + } + break; + } + + len -= leftInThisDashSegment; + // Advance to next dash segment + _idx = (_idx + 1) % _dashLen; + _dashOn = !_dashOn; + _phase = 0.0d; + } + // Save local state: + idx = _idx; + dashOn = _dashOn; + phase = _phase; + } // preconditions: curCurvepts 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(final int type) { - if (pointCurve(curCurvepts, type)) { + final double[] _curCurvepts = curCurvepts; + if (pointCurve(_curCurvepts, type)) { return; } final LengthIterator _li = li; - final double[] _curCurvepts = curCurvepts; final double[] _dash = dash; final int _dashLen = this.dashLen; @@ -403,17 +556,16 @@ // initially the current curve is at curCurvepts[0...type] int curCurveoff = 0; - double lastSplitT = 0.0d; + double prevT = 0.0d; double t; double leftInThisDashSegment = _dash[_idx] - _phase; while ((t = _li.next(leftInThisDashSegment)) < 1.0d) { if (t != 0.0d) { - DHelpers.subdivideAt((t - lastSplitT) / (1.0d - lastSplitT), + DHelpers.subdivideAt((t - prevT) / (1.0d - prevT), _curCurvepts, curCurveoff, - _curCurvepts, 0, - _curCurvepts, type, type); - lastSplitT = t; + _curCurvepts, 0, type); + prevT = t; goTo(_curCurvepts, 2, type, _dashOn); curCurveoff = type; } @@ -441,7 +593,29 @@ _li.reset(); } - private static boolean pointCurve(double[] curve, int type) { + private void skipSomethingTo(final int type) { + final double[] _curCurvepts = curCurvepts; + if (pointCurve(_curCurvepts, type)) { + return; + } + final LengthIterator _li = li; + + _li.initializeIterationOnCurve(_curCurvepts, type); + + // In contrary to somethingTo(), + // just estimate properly the curve length: + final double len = _li.totalLength(); + + // Accumulate skipped length: + this.outside = true; + this.totalSkipLen += len; + + // Fix initial move: + this.needsMoveTo = true; + this.starting = false; + } + + private static boolean pointCurve(final double[] curve, final int type) { for (int i = 2; i < type; i++) { if (curve[i] != curve[i-2]) { return false; @@ -464,15 +638,14 @@ // tree; however, the trees we are interested in have the property that // every non leaf node has exactly 2 children static final class LengthIterator { - private enum Side {LEFT, RIGHT} // Holds the curves at various levels of the recursion. The root // (i.e. the original curve) is at recCurveStack[0] (but then it // gets subdivided, the left half is put at 1, so most of the time // only the right half of the original curve is at 0) private final double[][] recCurveStack; // dirty - // sides[i] indicates whether the node at level i+1 in the path from + // sidesRight[i] indicates whether the node at level i+1 in the path from // the root to the current leaf is a left or right child of its parent. - private final Side[] sides; // dirty + private final boolean[] sidesRight; // dirty private int curveType; // lastT and nextT delimit the current leaf. private double nextT; @@ -493,7 +666,7 @@ LengthIterator() { this.recCurveStack = new double[REC_LIMIT + 1][8]; - this.sides = new Side[REC_LIMIT]; + this.sidesRight = new boolean[REC_LIMIT]; // if any methods are called without first initializing this object // on a curve, we want it to fail ASAP. this.nextT = Double.MAX_VALUE; @@ -515,7 +688,7 @@ for (int i = recLimit; i >= 0; i--) { Arrays.fill(recCurveStack[i], 0.0d); } - Arrays.fill(sides, Side.LEFT); + Arrays.fill(sidesRight, false); Arrays.fill(curLeafCtrlPolyLengths, 0.0d); Arrays.fill(nextRoots, 0.0d); Arrays.fill(flatLeafCoefCache, 0.0d); @@ -523,7 +696,7 @@ } } - void initializeIterationOnCurve(double[] pts, int type) { + void initializeIterationOnCurve(final double[] pts, final int type) { // optimize arraycopy (8 values faster than 6 = type): System.arraycopy(pts, 0, recCurveStack[0], 0, 8); this.curveType = type; @@ -535,11 +708,11 @@ goLeft(); // initializes nextT and lenAtNextT properly this.lenAtLastSplit = 0.0d; if (recLevel > 0) { - this.sides[0] = Side.LEFT; + this.sidesRight[0] = false; this.done = false; } else { // the root of the tree is a leaf so we're done. - this.sides[0] = Side.RIGHT; + this.sidesRight[0] = true; this.done = true; } this.lastSegLen = 0.0d; @@ -548,7 +721,7 @@ // 0 == false, 1 == true, -1 == invalid cached value. private int cachedHaveLowAcceleration = -1; - private boolean haveLowAcceleration(double err) { + private boolean haveLowAcceleration(final double err) { if (cachedHaveLowAcceleration == -1) { final double len1 = curLeafCtrlPolyLengths[0]; final double len2 = curLeafCtrlPolyLengths[1]; @@ -615,7 +788,7 @@ if (_flatLeafCoefCache[2] < 0.0d) { double x = curLeafCtrlPolyLengths[0], - y = x + curLeafCtrlPolyLengths[1]; + y = x + curLeafCtrlPolyLengths[1]; if (curveType == 8) { double z = y + curLeafCtrlPolyLengths[2]; _flatLeafCoefCache[0] = 3.0d * (x - y) + z; @@ -637,7 +810,7 @@ // 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.0d, 1.0d); + final int n = DHelpers.cubicRootsInAB(a, b, c, d, nextRoots, 0, 0.0d, 1.0d); if (n == 1 && !Double.isNaN(nextRoots[0])) { t = nextRoots[0]; } @@ -658,6 +831,16 @@ return t; } + double totalLength() { + while (!done) { + goToNextLeaf(); + } + // reset LengthIterator: + reset(); + + return lenAtNextT; + } + double lastSegLen() { return lastSegLen; } @@ -667,11 +850,11 @@ private void goToNextLeaf() { // We must go to the first ancestor node that has an unvisited // right child. + final boolean[] _sides = sidesRight; int _recLevel = recLevel; - final Side[] _sides = sides; - _recLevel--; - while(_sides[_recLevel] == Side.RIGHT) { + + while(_sides[_recLevel]) { if (_recLevel == 0) { recLevel = 0; done = true; @@ -680,19 +863,17 @@ _recLevel--; } - _sides[_recLevel] = Side.RIGHT; + _sides[_recLevel] = true; // optimize arraycopy (8 values faster than 6 = type): - System.arraycopy(recCurveStack[_recLevel], 0, - recCurveStack[_recLevel+1], 0, 8); - _recLevel++; - + System.arraycopy(recCurveStack[_recLevel++], 0, + recCurveStack[_recLevel], 0, 8); recLevel = _recLevel; goLeft(); } // go to the leftmost node from the current node. Return its length. private void goLeft() { - double len = onLeaf(); + final double len = onLeaf(); if (len >= 0.0d) { lastT = nextT; lenAtLastT = lenAtNextT; @@ -702,10 +883,11 @@ flatLeafCoefCache[2] = -1.0d; cachedHaveLowAcceleration = -1; } else { - DHelpers.subdivide(recCurveStack[recLevel], 0, - recCurveStack[recLevel+1], 0, - recCurveStack[recLevel], 0, curveType); - sides[recLevel] = Side.LEFT; + DHelpers.subdivide(recCurveStack[recLevel], + recCurveStack[recLevel + 1], + recCurveStack[recLevel], curveType); + + sidesRight[recLevel] = false; recLevel++; goLeft(); } @@ -720,7 +902,7 @@ 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 x1 = curve[i], y1 = curve[i + 1]; final double len = DHelpers.linelen(x0, y0, x1, y1); polyLen += len; curLeafCtrlPolyLengths[(i >> 1) - 1] = len; @@ -728,10 +910,9 @@ y0 = y1; } - final double lineLen = DHelpers.linelen(curve[0], curve[1], - curve[_curveType-2], - curve[_curveType-1]); - if ((polyLen - lineLen) < ERR || recLevel == REC_LIMIT) { + final double lineLen = DHelpers.linelen(curve[0], curve[1], x0, y0); + + if ((polyLen - lineLen) < CURVE_LEN_ERR || recLevel == REC_LIMIT) { return (polyLen + lineLen) / 2.0d; } return -1.0d; @@ -743,41 +924,190 @@ final double x2, final double y2, final double x3, final double y3) { + final int outcode0 = this.cOutCode; + + if (clipRect != null) { + final int outcode1 = DHelpers.outcode(x1, y1, clipRect); + final int outcode2 = DHelpers.outcode(x2, y2, clipRect); + final int outcode3 = DHelpers.outcode(x3, y3, clipRect); + + // Should clip + final int orCode = (outcode0 | outcode1 | outcode2 | outcode3); + if (orCode != 0) { + final int sideCode = outcode0 & outcode1 & outcode2 & outcode3; + + // basic rejection criteria: + if (sideCode == 0) { + // ovelap clip: + if (subdivide) { + // avoid reentrance + subdivide = false; + // subdivide curve => callback with subdivided parts: + boolean ret = curveSplitter.splitCurve(cx0, cy0, x1, y1, x2, y2, x3, y3, + orCode, this); + // reentrance is done: + subdivide = true; + if (ret) { + return; + } + } + // already subdivided so render it + } else { + this.cOutCode = outcode3; + skipCurveTo(x1, y1, x2, y2, x3, y3); + return; + } + } + + this.cOutCode = outcode3; + + if (this.outside) { + this.outside = false; + // Adjust current index, phase & dash: + skipLen(); + } + } + _curveTo(x1, y1, x2, y2, x3, y3); + } + + private void _curveTo(final double x1, final double y1, + final double x2, final double y2, + final double x3, final double y3) + { final double[] _curCurvepts = curCurvepts; - _curCurvepts[0] = x0; _curCurvepts[1] = y0; - _curCurvepts[2] = x1; _curCurvepts[3] = y1; - _curCurvepts[4] = x2; _curCurvepts[5] = y2; - _curCurvepts[6] = x3; _curCurvepts[7] = y3; - somethingTo(8); + + // monotonize curve: + final CurveBasicMonotonizer monotonizer + = rdrCtx.monotonizer.curve(cx0, cy0, x1, y1, x2, y2, x3, y3); + + final int nSplits = monotonizer.nbSplits; + final double[] mid = monotonizer.middle; + + for (int i = 0, off = 0; i <= nSplits; i++, off += 6) { + // optimize arraycopy (8 values faster than 6 = type): + System.arraycopy(mid, off, _curCurvepts, 0, 8); + + somethingTo(8); + } + } + + private void skipCurveTo(final double x1, final double y1, + final double x2, final double y2, + final double x3, final double y3) + { + final double[] _curCurvepts = curCurvepts; + _curCurvepts[0] = cx0; _curCurvepts[1] = cy0; + _curCurvepts[2] = x1; _curCurvepts[3] = y1; + _curCurvepts[4] = x2; _curCurvepts[5] = y2; + _curCurvepts[6] = x3; _curCurvepts[7] = y3; + + skipSomethingTo(8); + + this.cx0 = x3; + this.cy0 = y3; } @Override public void quadTo(final double x1, final double y1, final double x2, final double y2) { + final int outcode0 = this.cOutCode; + + if (clipRect != null) { + final int outcode1 = DHelpers.outcode(x1, y1, clipRect); + final int outcode2 = DHelpers.outcode(x2, y2, clipRect); + + // Should clip + final int orCode = (outcode0 | outcode1 | outcode2); + if (orCode != 0) { + final int sideCode = outcode0 & outcode1 & outcode2; + + // basic rejection criteria: + if (sideCode == 0) { + // ovelap clip: + if (subdivide) { + // avoid reentrance + subdivide = false; + // subdivide curve => call lineTo() with subdivided curves: + boolean ret = curveSplitter.splitQuad(cx0, cy0, x1, y1, + x2, y2, orCode, this); + // reentrance is done: + subdivide = true; + if (ret) { + return; + } + } + // already subdivided so render it + } else { + this.cOutCode = outcode2; + skipQuadTo(x1, y1, x2, y2); + return; + } + } + + this.cOutCode = outcode2; + + if (this.outside) { + this.outside = false; + // Adjust current index, phase & dash: + skipLen(); + } + } + _quadTo(x1, y1, x2, y2); + } + + private void _quadTo(final double x1, final double y1, + final double x2, final double y2) + { final double[] _curCurvepts = curCurvepts; - _curCurvepts[0] = x0; _curCurvepts[1] = y0; - _curCurvepts[2] = x1; _curCurvepts[3] = y1; - _curCurvepts[4] = x2; _curCurvepts[5] = y2; - somethingTo(6); + + // monotonize quad: + final CurveBasicMonotonizer monotonizer + = rdrCtx.monotonizer.quad(cx0, cy0, x1, y1, x2, y2); + + final int nSplits = monotonizer.nbSplits; + final double[] mid = monotonizer.middle; + + for (int i = 0, off = 0; i <= nSplits; i++, off += 4) { + // optimize arraycopy (8 values faster than 6 = type): + System.arraycopy(mid, off, _curCurvepts, 0, 8); + + somethingTo(6); + } + } + + private void skipQuadTo(final double x1, final double y1, + final double x2, final double y2) + { + final double[] _curCurvepts = curCurvepts; + _curCurvepts[0] = cx0; _curCurvepts[1] = cy0; + _curCurvepts[2] = x1; _curCurvepts[3] = y1; + _curCurvepts[4] = x2; _curCurvepts[5] = y2; + + skipSomethingTo(6); + + this.cx0 = x2; + this.cy0 = y2; } @Override public void closePath() { - lineTo(sx, sy); + if (cx0 != sx0 || cy0 != sy0) { + lineTo(sx0, sy0); + } if (firstSegidx != 0) { if (!dashOn || needsMoveTo) { - out.moveTo(sx, sy); + out.moveTo(sx0, sy0); } emitFirstSegments(); } - moveTo(sx, sy); + moveTo(sx0, sy0); } @Override public void pathDone() { if (firstSegidx != 0) { - out.moveTo(sx, sy); + out.moveTo(sx0, sy0); emitFirstSegments(); } out.pathDone(); --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/DHelpers.java 2018-06-08 16:12:46.023686588 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/DHelpers.java 2018-06-08 16:12:45.859686590 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,7 +25,6 @@ package com.sun.marlin; -import static java.lang.Math.PI; import java.util.Arrays; import com.sun.marlin.stats.Histogram; import com.sun.marlin.stats.StatLong; @@ -41,13 +40,25 @@ return (d <= err && d >= -err); } - static int quadraticRoots(final double a, final double b, - final double c, double[] zeroes, final int 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; + } + + static int quadraticRoots(final double a, final double b, final double c, + final double[] zeroes, final int off) { int ret = off; - double t; if (a != 0.0d) { - final double dis = b*b - 4*a*c; + final double dis = b*b - 4.0d * a * c; if (dis > 0.0d) { final double sqrtDis = Math.sqrt(dis); // depending on the sign of b we use a slightly different @@ -62,34 +73,34 @@ zeroes[ret++] = (2.0d * c) / (-b + sqrtDis); } } else if (dis == 0.0d) { - t = (-b) / (2.0d * a); - zeroes[ret++] = t; - } - } else { - if (b != 0.0d) { - t = (-c) / b; - zeroes[ret++] = t; + zeroes[ret++] = -b / (2.0d * a); } + } else if (b != 0.0d) { + zeroes[ret++] = -c / b; } 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, + static int cubicRootsInAB(final double d, double a, double b, double c, + final double[] pts, final int off, final double A, final double B) { if (d == 0.0d) { - int num = quadraticRoots(a, b, c, pts, off); + final 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 + // https://github.com/erich666/GraphicsGems/blob/master/gems/Roots3And4.c // (also from awt.geom.CubicCurve2D. 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 + + /* + * TODO: cleanup all that code after reading Roots3And4.c + */ a /= d; b /= d; c /= d; @@ -102,63 +113,45 @@ // p = P/3 // q = Q/2 // instead and use those values for simplicity of the code. - double sq_A = a * a; - double p = (1.0d/3.0d) * ((-1.0d/3.0d) * sq_A + b); - double q = (1.0d/2.0d) * ((2.0d/27.0d) * a * sq_A - (1.0d/3.0d) * a * b + c); + final double sub = (1.0d / 3.0d) * a; + final double sq_A = a * a; + final double p = (1.0d / 3.0d) * ((-1.0d / 3.0d) * sq_A + b); + final double q = (1.0d / 2.0d) * ((2.0d / 27.0d) * a * sq_A - sub * b + c); // use Cardano's formula - double cb_p = p * p * p; - double D = q * q + cb_p; + final double cb_p = p * p * p; + final double D = q * q + cb_p; int num; if (D < 0.0d) { // see: http://en.wikipedia.org/wiki/Cubic_function#Trigonometric_.28and_hyperbolic.29_method - final double phi = (1.0d/3.0d) * Math.acos(-q / Math.sqrt(-cb_p)); + final double phi = (1.0d / 3.0d) * Math.acos(-q / Math.sqrt(-cb_p)); final double t = 2.0d * Math.sqrt(-p); - pts[ off+0 ] = ( t * Math.cos(phi)); - pts[ off+1 ] = (-t * Math.cos(phi + (PI / 3.0d))); - pts[ off+2 ] = (-t * Math.cos(phi - (PI / 3.0d))); + pts[off ] = ( t * Math.cos(phi) - sub); + pts[off + 1] = (-t * Math.cos(phi + (Math.PI / 3.0d)) - sub); + pts[off + 2] = (-t * Math.cos(phi - (Math.PI / 3.0d)) - sub); num = 3; } else { final double sqrt_D = Math.sqrt(D); final double u = Math.cbrt(sqrt_D - q); final double v = - Math.cbrt(sqrt_D + q); - pts[ off ] = (u + v); + pts[off ] = (u + v - sub); num = 1; if (within(D, 0.0d, 1e-8d)) { - pts[off+1] = -(pts[off] / 2.0d); + pts[off + 1] = ((-1.0d / 2.0d) * (u + v) - sub); num = 2; } } - final double sub = (1.0d/3.0d) * 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, + static int filterOutNotInAB(final double[] nums, final int off, final int len, final double a, final double b) { int ret = off; @@ -170,35 +163,189 @@ return ret; } - 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 double fastLineLen(final double x0, final double y0, + final double x1, final double y1) + { + final double dx = x1 - x0; + final double dy = y1 - y0; + + // use manhattan norm: + return Math.abs(dx) + Math.abs(dy); + } + + static double linelen(final double x0, final double y0, + final double x1, final double y1) + { + final double dx = x1 - x0; + final double dy = y1 - y0; + return Math.sqrt(dx * dx + dy * dy); + } + + static double fastQuadLen(final double x0, final double y0, + final double x1, final double y1, + final double x2, final double y2) + { + final double dx1 = x1 - x0; + final double dx2 = x2 - x1; + final double dy1 = y1 - y0; + final double dy2 = y2 - y1; + + // use manhattan norm: + return Math.abs(dx1) + Math.abs(dx2) + + Math.abs(dy1) + Math.abs(dy2); + } + + static double quadlen(final double x0, final double y0, + final double x1, final double y1, + final double x2, final double y2) + { + return (linelen(x0, y0, x1, y1) + + linelen(x1, y1, x2, y2) + + linelen(x0, y0, x2, y2)) / 2.0d; + } + + static double fastCurvelen(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 double dx1 = x1 - x0; + final double dx2 = x2 - x1; + final double dx3 = x3 - x2; + final double dy1 = y1 - y0; + final double dy2 = y2 - y1; + final double dy3 = y3 - y2; + + // use manhattan norm: + return Math.abs(dx1) + Math.abs(dx2) + Math.abs(dx3) + + Math.abs(dy1) + Math.abs(dy2) + Math.abs(dy3); + } + + static double curvelen(final double x0, final double y0, + final double x1, final double y1, + final double x2, final double y2, + final double x3, final double y3) + { + return (linelen(x0, y0, x1, y1) + + linelen(x1, y1, x2, y2) + + linelen(x2, y2, x3, y3) + + linelen(x0, y0, x3, y3)) / 2.0d; + } + + // 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. + static int findSubdivPoints(final DCurve c, final double[] pts, + final double[] ts, final int type, + final double w2) + { + 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 != 0.0d && x12 != 0.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, w2, 0.0001d); + + ret = filterOutNotInAB(ts, 0, ret, 0.0001d, 0.9999d); + isort(ts, ret); + return ret; + } + + // finds values of t where the curve in pts should be subdivided in order + // to get intersections with the given clip rectangle. + // Stores the points in ts, and returns how many of them there were. + static int findClipPoints(final DCurve curve, final double[] pts, + final double[] ts, final int type, + final int outCodeOR, + final double[] clipRect) + { + curve.set(pts, type); + + // clip rectangle (ymin, ymax, xmin, xmax) + int ret = 0; + + if ((outCodeOR & OUTCODE_LEFT) != 0) { + ret += curve.xPoints(ts, ret, clipRect[2]); + } + if ((outCodeOR & OUTCODE_RIGHT) != 0) { + ret += curve.xPoints(ts, ret, clipRect[3]); + } + if ((outCodeOR & OUTCODE_TOP) != 0) { + ret += curve.yPoints(ts, ret, clipRect[0]); + } + if ((outCodeOR & OUTCODE_BOTTOM) != 0) { + ret += curve.yPoints(ts, ret, clipRect[1]); + } + isort(ts, ret); + return ret; } - static void subdivide(double[] src, int srcoff, double[] left, int leftoff, - double[] right, int rightoff, int type) + static void subdivide(final double[] src, + final double[] left, final double[] right, + final 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); + subdivideCubic(src, left, right); + return; + case 6: + subdivideQuad(src, left, right); 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]; + static void isort(final double[] a, final int len) { + for (int i = 1, j; i < len; i++) { + final double ai = a[i]; + j = i - 1; + for (; j >= 0 && a[j] > ai; j--) { + a[j + 1] = a[j]; } - a[j+1] = ai; + a[j + 1] = ai; } } @@ -221,206 +368,216 @@ * 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) / 2.0d; - y1 = (y1 + ctrly1) / 2.0d; - x2 = (x2 + ctrlx2) / 2.0d; - y2 = (y2 + ctrly2) / 2.0d; - double centerx = (ctrlx1 + ctrlx2) / 2.0d; - double centery = (ctrly1 + ctrly2) / 2.0d; - ctrlx1 = (x1 + centerx) / 2.0d; - ctrly1 = (y1 + centery) / 2.0d; - ctrlx2 = (x2 + centerx) / 2.0d; - ctrly2 = (y2 + centery) / 2.0d; - centerx = (ctrlx1 + ctrlx2) / 2.0d; - centery = (ctrly1 + ctrly2) / 2.0d; - 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) / 2.0d; - y1 = (y1 + ctrly) / 2.0d; - x2 = (x2 + ctrlx) / 2.0d; - y2 = (y2 + ctrly) / 2.0d; - ctrlx = (x1 + x2) / 2.0d; - ctrly = (y1 + y2) / 2.0d; - 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; + static void subdivideCubic(final double[] src, + final double[] left, + final double[] right) + { + double x1 = src[0]; + double y1 = src[1]; + double cx1 = src[2]; + double cy1 = src[3]; + double cx2 = src[4]; + double cy2 = src[5]; + double x2 = src[6]; + double y2 = src[7]; + + left[0] = x1; + left[1] = y1; + + right[6] = x2; + right[7] = y2; + + x1 = (x1 + cx1) / 2.0d; + y1 = (y1 + cy1) / 2.0d; + x2 = (x2 + cx2) / 2.0d; + y2 = (y2 + cy2) / 2.0d; + + double cx = (cx1 + cx2) / 2.0d; + double cy = (cy1 + cy2) / 2.0d; + + cx1 = (x1 + cx) / 2.0d; + cy1 = (y1 + cy) / 2.0d; + cx2 = (x2 + cx) / 2.0d; + cy2 = (y2 + cy) / 2.0d; + cx = (cx1 + cx2) / 2.0d; + cy = (cy1 + cy2) / 2.0d; + + left[2] = x1; + left[3] = y1; + left[4] = cx1; + left[5] = cy1; + left[6] = cx; + left[7] = cy; + + right[0] = cx; + right[1] = cy; + right[2] = cx2; + right[3] = cy2; + right[4] = x2; + right[5] = y2; + } + + static void subdivideCubicAt(final double t, + final double[] src, final int offS, + final double[] pts, final int offL, final int offR) + { + double x1 = src[offS ]; + double y1 = src[offS + 1]; + double cx1 = src[offS + 2]; + double cy1 = src[offS + 3]; + double cx2 = src[offS + 4]; + double cy2 = src[offS + 5]; + double x2 = src[offS + 6]; + double y2 = src[offS + 7]; + + pts[offL ] = x1; + pts[offL + 1] = y1; + + pts[offR + 6] = x2; + pts[offR + 7] = y2; + + x1 = x1 + t * (cx1 - x1); + y1 = y1 + t * (cy1 - y1); + x2 = cx2 + t * (x2 - cx2); + y2 = cy2 + t * (y2 - cy2); + + double cx = cx1 + t * (cx2 - cx1); + double cy = cy1 + t * (cy2 - cy1); + + cx1 = x1 + t * (cx - x1); + cy1 = y1 + t * (cy - y1); + cx2 = cx + t * (x2 - cx); + cy2 = cy + t * (y2 - cy); + cx = cx1 + t * (cx2 - cx1); + cy = cy1 + t * (cy2 - cy1); + + pts[offL + 2] = x1; + pts[offL + 3] = y1; + pts[offL + 4] = cx1; + pts[offL + 5] = cy1; + pts[offL + 6] = cx; + pts[offL + 7] = cy; + + pts[offR ] = cx; + pts[offR + 1] = cy; + pts[offR + 2] = cx2; + pts[offR + 3] = cy2; + pts[offR + 4] = x2; + pts[offR + 5] = y2; + } + + static void subdivideQuad(final double[] src, + final double[] left, + final double[] right) + { + double x1 = src[0]; + double y1 = src[1]; + double cx = src[2]; + double cy = src[3]; + double x2 = src[4]; + double y2 = src[5]; + + left[0] = x1; + left[1] = y1; + + right[4] = x2; + right[5] = y2; + + x1 = (x1 + cx) / 2.0d; + y1 = (y1 + cy) / 2.0d; + x2 = (x2 + cx) / 2.0d; + y2 = (y2 + cy) / 2.0d; + cx = (x1 + x2) / 2.0d; + cy = (y1 + y2) / 2.0d; + + left[2] = x1; + left[3] = y1; + left[4] = cx; + left[5] = cy; + + right[0] = cx; + right[1] = cy; + right[2] = x2; + right[3] = y2; + } + + static void subdivideQuadAt(final double t, + final double[] src, final int offS, + final double[] pts, final int offL, final int offR) + { + double x1 = src[offS ]; + double y1 = src[offS + 1]; + double cx = src[offS + 2]; + double cy = src[offS + 3]; + double x2 = src[offS + 4]; + double y2 = src[offS + 5]; + + pts[offL ] = x1; + pts[offL + 1] = y1; + + pts[offR + 4] = x2; + pts[offR + 5] = y2; + + x1 = x1 + t * (cx - x1); + y1 = y1 + t * (cy - y1); + x2 = cx + t * (x2 - cx); + y2 = cy + t * (y2 - cy); + cx = x1 + t * (x2 - x1); + cy = y1 + t * (y2 - y1); + + pts[offL + 2] = x1; + pts[offL + 3] = y1; + pts[offL + 4] = cx; + pts[offL + 5] = cy; + + pts[offR ] = cx; + pts[offR + 1] = cy; + pts[offR + 2] = x2; + pts[offR + 3] = y2; + } + + static void subdivideLineAt(final double t, + final double[] src, final int offS, + final double[] pts, final int offL, final int offR) + { + double x1 = src[offS ]; + double y1 = src[offS + 1]; + double x2 = src[offS + 2]; + double y2 = src[offS + 3]; + + pts[offL ] = x1; + pts[offL + 1] = y1; + + pts[offR + 2] = x2; + pts[offR + 3] = y2; + + x1 = x1 + t * (x2 - x1); + y1 = y1 + t * (y2 - y1); + + pts[offL + 2] = x1; + pts[offL + 3] = y1; + + pts[offR ] = x1; + pts[offR + 1] = y1; + } + + static void subdivideAt(final double t, + final double[] src, final int offS, + final double[] pts, final int offL, final int type) + { + // if instead of switch (perf + most probable cases first) + if (type == 8) { + subdivideCubicAt(t, src, offS, pts, offL, offL + type); + } else if (type == 4) { + subdivideLineAt(t, src, offS, pts, offL, offL + type); + } else { + subdivideQuadAt(t, src, offS, pts, offL, offL + type); } } @@ -607,17 +764,17 @@ io.lineTo(_curves[e], _curves[e+1]); e += 2; continue; - case TYPE_QUADTO: - io.quadTo(_curves[e+0], _curves[e+1], - _curves[e+2], _curves[e+3]); - e += 4; - continue; case TYPE_CUBICTO: - io.curveTo(_curves[e+0], _curves[e+1], + io.curveTo(_curves[e], _curves[e+1], _curves[e+2], _curves[e+3], _curves[e+4], _curves[e+5]); e += 6; continue; + case TYPE_QUADTO: + io.quadTo(_curves[e], _curves[e+1], + _curves[e+2], _curves[e+3]); + e += 4; + continue; default: } } @@ -649,17 +806,17 @@ 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], + io.curveTo(_curves[e], _curves[e+1], _curves[e+2], _curves[e+3], _curves[e+4], _curves[e+5]); continue; + case TYPE_QUADTO: + e -= 4; + io.quadTo(_curves[e], _curves[e+1], + _curves[e+2], _curves[e+3]); + continue; default: } } --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/DMarlinRenderingEngine.java 2018-06-08 16:12:46.367686583 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/DMarlinRenderingEngine.java 2018-06-08 16:12:46.203686586 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -151,8 +151,14 @@ logInfo("prism.marlin.edges = " + MarlinConst.INITIAL_EDGES_COUNT); - logInfo("prism.marlin.pixelsize = " - + MarlinConst.INITIAL_PIXEL_DIM); + logInfo("prism.marlin.pixelWidth = " + + MarlinConst.INITIAL_PIXEL_WIDTH); + logInfo("prism.marlin.pixelHeight = " + + MarlinConst.INITIAL_PIXEL_HEIGHT); + + logInfo("prism.marlin.profile = " + + (MarlinProperties.isProfileQuality() ? + "quality" : "speed")); logInfo("prism.marlin.subPixel_log2_X = " + MarlinConst.SUBPIXEL_LG_POSITIONS_X); @@ -178,11 +184,21 @@ // optimisation parameters logInfo("prism.marlin.useSimplifier = " + MarlinConst.USE_SIMPLIFIER); + logInfo("prism.marlin.usePathSimplifier= " + + MarlinConst.USE_PATH_SIMPLIFIER); + logInfo("prism.marlin.pathSimplifier.pixTol = " + + MarlinProperties.getPathSimplifierPixelTolerance()); + logInfo("prism.marlin.clip = " + MarlinProperties.isDoClip()); logInfo("prism.marlin.clip.runtime.enable = " + MarlinProperties.isDoClipRuntimeFlag()); + logInfo("prism.marlin.clip.subdivider = " + + MarlinProperties.isDoClipSubdivider()); + logInfo("prism.marlin.clip.subdivider.minLength = " + + MarlinProperties.getSubdividerMinLength()); + // debugging parameters logInfo("prism.marlin.doStats = " + MarlinConst.DO_STATS); @@ -202,6 +218,8 @@ + MarlinConst.LOG_UNSAFE_MALLOC); // quality settings + logInfo("prism.marlin.curve_len_err = " + + MarlinProperties.getCurveLengthError()); logInfo("prism.marlin.cubic_dec_d2 = " + MarlinProperties.getCubicDecD2()); logInfo("prism.marlin.cubic_inc_d1 = " --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/DPathConsumer2D.java 2018-06-08 16:12:46.819686577 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/DPathConsumer2D.java 2018-06-08 16:12:46.655686579 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2017, 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 --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/DRenderer.java 2018-06-08 16:12:47.267686571 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/DRenderer.java 2018-06-08 16:12:47.103686573 +0200 @@ -62,13 +62,17 @@ // curve break into lines // cubic error in subpixels to decrement step private static final double CUB_DEC_ERR_SUBPIX - = MarlinProperties.getCubicDecD2() * (NORM_SUBPIXELS / 8.0d); // 1 pixel + = MarlinProperties.getCubicDecD2() * (SUBPIXEL_POSITIONS_X / 8.0d); // 1.0 / 8th pixel // cubic error in subpixels to increment step private static final double CUB_INC_ERR_SUBPIX - = MarlinProperties.getCubicIncD1() * (NORM_SUBPIXELS / 8.0d); // 0.4 pixel + = MarlinProperties.getCubicIncD1() * (SUBPIXEL_POSITIONS_X / 8.0d); // 0.4 / 8th pixel + // scale factor for Y-axis contribution to quad / cubic errors: + public static final double SCALE_DY = ((double) SUBPIXEL_POSITIONS_X) / SUBPIXEL_POSITIONS_Y; // TestNonAARasterization (JDK-8170879): cubics // bad paths (59294/100000 == 59,29%, 94335 bad pixels (avg = 1,59), 3966 warnings (avg = 0,07) +// 2018 + // 1.0 / 0.2: bad paths (67194/100000 == 67,19%, 117394 bad pixels (avg = 1,75 - max = 9), 4042 warnings (avg = 0,06) // cubic bind length to decrement step public static final double CUB_DEC_BND @@ -95,10 +99,12 @@ // quad break into lines // quadratic error in subpixels private static final double QUAD_DEC_ERR_SUBPIX - = MarlinProperties.getQuadDecD2() * (NORM_SUBPIXELS / 8.0d); // 0.5 pixel + = MarlinProperties.getQuadDecD2() * (SUBPIXEL_POSITIONS_X / 8.0d); // 0.5 / 8th pixel // TestNonAARasterization (JDK-8170879): quads // bad paths (62916/100000 == 62,92%, 103818 bad pixels (avg = 1,65), 6514 warnings (avg = 0,10) +// 2018 + // 0.50px = bad paths (62915/100000 == 62,92%, 103810 bad pixels (avg = 1,65), 6512 warnings (avg = 0,10) // quadratic bind length to decrement step public static final double QUAD_DEC_BND @@ -167,7 +173,7 @@ int count = 1; // dt = 1 / count // maximum(ddX|Y) = norm(dbx, dby) * dt^2 (= 1) - double maxDD = Math.abs(c.dbx) + Math.abs(c.dby); + double maxDD = Math.abs(c.dbx) + Math.abs(c.dby) * SCALE_DY; final double _DEC_BND = QUAD_DEC_BND; @@ -181,7 +187,8 @@ } } - int nL = 0; // line count + final int nL = count; // line count + if (count > 1) { final double icount = 1.0d / count; // dt final double icount2 = icount * icount; // dt^2 @@ -191,17 +198,12 @@ 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; + // we use x0, y0 to walk the line + for (double x1 = x0, y1 = y0; --count > 0; dx += ddx, dy += ddy) { + x1 += dx; + y1 += dy; addLine(x0, y0, x1, y1); - - if (DO_STATS) { nL++; } x0 = x1; y0 = y1; } @@ -209,7 +211,7 @@ addLine(x0, y0, x2, y2); if (DO_STATS) { - rdrCtx.stats.stat_rdr_quadBreak.add(nL + 1); + rdrCtx.stats.stat_rdr_quadBreak.add(nL); } } @@ -222,7 +224,7 @@ final DCurve c, final double x3, final double y3) { - int count = CUB_COUNT; + 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 @@ -237,34 +239,20 @@ 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; + final double _SCALE_DY = SCALE_DY; - while (count > 0) { - // divide step by half: - while (Math.abs(ddx) + Math.abs(ddy) >= _DEC_BND) { - dddx /= 8.0d; - dddy /= 8.0d; - ddx = ddx / 4.0d - dddx; - ddy = ddy / 4.0d - dddy; - dx = (dx - ddx) / 2.0d; - dy = (dy - ddy) / 2.0d; - - count <<= 1; - if (DO_STATS) { - rdrCtx.stats.stat_rdr_curveBreak_dec.add(count); - } - } + // we use x0, y0 to walk the line + for (double x1 = x0, y1 = y0; count > 0; ) { + // inc / dec => ratio ~ 5 to minimize upscale / downscale but minimize edges // double step: // can only do this on even "count" values, because we must divide count by 2 - while (count % 2 == 0 - && Math.abs(dx) + Math.abs(dy) <= _INC_BND) - { + while ((count % 2 == 0) + && ((Math.abs(ddx) + Math.abs(ddy) * _SCALE_DY) <= _INC_BND)) { dx = 2.0d * dx + ddx; dy = 2.0d * dy + ddy; ddx = 4.0d * (ddx + dddx); @@ -277,26 +265,40 @@ 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; + + // divide step by half: + while ((Math.abs(ddx) + Math.abs(ddy) * _SCALE_DY) >= _DEC_BND) { + dddx /= 8.0d; + dddy /= 8.0d; + ddx = ddx / 4.0d - dddx; + ddy = ddy / 4.0d - dddy; + dx = (dx - ddx) / 2.0d; + dy = (dy - ddy) / 2.0d; + + count <<= 1; + if (DO_STATS) { + rdrCtx.stats.stat_rdr_curveBreak_dec.add(count); + } + } + if (--count == 0) { + break; } - addLine(x0, y0, x1, y1); + x1 += dx; + y1 += dy; + dx += ddx; + dy += ddy; + ddx += dddx; + ddy += dddy; - if (DO_STATS) { nL++; } + addLine(x0, y0, x1, y1); x0 = x1; y0 = y1; } + addLine(x0, y0, x3, y3); + if (DO_STATS) { - rdrCtx.stats.stat_rdr_curveBreak.add(nL); + rdrCtx.stats.stat_rdr_curveBreak.add(nL + 1); } } @@ -677,8 +679,10 @@ { final double xe = tosubpixx(pix_x3); final double ye = tosubpixy(pix_y3); - curve.set(x0, y0, tosubpixx(pix_x1), tosubpixy(pix_y1), - tosubpixx(pix_x2), tosubpixy(pix_y2), xe, ye); + curve.set(x0, y0, + tosubpixx(pix_x1), tosubpixy(pix_y1), + tosubpixx(pix_x2), tosubpixy(pix_y2), + xe, ye); curveBreakIntoLinesAndAdd(x0, y0, curve, xe, ye); x0 = xe; y0 = ye; @@ -690,7 +694,9 @@ { final double xe = tosubpixx(pix_x2); final double ye = tosubpixy(pix_y2); - curve.set(x0, y0, tosubpixx(pix_x1), tosubpixy(pix_y1), xe, ye); + curve.set(x0, y0, + tosubpixx(pix_x1), tosubpixy(pix_y1), + xe, ye); quadBreakIntoLinesAndAdd(x0, y0, curve, xe, ye); x0 = xe; y0 = ye; --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/DRendererContext.java 2018-06-08 16:12:47.615686566 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/DRendererContext.java 2018-06-08 16:12:47.447686569 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,12 +25,14 @@ package com.sun.marlin; -import com.sun.javafx.geom.Path2D; +import java.lang.ref.WeakReference; import java.util.concurrent.atomic.AtomicInteger; -import com.sun.util.reentrant.ReentrantContext; +import com.sun.javafx.geom.Path2D; import com.sun.javafx.geom.Rectangle; import com.sun.marlin.ArrayCacheConst.CacheStats; -import java.lang.ref.WeakReference; +import com.sun.marlin.DTransformingPathConsumer2D.CurveBasicMonotonizer; +import com.sun.marlin.DTransformingPathConsumer2D.CurveClipSplitter; +import com.sun.util.reentrant.ReentrantContext; /** * This class is a renderer context dedicated to a single thread @@ -62,13 +64,12 @@ public final DTransformingPathConsumer2D transformerPC2D; // recycled Path2D instance (weak) private WeakReference refPath2D = null; - // shared memory between renderer instances: - final DRendererSharedMemory rdrMem; public final DRenderer renderer; - private DRendererNoAA rendererNoAA = null; public final DStroker stroker; // Simplifies out collinear lines public final DCollinearSimplifier simplifier = new DCollinearSimplifier(); + // Simplifies path + public final DPathSimplifier pathSimplifier = new DPathSimplifier(); public final DDasher dasher; // flag indicating the shape is stroked (1) or filled (0) int stroking = 0; @@ -78,8 +79,15 @@ boolean closedPath = false; // clip rectangle (ymin, ymax, xmin, xmax): public final double[] clipRect = new double[4]; + // CurveBasicMonotonizer instance + public final CurveBasicMonotonizer monotonizer; + // CurveClipSplitter instance + final CurveClipSplitter curveClipSplitter; // MarlinFX specific: + // shared memory between renderer instances: + final DRendererSharedMemory rdrMem; + private DRendererNoAA rendererNoAA = null; // dirty bbox rectangle public final Rectangle clip = new Rectangle(); // dirty MaskMarlinAlphaConsumer @@ -120,6 +128,10 @@ stats = null; } + // curve monotonizer & clip subdivider (before transformerPC2D init) + monotonizer = new CurveBasicMonotonizer(this); + curveClipSplitter = new CurveClipSplitter(this); + // MarlinRenderingEngine.TransformingPathConsumer2D transformerPC2D = new DTransformingPathConsumer2D(this); @@ -241,8 +253,8 @@ edgeBuckets_ref = rdrCtx.newCleanIntArrayRef(INITIAL_BUCKET_ARRAY); // 64K edgeBucketCounts_ref = rdrCtx.newCleanIntArrayRef(INITIAL_BUCKET_ARRAY); // 64K - // 2048 (pixelsize) pixel large - alphaLine_ref = rdrCtx.newCleanIntArrayRef(INITIAL_AA_ARRAY); // 8K + // 4096 pixels large + alphaLine_ref = rdrCtx.newCleanIntArrayRef(INITIAL_AA_ARRAY); // 16K crossings_ref = rdrCtx.newDirtyIntArrayRef(INITIAL_CROSSING_COUNT); // 2K aux_crossings_ref = rdrCtx.newDirtyIntArrayRef(INITIAL_CROSSING_COUNT); // 2K --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/DRendererNoAA.java 2018-06-08 16:12:47.947686562 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/DRendererNoAA.java 2018-06-08 16:12:47.787686564 +0200 @@ -63,6 +63,8 @@ // TestNonAARasterization (JDK-8170879): cubics // bad paths (59294/100000 == 59,29%, 94335 bad pixels (avg = 1,59), 3966 warnings (avg = 0,07) +// 2018 + // 1.0 / 0.2: bad paths (67194/100000 == 67,19%, 117394 bad pixels (avg = 1,75 - max = 9), 4042 warnings (avg = 0,06) // cubic bind length to decrement step public static final double CUB_DEC_BND @@ -93,6 +95,8 @@ // TestNonAARasterization (JDK-8170879): quads // bad paths (62916/100000 == 62,92%, 103818 bad pixels (avg = 1,65), 6514 warnings (avg = 0,10) +// 2018 + // 0.50px = bad paths (62915/100000 == 62,92%, 103810 bad pixels (avg = 1,65), 6512 warnings (avg = 0,10) // quadratic bind length to decrement step public static final double QUAD_DEC_BND @@ -175,7 +179,8 @@ } } - int nL = 0; // line count + final int nL = count; // line count + if (count > 1) { final double icount = 1.0d / count; // dt final double icount2 = icount * icount; // dt^2 @@ -185,17 +190,12 @@ 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; + // we use x0, y0 to walk the line + for (double x1 = x0, y1 = y0; --count > 0; dx += ddx, dy += ddy) { + x1 += dx; + y1 += dy; addLine(x0, y0, x1, y1); - - if (DO_STATS) { nL++; } x0 = x1; y0 = y1; } @@ -203,7 +203,7 @@ addLine(x0, y0, x2, y2); if (DO_STATS) { - rdrCtx.stats.stat_rdr_quadBreak.add(nL + 1); + rdrCtx.stats.stat_rdr_quadBreak.add(nL); } } @@ -216,7 +216,7 @@ final DCurve c, final double x3, final double y3) { - int count = CUB_COUNT; + 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 @@ -231,34 +231,19 @@ 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) + Math.abs(ddy) >= _DEC_BND) { - dddx /= 8.0d; - dddy /= 8.0d; - ddx = ddx / 4.0d - dddx; - ddy = ddy / 4.0d - dddy; - dx = (dx - ddx) / 2.0d; - dy = (dy - ddy) / 2.0d; - - count <<= 1; - if (DO_STATS) { - rdrCtx.stats.stat_rdr_curveBreak_dec.add(count); - } - } + // we use x0, y0 to walk the line + for (double x1 = x0, y1 = y0; count > 0; ) { + // inc / dec => ratio ~ 5 to minimize upscale / downscale but minimize edges // double step: // can only do this on even "count" values, because we must divide count by 2 - while (count % 2 == 0 - && Math.abs(dx) + Math.abs(dy) <= _INC_BND) - { + while ((count % 2 == 0) + && ((Math.abs(ddx) + Math.abs(ddy)) <= _INC_BND)) { dx = 2.0d * dx + ddx; dy = 2.0d * dy + ddy; ddx = 4.0d * (ddx + dddx); @@ -271,26 +256,40 @@ 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; + + // divide step by half: + while ((Math.abs(ddx) + Math.abs(ddy)) >= _DEC_BND) { + dddx /= 8.0d; + dddy /= 8.0d; + ddx = ddx / 4.0d - dddx; + ddy = ddy / 4.0d - dddy; + dx = (dx - ddx) / 2.0d; + dy = (dy - ddy) / 2.0d; + + count <<= 1; + if (DO_STATS) { + rdrCtx.stats.stat_rdr_curveBreak_dec.add(count); + } + } + if (--count == 0) { + break; } - addLine(x0, y0, x1, y1); + x1 += dx; + y1 += dy; + dx += ddx; + dy += ddy; + ddx += dddx; + ddy += dddy; - if (DO_STATS) { nL++; } + addLine(x0, y0, x1, y1); x0 = x1; y0 = y1; } + addLine(x0, y0, x3, y3); + if (DO_STATS) { - rdrCtx.stats.stat_rdr_curveBreak.add(nL); + rdrCtx.stats.stat_rdr_curveBreak.add(nL + 1); } } @@ -669,8 +668,10 @@ { final double xe = tosubpixx(pix_x3); final double ye = tosubpixy(pix_y3); - curve.set(x0, y0, tosubpixx(pix_x1), tosubpixy(pix_y1), - tosubpixx(pix_x2), tosubpixy(pix_y2), xe, ye); + curve.set(x0, y0, + tosubpixx(pix_x1), tosubpixy(pix_y1), + tosubpixx(pix_x2), tosubpixy(pix_y2), + xe, ye); curveBreakIntoLinesAndAdd(x0, y0, curve, xe, ye); x0 = xe; y0 = ye; @@ -682,7 +683,9 @@ { final double xe = tosubpixx(pix_x2); final double ye = tosubpixy(pix_y2); - curve.set(x0, y0, tosubpixx(pix_x1), tosubpixy(pix_y1), xe, ye); + curve.set(x0, y0, + tosubpixx(pix_x1), tosubpixy(pix_y1), + xe, ye); quadBreakIntoLinesAndAdd(x0, y0, curve, xe, ye); x0 = xe; y0 = ye; --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/DStroker.java 2018-06-08 16:12:48.295686557 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/DStroker.java 2018-06-08 16:12:48.131686559 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,6 +27,8 @@ import java.util.Arrays; import com.sun.marlin.DHelpers.PolyStack; +import com.sun.marlin.DTransformingPathConsumer2D.CurveBasicMonotonizer; +import com.sun.marlin.DTransformingPathConsumer2D.CurveClipSplitter; // 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 @@ -37,10 +39,9 @@ private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad private static final int CLOSE = 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 floating point, so that's why the divisions by 2^16 are there. - private static final double ROUND_JOIN_THRESHOLD = 1000.0d/65536.0d; + // round join threshold = 1 subpixel + private static final double ERR_JOIN = (1.0f / MIN_SUBPIXELS); + private static final double ROUND_JOIN_THRESHOLD = ERR_JOIN * ERR_JOIN; // kappa = (4/3) * (SQRT(2) - 1) private static final double C = (4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d); @@ -48,8 +49,6 @@ // SQRT(2) private static final double SQRT_2 = Math.sqrt(2.0d); - private static final int MAX_N_CURVES = 11; - private DPathConsumer2D out; private int capStyle; @@ -80,12 +79,8 @@ 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 * 6 + 2]; 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; @@ -106,6 +101,11 @@ private boolean opened = false; // flag indicating if the starting point's cap is done private boolean capStart = false; + // flag indicating to monotonize curves + private boolean monotonize; + + private boolean subdivide = false; + private final CurveClipSplitter curveSplitter; /** * Constructs a DStroker. @@ -124,6 +124,7 @@ : new PolyStack(rdrCtx); this.curve = rdrCtx.curve; + this.curveSplitter = rdrCtx.curveClipSplitter; } /** @@ -141,6 +142,7 @@ * @param scale scaling factor applied to clip boundaries * @param rdrOffX renderer's coordinate offset on X axis * @param rdrOffY renderer's coordinate offset on Y axis + * @param subdivideCurves true to indicate to subdivide curves, false if dasher does * @return this instance */ public DStroker init(final DPathConsumer2D pc2d, @@ -150,12 +152,15 @@ final double miterLimit, final double scale, double rdrOffX, - double rdrOffY) + double rdrOffY, + final boolean subdivideCurves) { this.out = pc2d; this.lineWidth2 = lineWidth / 2.0d; this.invHalfLineWidth2Sq = 1.0d / (2.0d * lineWidth2 * lineWidth2); + this.monotonize = subdivideCurves; + this.capStyle = capStyle; this.joinStyle = joinStyle; @@ -192,6 +197,15 @@ _clipRect[2] -= margin - rdrOffX; _clipRect[3] += margin + rdrOffX; this.clipRect = _clipRect; + + // initialize curve splitter here for stroker & dasher: + if (DO_CLIP_SUBDIVIDER) { + subdivide = subdivideCurves; + // adjust padded clip rectangle: + curveSplitter.init(); + } else { + subdivide = false; + } } else { this.clipRect = null; this.cOutCode = 0; @@ -200,6 +214,12 @@ return this; // fluent API } + public void disableClipping() { + this.clipRect = null; + this.cOutCode = 0; + this.sOutCode = 0; + } + /** * Disposes this stroker: * clean up before reusing this instance @@ -216,10 +236,8 @@ Arrays.fill(offset1, 0.0d); Arrays.fill(offset2, 0.0d); Arrays.fill(miter, 0.0d); - Arrays.fill(middle, 0.0d); Arrays.fill(lp, 0.0d); Arrays.fill(rp, 0.0d); - Arrays.fill(subdivTs, 0.0d); } } @@ -251,19 +269,20 @@ return dx1 * dy2 <= dy1 * dx2; } - private void drawRoundJoin(double x, double y, - double omx, double omy, double mx, double my, - boolean rev, - double threshold) + private void mayDrawRoundJoin(double cx, double cy, + double omx, double omy, + double mx, double my, + boolean rev) { if ((omx == 0.0d && omy == 0.0d) || (mx == 0.0d && my == 0.0d)) { return; } - double domx = omx - mx; - double domy = omy - my; - double len = domx*domx + domy*domy; - if (len < threshold) { + final double domx = omx - mx; + final double domy = omy - my; + final double lenSq = domx*domx + domy*domy; + + if (lenSq < ROUND_JOIN_THRESHOLD) { return; } @@ -273,7 +292,7 @@ mx = -mx; my = -my; } - drawRoundJoin(x, y, omx, omy, mx, my, rev); + drawRoundJoin(cx, cy, omx, omy, mx, my, rev); } private void drawRoundJoin(double cx, double cy, @@ -288,13 +307,9 @@ // 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 numCurves = (cosext >= 0.0d) ? 1 : 2; - - switch (numCurves) { - case 1: + if (cosext >= 0.0d) { drawBezApproxForArc(cx, cy, omx, omy, mx, my, rev); - break; - case 2: + } else { // 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 @@ -323,8 +338,6 @@ } drawBezApproxForArc(cx, cy, omx, omy, mmx, mmy, rev); drawBezApproxForArc(cx, cy, mmx, mmy, mx, my, rev); - break; - default: } } @@ -384,7 +397,7 @@ final double x1, final double y1, final double x0p, final double y0p, final double x1p, final double y1p, - final double[] m, int off) + final double[] m) { double x10 = x1 - x0; double y10 = y1 - y0; @@ -403,8 +416,8 @@ 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; + m[0] = x0 + t*x10; + m[1] = y0 + t*y10; } // Return the intersection point of the lines (x0, y0) -> (x1, y1) @@ -413,7 +426,7 @@ final double x1, final double y1, final double x0p, final double y0p, final double x1p, final double y1p, - final double[] m, int off) + final double[] m) { double x10 = x1 - x0; double y10 = y1 - y0; @@ -431,20 +444,21 @@ // immediately). double den = x10*y10p - x10p*y10; if (den == 0.0d) { - m[off++] = (x0 + x0p) / 2.0d; - m[off] = (y0 + y0p) / 2.0d; - return; + m[2] = (x0 + x0p) / 2.0d; + m[3] = (y0 + y0p) / 2.0d; + } else { + double t = x10p*(y0-y0p) - y10p*(x0-x0p); + t /= den; + m[2] = x0 + t*x10; + m[3] = y0 + t*y10; } - 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, + double omx, double omy, + double mx, double my, boolean rev) { if ((mx == omx && my == omy) || @@ -462,8 +476,7 @@ } computeMiter((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy, - (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my, - miter, 0); + (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my, miter); final double miterX = miter[0]; final double miterY = miter[1]; @@ -481,7 +494,7 @@ @Override public void moveTo(final double x0, final double y0) { - moveTo(x0, y0, cOutCode); + _moveTo(x0, y0, cOutCode); // update starting point: this.sx0 = x0; this.sy0 = y0; @@ -497,7 +510,7 @@ } } - private void moveTo(final double x0, final double y0, + private void _moveTo(final double x0, final double y0, final int outcode) { if (prev == MOVE_TO) { @@ -524,16 +537,40 @@ final boolean force) { final int outcode0 = this.cOutCode; + if (!force && clipRect != null) { final int outcode1 = DHelpers.outcode(x1, y1, clipRect); - this.cOutCode = outcode1; - // basic rejection criteria - if ((outcode0 & outcode1) != 0) { - moveTo(x1, y1, outcode0); - opened = true; - return; + // Should clip + final int orCode = (outcode0 | outcode1); + if (orCode != 0) { + final int sideCode = outcode0 & outcode1; + + // basic rejection criteria: + if (sideCode == 0) { + // ovelap clip: + if (subdivide) { + // avoid reentrance + subdivide = false; + // subdivide curve => callback with subdivided parts: + boolean ret = curveSplitter.splitLine(cx0, cy0, x1, y1, + orCode, this); + // reentrance is done: + subdivide = true; + if (ret) { + return; + } + } + // already subdivided so render it + } else { + this.cOutCode = outcode1; + _moveTo(x1, y1, outcode0); + opened = true; + return; + } } + + this.cOutCode = outcode1; } double dx = x1 - cx0; @@ -755,10 +792,7 @@ 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); + mayDrawRoundJoin(x0, y0, omx, omy, mx, my, cw); } } emitLineTo(x0, y0, !cw); @@ -768,18 +802,19 @@ private static boolean within(final double x1, final double y1, final double x2, final double y2, - final double ERR) + final double err) { - assert ERR > 0 : ""; + 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. + 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) { + private void getLineOffsets(final double x1, final double y1, + final double x2, final double y2, + final double[] left, final double[] right) + { computeOffset(x2 - x1, y2 - y1, lineWidth2, offset0); final double mx = offset0[0]; final double my = offset0[1]; @@ -787,14 +822,16 @@ 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) + private int computeOffsetCubic(final double[] pts, final int off, + final double[] leftOff, + final double[] rightOff) { // if p1=p2 or p3=p4 it means that the derivative at the endpoint // vanishes, which creates problems with computeOffset. Usually @@ -803,7 +840,7 @@ // 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 x1 = pts[off ], 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]; @@ -817,6 +854,7 @@ // in which case ignore if p1 == p2 final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0d * Math.ulp(y2)); final boolean p3eqp4 = within(x3, y3, x4, y4, 6.0d * Math.ulp(y4)); + if (p1eqp2 && p3eqp4) { getLineOffsets(x1, y1, x4, y4, leftOff, rightOff); return 4; @@ -832,6 +870,7 @@ 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, 4.0d * Math.ulp(dotsq))) { getLineOffsets(x1, y1, x4, y4, leftOff, rightOff); return 4; @@ -945,10 +984,11 @@ // compute offset curves using bezier spline through t=0.5 (i.e. // ComputedCurve(0.5) == IdealParallelCurve(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) + private int computeOffsetQuad(final double[] pts, final int off, + final double[] leftOff, + final double[] rightOff) { - final double x1 = pts[off + 0], y1 = pts[off + 1]; + final double x1 = pts[off ], y1 = pts[off + 1]; final double x2 = pts[off + 2], y2 = pts[off + 3]; final double x3 = pts[off + 4], y3 = pts[off + 5]; @@ -969,6 +1009,7 @@ // in which case ignore. final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0d * Math.ulp(y2)); final boolean p2eqp3 = within(x2, y2, x3, y3, 6.0d * Math.ulp(y3)); + if (p1eqp2 || p2eqp3) { getLineOffsets(x1, y1, x3, y3, leftOff, rightOff); return 4; @@ -978,6 +1019,7 @@ 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, 4.0d * Math.ulp(dotsq))) { getLineOffsets(x1, y1, x3, y3, leftOff, rightOff); return 4; @@ -993,151 +1035,111 @@ 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); + safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, leftOff); 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); + safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, rightOff); rightOff[0] = x1p; rightOff[1] = y1p; rightOff[4] = x3p; rightOff[5] = y3p; return 6; } - // 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 != 0.0d && x12 != 0.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(final double x1, final double y1, final double x2, final double y2, final double x3, final double y3) { final int outcode0 = this.cOutCode; + if (clipRect != null) { + final int outcode1 = DHelpers.outcode(x1, y1, clipRect); + final int outcode2 = DHelpers.outcode(x2, y2, clipRect); final int outcode3 = DHelpers.outcode(x3, y3, clipRect); - this.cOutCode = outcode3; - if ((outcode0 & outcode3) != 0) { - final int outcode1 = DHelpers.outcode(x1, y1, clipRect); - final int outcode2 = DHelpers.outcode(x2, y2, clipRect); - - // basic rejection criteria - if ((outcode0 & outcode1 & outcode2 & outcode3) != 0) { - moveTo(x3, y3, outcode0); + // Should clip + final int orCode = (outcode0 | outcode1 | outcode2 | outcode3); + if (orCode != 0) { + final int sideCode = outcode0 & outcode1 & outcode2 & outcode3; + + // basic rejection criteria: + if (sideCode == 0) { + // ovelap clip: + if (subdivide) { + // avoid reentrance + subdivide = false; + // subdivide curve => callback with subdivided parts: + boolean ret = curveSplitter.splitCurve(cx0, cy0, x1, y1, + x2, y2, x3, y3, + orCode, this); + // reentrance is done: + subdivide = true; + if (ret) { + return; + } + } + // already subdivided so render it + } else { + this.cOutCode = outcode3; + _moveTo(x3, y3, outcode0); opened = true; return; } } - } - 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; + this.cOutCode = outcode3; + } + _curveTo(x1, y1, x2, y2, x3, y3, outcode0); + } + private void _curveTo(final double x1, final double y1, + final double x2, final double y2, + final double x3, final double y3, + final int outcode0) + { // need these so we can update the state at the end of this method - final double xf = x3, yf = y3; - 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 == 0.0d && dys == 0.0d); - boolean p3eqp4 = (dxf == 0.0d && dyf == 0.0d); - if (p1eqp2) { - dxs = mid[4] - mid[0]; - dys = mid[5] - mid[1]; - if (dxs == 0.0d && dys == 0.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 == 0.0d && dyf == 0.0d) { - dxf = mid[6] - mid[0]; - dyf = mid[7] - mid[1]; + double dxs = x1 - cx0; + double dys = y1 - cy0; + double dxf = x3 - x2; + double dyf = y3 - y2; + + if ((dxs == 0.0d) && (dys == 0.0d)) { + dxs = x2 - cx0; + dys = y2 - cy0; + if ((dxs == 0.0d) && (dys == 0.0d)) { + dxs = x3 - cx0; + dys = y3 - cy0; + } + } + if ((dxf == 0.0d) && (dyf == 0.0d)) { + dxf = x3 - x1; + dyf = y3 - y1; + if ((dxf == 0.0d) && (dyf == 0.0d)) { + dxf = x3 - cx0; + dyf = y3 - cy0; } } - if (dxs == 0.0d && dys == 0.0d) { + if ((dxs == 0.0d) && (dys == 0.0d)) { // this happens if the "curve" is just a point // fix outcode0 for lineTo() call: if (clipRect != null) { this.cOutCode = outcode0; } - lineTo(mid[0], mid[1]); + lineTo(cx0, cy0); 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); + final 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); + final double len = Math.sqrt(dxf * dxf + dyf * dyf); dxf /= len; dyf /= len; } @@ -1145,17 +1147,25 @@ computeOffset(dxs, dys, lineWidth2, offset0); drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0); - final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2); + int nSplits = 0; + final double[] mid; + final double[] l = lp; - double prevT = 0.0d; - for (int i = 0, off = 0; i < nSplits; i++, off += 6) { - final double t = subdivTs[i]; - DHelpers.subdivideCubicAt((t - prevT) / (1.0d - prevT), - mid, off, mid, off, mid, off + 6); - prevT = t; - } + if (monotonize) { + // monotonize curve: + final CurveBasicMonotonizer monotonizer + = rdrCtx.monotonizer.curve(cx0, cy0, x1, y1, x2, y2, x3, y3); - final double[] l = lp; + nSplits = monotonizer.nbSplits; + mid = monotonizer.middle; + } else { + // use left instead: + mid = l; + mid[0] = cx0; mid[1] = cy0; + mid[2] = x1; mid[3] = y1; + mid[4] = x2; mid[5] = y2; + mid[6] = x3; mid[7] = y3; + } final double[] r = rp; int kind = 0; @@ -1179,8 +1189,8 @@ } this.prev = DRAWING_OP_TO; - this.cx0 = xf; - this.cy0 = yf; + this.cx0 = x3; + this.cy0 = y3; this.cdx = dxf; this.cdy = dyf; this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d; @@ -1192,74 +1202,101 @@ final double x2, final double y2) { final int outcode0 = this.cOutCode; + if (clipRect != null) { + final int outcode1 = DHelpers.outcode(x1, y1, clipRect); final int outcode2 = DHelpers.outcode(x2, y2, clipRect); - this.cOutCode = outcode2; - - if ((outcode0 & outcode2) != 0) { - final int outcode1 = DHelpers.outcode(x1, y1, clipRect); - // basic rejection criteria - if ((outcode0 & outcode1 & outcode2) != 0) { - moveTo(x2, y2, outcode0); + // Should clip + final int orCode = (outcode0 | outcode1 | outcode2); + if (orCode != 0) { + final int sideCode = outcode0 & outcode1 & outcode2; + + // basic rejection criteria: + if (sideCode == 0) { + // ovelap clip: + if (subdivide) { + // avoid reentrance + subdivide = false; + // subdivide curve => call lineTo() with subdivided curves: + boolean ret = curveSplitter.splitQuad(cx0, cy0, x1, y1, + x2, y2, orCode, this); + // reentrance is done: + subdivide = true; + if (ret) { + return; + } + } + // already subdivided so render it + } else { + this.cOutCode = outcode2; + _moveTo(x2, y2, outcode0); opened = true; return; } } - } - final double[] mid = middle; - - mid[0] = cx0; mid[1] = cy0; - mid[2] = x1; mid[3] = y1; - mid[4] = x2; mid[5] = y2; + this.cOutCode = outcode2; + } + _quadTo(x1, y1, x2, y2, outcode0); + } + private void _quadTo(final double x1, final double y1, + final double x2, final double y2, + final int outcode0) + { // need these so we can update the state at the end of this method - final double xf = x2, yf = y2; - 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 == 0.0d && dys == 0.0d) || (dxf == 0.0d && dyf == 0.0d)) { - dxs = dxf = mid[4] - mid[0]; - dys = dyf = mid[5] - mid[1]; + double dxs = x1 - cx0; + double dys = y1 - cy0; + double dxf = x2 - x1; + double dyf = y2 - y1; + + if (((dxs == 0.0d) && (dys == 0.0d)) || ((dxf == 0.0d) && (dyf == 0.0d))) { + dxs = dxf = x2 - cx0; + dys = dyf = y2 - cy0; } - if (dxs == 0.0d && dys == 0.0d) { + if ((dxs == 0.0d) && (dys == 0.0d)) { // this happens if the "curve" is just a point // fix outcode0 for lineTo() call: if (clipRect != null) { this.cOutCode = outcode0; } - lineTo(mid[0], mid[1]); + lineTo(cx0, cy0); 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); + final 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); + final 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], outcode0); - int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2); + int nSplits = 0; + final double[] mid; + final double[] l = lp; - double prevt = 0.0d; - for (int i = 0, off = 0; i < nSplits; i++, off += 4) { - final double t = subdivTs[i]; - DHelpers.subdivideQuadAt((t - prevt) / (1.0d - prevt), - mid, off, mid, off, mid, off + 4); - prevt = t; - } + if (monotonize) { + // monotonize quad: + final CurveBasicMonotonizer monotonizer + = rdrCtx.monotonizer.quad(cx0, cy0, x1, y1, x2, y2); - final double[] l = lp; + nSplits = monotonizer.nbSplits; + mid = monotonizer.middle; + } else { + // use left instead: + mid = l; + mid[0] = cx0; mid[1] = cy0; + mid[2] = x1; mid[3] = y1; + mid[4] = x2; mid[5] = y2; + } final double[] r = rp; int kind = 0; @@ -1283,8 +1320,8 @@ } this.prev = DRAWING_OP_TO; - this.cx0 = xf; - this.cy0 = yf; + this.cx0 = x2; + this.cy0 = y2; this.cdx = dxf; this.cdy = dyf; this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d; --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/DTransformingPathConsumer2D.java 2018-06-08 16:12:48.651686552 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/DTransformingPathConsumer2D.java 2018-06-08 16:12:48.491686554 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,9 +29,13 @@ import com.sun.javafx.geom.transform.BaseTransform; import com.sun.marlin.DHelpers.IndexStack; import com.sun.marlin.DHelpers.PolyStack; +import java.util.Arrays; public final class DTransformingPathConsumer2D { + // smaller uncertainty in double variant + static final double CLIP_RECT_PADDING = 0.25d; + private final DRendererContext rdrCtx; // recycled ClosedPathDetector instance from detectClosedPath() @@ -56,6 +60,7 @@ private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector"); private final PathTracer tracerFiller = new PathTracer("Filler"); private final PathTracer tracerStroker = new PathTracer("Stroker"); + private final PathTracer tracerDasher = new PathTracer("Dasher"); DTransformingPathConsumer2D(final DRendererContext rdrCtx) { // used by RendererContext @@ -84,6 +89,10 @@ return tracerStroker.init(out); } + public DPathConsumer2D traceDasher(DPathConsumer2D out) { + return tracerDasher.init(out); + } + public DPathConsumer2D detectClosedPath(DPathConsumer2D out) { return cpDetector.init(out); } @@ -486,11 +495,19 @@ private boolean outside = false; - // The current point OUTSIDE + // The current point (TODO stupid repeated info) private double cx0, cy0; + // The current point OUTSIDE + private double cox0, coy0; + + private boolean subdivide = MarlinConst.DO_CLIP_SUBDIVIDER; + private final CurveClipSplitter curveSplitter; + PathClipFilter(final DRendererContext rdrCtx) { this.clipRect = rdrCtx.clipRect; + this.curveSplitter = rdrCtx.curveClipSplitter; + this.stack = (rdrCtx.stats != null) ? new IndexStack(rdrCtx, rdrCtx.stats.stat_pcf_idxstack_indices, @@ -515,6 +532,11 @@ _clipRect[2] -= margin - rdrOffX; _clipRect[3] += margin + rdrOffX; + if (MarlinConst.DO_CLIP_SUBDIVIDER) { + // adjust padded clip rectangle: + curveSplitter.init(); + } + this.init_corners = true; this.gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R; @@ -565,7 +587,9 @@ } stack.pullAll(corners, out); } - out.lineTo(cx0, cy0); + out.lineTo(cox0, coy0); + this.cx0 = cox0; + this.cy0 = coy0; } @Override @@ -590,38 +614,68 @@ public void moveTo(final double x0, final double y0) { finishPath(); - final int outcode = DHelpers.outcode(x0, y0, clipRect); - this.cOutCode = outcode; + this.cOutCode = DHelpers.outcode(x0, y0, clipRect); this.outside = false; out.moveTo(x0, y0); + this.cx0 = x0; + this.cy0 = y0; } @Override public void lineTo(final double xe, final double ye) { final int outcode0 = this.cOutCode; final int outcode1 = DHelpers.outcode(xe, ye, clipRect); - this.cOutCode = outcode1; - final int sideCode = (outcode0 & outcode1); + // Should clip + final int orCode = (outcode0 | outcode1); + if (orCode != 0) { + final int sideCode = (outcode0 & outcode1); - // basic rejection criteria: - if (sideCode == 0) { - this.gOutCode = 0; - } else { - this.gOutCode &= sideCode; - // keep last point coordinate before entering the clip again: - this.outside = true; - this.cx0 = xe; - this.cy0 = ye; + // basic rejection criteria: + if (sideCode == 0) { + // ovelap clip: + if (subdivide) { + // avoid reentrance + subdivide = false; + boolean ret; + // subdivide curve => callback with subdivided parts: + if (outside) { + ret = curveSplitter.splitLine(cox0, coy0, xe, ye, + orCode, this); + } else { + ret = curveSplitter.splitLine(cx0, cy0, xe, ye, + orCode, this); + } + // reentrance is done: + subdivide = true; + if (ret) { + return; + } + } + // already subdivided so render it + } else { + this.cOutCode = outcode1; + this.gOutCode &= sideCode; + // keep last point coordinate before entering the clip again: + this.outside = true; + this.cox0 = xe; + this.coy0 = ye; - clip(sideCode, outcode0, outcode1); - return; + clip(sideCode, outcode0, outcode1); + return; + } } + + this.cOutCode = outcode1; + this.gOutCode = 0; + if (outside) { finish(); } // clipping disabled: out.lineTo(xe, ye); + this.cx0 = xe; + this.cy0 = ye; } private void clip(final int sideCode, @@ -641,22 +695,18 @@ // add corners to outside stack: switch (tbCode) { case MarlinConst.OUTCODE_TOP: -// System.out.println("TOP "+ ((off == 0) ? "LEFT" : "RIGHT")); stack.push(off); // top return; case MarlinConst.OUTCODE_BOTTOM: -// System.out.println("BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT")); stack.push(off + 1); // bottom return; default: // both TOP / BOTTOM: if ((outcode0 & MarlinConst.OUTCODE_TOP) != 0) { -// System.out.println("TOP + BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT")); // top to bottom stack.push(off); // top stack.push(off + 1); // bottom } else { -// System.out.println("BOTTOM + TOP "+ ((off == 0) ? "LEFT" : "RIGHT")); // bottom to top stack.push(off + 1); // bottom stack.push(off); // top @@ -671,34 +721,62 @@ final double xe, final double ye) { final int outcode0 = this.cOutCode; + final int outcode1 = DHelpers.outcode(x1, y1, clipRect); + final int outcode2 = DHelpers.outcode(x2, y2, clipRect); final int outcode3 = DHelpers.outcode(xe, ye, clipRect); - this.cOutCode = outcode3; - - int sideCode = outcode0 & outcode3; - if (sideCode == 0) { - this.gOutCode = 0; - } else { - sideCode &= DHelpers.outcode(x1, y1, clipRect); - sideCode &= DHelpers.outcode(x2, y2, clipRect); - this.gOutCode &= sideCode; + // Should clip + final int orCode = (outcode0 | outcode1 | outcode2 | outcode3); + if (orCode != 0) { + final int sideCode = outcode0 & outcode1 & outcode2 & outcode3; // basic rejection criteria: - if (sideCode != 0) { + if (sideCode == 0) { + // ovelap clip: + if (subdivide) { + // avoid reentrance + subdivide = false; + // subdivide curve => callback with subdivided parts: + boolean ret; + if (outside) { + ret = curveSplitter.splitCurve(cox0, coy0, x1, y1, + x2, y2, xe, ye, + orCode, this); + } else { + ret = curveSplitter.splitCurve(cx0, cy0, x1, y1, + x2, y2, xe, ye, + orCode, this); + } + // reentrance is done: + subdivide = true; + if (ret) { + return; + } + } + // already subdivided so render it + } else { + this.cOutCode = outcode3; + this.gOutCode &= sideCode; // keep last point coordinate before entering the clip again: this.outside = true; - this.cx0 = xe; - this.cy0 = ye; + this.cox0 = xe; + this.coy0 = ye; clip(sideCode, outcode0, outcode3); return; } } + + this.cOutCode = outcode3; + this.gOutCode = 0; + if (outside) { finish(); } // clipping disabled: out.curveTo(x1, y1, x2, y2, xe, ye); + this.cx0 = xe; + this.cy0 = ye; } @Override @@ -706,33 +784,314 @@ final double xe, final double ye) { final int outcode0 = this.cOutCode; + final int outcode1 = DHelpers.outcode(x1, y1, clipRect); final int outcode2 = DHelpers.outcode(xe, ye, clipRect); - this.cOutCode = outcode2; - - int sideCode = outcode0 & outcode2; - if (sideCode == 0) { - this.gOutCode = 0; - } else { - sideCode &= DHelpers.outcode(x1, y1, clipRect); - this.gOutCode &= sideCode; + // Should clip + final int orCode = (outcode0 | outcode1 | outcode2); + if (orCode != 0) { + final int sideCode = outcode0 & outcode1 & outcode2; // basic rejection criteria: - if (sideCode != 0) { + if (sideCode == 0) { + // ovelap clip: + if (subdivide) { + // avoid reentrance + subdivide = false; + // subdivide curve => callback with subdivided parts: + boolean ret; + if (outside) { + ret = curveSplitter.splitQuad(cox0, coy0, x1, y1, + xe, ye, orCode, this); + } else { + ret = curveSplitter.splitQuad(cx0, cy0, x1, y1, + xe, ye, orCode, this); + } + // reentrance is done: + subdivide = true; + if (ret) { + return; + } + } + // already subdivided so render it + } else { + this.cOutCode = outcode2; + this.gOutCode &= sideCode; // keep last point coordinate before entering the clip again: this.outside = true; - this.cx0 = xe; - this.cy0 = ye; + this.cox0 = xe; + this.coy0 = ye; clip(sideCode, outcode0, outcode2); return; } } + + this.cOutCode = outcode2; + this.gOutCode = 0; + if (outside) { finish(); } // clipping disabled: out.quadTo(x1, y1, xe, ye); + this.cx0 = xe; + this.cy0 = ye; + } + } + + static final class CurveClipSplitter { + + static final double LEN_TH = MarlinProperties.getSubdividerMinLength(); + static final boolean DO_CHECK_LENGTH = (LEN_TH > 0.0d); + + private static final boolean TRACE = false; + + private static final int MAX_N_CURVES = 3 * 4; + + // clip rectangle (ymin, ymax, xmin, xmax): + final double[] clipRect; + + // clip rectangle (ymin, ymax, xmin, xmax) including padding: + final double[] clipRectPad = new double[4]; + private boolean init_clipRectPad = false; + + // This is where the curve to be processed is put. We give it + // enough room to store all curves. + final double[] middle = new double[MAX_N_CURVES * 8 + 2]; + // t values at subdivision points + private final double[] subdivTs = new double[MAX_N_CURVES]; + + // dirty curve + private final DCurve curve; + + CurveClipSplitter(final DRendererContext rdrCtx) { + this.clipRect = rdrCtx.clipRect; + this.curve = rdrCtx.curve; + } + + void init() { + this.init_clipRectPad = true; + } + + private void initPaddedClip() { + // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY + // adjust padded clip rectangle (ymin, ymax, xmin, xmax): + // add a rounding error (curve subdivision ~ 0.1px): + final double[] _clipRect = clipRect; + final double[] _clipRectPad = clipRectPad; + + _clipRectPad[0] = _clipRect[0] - CLIP_RECT_PADDING; + _clipRectPad[1] = _clipRect[1] + CLIP_RECT_PADDING; + _clipRectPad[2] = _clipRect[2] - CLIP_RECT_PADDING; + _clipRectPad[3] = _clipRect[3] + CLIP_RECT_PADDING; + + if (TRACE) { + MarlinUtils.logInfo("clip: X [" + _clipRectPad[2] + " .. " + _clipRectPad[3] +"] " + + "Y ["+ _clipRectPad[0] + " .. " + _clipRectPad[1] +"]"); + } + } + + boolean splitLine(final double x0, final double y0, + final double x1, final double y1, + final int outCodeOR, + final DPathConsumer2D out) + { + if (TRACE) { + MarlinUtils.logInfo("divLine P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ")"); + } + + if (DO_CHECK_LENGTH && DHelpers.fastLineLen(x0, y0, x1, y1) <= LEN_TH) { + return false; + } + + final double[] mid = middle; + mid[0] = x0; mid[1] = y0; + mid[2] = x1; mid[3] = y1; + + return subdivideAtIntersections(4, outCodeOR, out); + } + + boolean splitQuad(final double x0, final double y0, + final double x1, final double y1, + final double x2, final double y2, + final int outCodeOR, + final DPathConsumer2D out) + { + if (TRACE) { + MarlinUtils.logInfo("divQuad P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ")"); + } + + if (DO_CHECK_LENGTH && DHelpers.fastQuadLen(x0, y0, x1, y1, x2, y2) <= LEN_TH) { + return false; + } + + final double[] mid = middle; + mid[0] = x0; mid[1] = y0; + mid[2] = x1; mid[3] = y1; + mid[4] = x2; mid[5] = y2; + + return subdivideAtIntersections(6, outCodeOR, out); + } + + boolean splitCurve(final double x0, final double y0, + final double x1, final double y1, + final double x2, final double y2, + final double x3, final double y3, + final int outCodeOR, + final DPathConsumer2D out) + { + if (TRACE) { + MarlinUtils.logInfo("divCurve P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ")"); + } + + if (DO_CHECK_LENGTH && DHelpers.fastCurvelen(x0, y0, x1, y1, x2, y2, x3, y3) <= LEN_TH) { + return false; + } + + final double[] mid = middle; + mid[0] = x0; mid[1] = y0; + mid[2] = x1; mid[3] = y1; + mid[4] = x2; mid[5] = y2; + mid[6] = x3; mid[7] = y3; + + return subdivideAtIntersections(8, outCodeOR, out); + } + + private boolean subdivideAtIntersections(final int type, final int outCodeOR, + final DPathConsumer2D out) + { + final double[] mid = middle; + final double[] subTs = subdivTs; + + if (init_clipRectPad) { + init_clipRectPad = false; + initPaddedClip(); + } + + final int nSplits = DHelpers.findClipPoints(curve, mid, subTs, type, + outCodeOR, clipRectPad); + + if (TRACE) { + MarlinUtils.logInfo("nSplits: "+ nSplits); + MarlinUtils.logInfo("subTs: " + Arrays.toString(Arrays.copyOfRange(subTs, 0, nSplits))); + } + if (nSplits == 0) { + // only curve support shortcut + return false; + } + double prevT = 0.0d; + + for (int i = 0, off = 0; i < nSplits; i++, off += type) { + final double t = subTs[i]; + + DHelpers.subdivideAt((t - prevT) / (1.0d - prevT), + mid, off, mid, off, type); + prevT = t; + } + + for (int i = 0, off = 0; i <= nSplits; i++, off += type) { + if (TRACE) { + MarlinUtils.logInfo("Part Curve " + Arrays.toString(Arrays.copyOfRange(mid, off, off + type))); + } + emitCurrent(type, mid, off, out); + } + return true; + } + + static void emitCurrent(final int type, final double[] pts, + final int off, final DPathConsumer2D out) + { + // if instead of switch (perf + most probable cases first) + if (type == 8) { + out.curveTo(pts[off + 2], pts[off + 3], + pts[off + 4], pts[off + 5], + pts[off + 6], pts[off + 7]); + } else if (type == 4) { + out.lineTo(pts[off + 2], pts[off + 3]); + } else { + out.quadTo(pts[off + 2], pts[off + 3], + pts[off + 4], pts[off + 5]); + } + } + } + + public static final class CurveBasicMonotonizer { + + private static final int MAX_N_CURVES = 11; + + // squared half line width (for stroker) + private double lw2; + + // number of splitted curves + int nbSplits; + + // This is where the curve to be processed is put. We give it + // enough room to store all curves. + final double[] middle = new double[MAX_N_CURVES * 6 + 2]; + // t values at subdivision points + private final double[] subdivTs = new double[MAX_N_CURVES - 1]; + + // dirty curve + private final DCurve curve; + + CurveBasicMonotonizer(final DRendererContext rdrCtx) { + this.curve = rdrCtx.curve; + } + + public void init(final double lineWidth) { + this.lw2 = (lineWidth * lineWidth) / 4.0d; + } + + CurveBasicMonotonizer curve(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 double[] mid = middle; + mid[0] = x0; mid[1] = y0; + mid[2] = x1; mid[3] = y1; + mid[4] = x2; mid[5] = y2; + mid[6] = x3; mid[7] = y3; + + final double[] subTs = subdivTs; + final int nSplits = DHelpers.findSubdivPoints(curve, mid, subTs, 8, lw2); + + double prevT = 0.0d; + for (int i = 0, off = 0; i < nSplits; i++, off += 6) { + final double t = subTs[i]; + + DHelpers.subdivideCubicAt((t - prevT) / (1.0d - prevT), + mid, off, mid, off, off + 6); + prevT = t; + } + + this.nbSplits = nSplits; + return this; + } + + CurveBasicMonotonizer quad(final double x0, final double y0, + final double x1, final double y1, + final double x2, final double y2) + { + final double[] mid = middle; + mid[0] = x0; mid[1] = y0; + mid[2] = x1; mid[3] = y1; + mid[4] = x2; mid[5] = y2; + + final double[] subTs = subdivTs; + final int nSplits = DHelpers.findSubdivPoints(curve, mid, subTs, 6, lw2); + + double prevt = 0.0d; + for (int i = 0, off = 0; i < nSplits; i++, off += 4) { + final double t = subTs[i]; + DHelpers.subdivideQuadAt((t - prevt) / (1.0d - prevt), + mid, off, mid, off, off + 4); + prevt = t; + } + + this.nbSplits = nSplits; + return this; } } @@ -789,7 +1148,7 @@ } private void log(final String message) { - System.out.println(prefix + message); + MarlinUtils.logInfo(prefix + message); } } } --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/Dasher.java 2018-06-08 16:12:48.999686547 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/Dasher.java 2018-06-08 16:12:48.835686549 +0200 @@ -27,6 +27,8 @@ import java.util.Arrays; import com.sun.javafx.geom.PathConsumer2D; +import com.sun.marlin.TransformingPathConsumer2D.CurveBasicMonotonizer; +import com.sun.marlin.TransformingPathConsumer2D.CurveClipSplitter; /** * The Dasher class takes a series of linear commands @@ -41,8 +43,9 @@ */ public final class Dasher implements PathConsumer2D, MarlinConst { - static final int REC_LIMIT = 4; - static final float ERR = 0.01f; + /* huge circle with radius ~ 2E9 only needs 12 subdivision levels */ + static final int REC_LIMIT = 16; + static final float CURVE_LEN_ERR = MarlinProperties.getCurveLengthError(); // 0.01 static final float MIN_T_INC = 1.0f / (1 << REC_LIMIT); // More than 24 bits of mantissa means we can no longer accurately @@ -64,8 +67,10 @@ private boolean dashOn; private float phase; - private float sx, sy; - private float x0, y0; + // The starting point of the path + private float sx0, sy0; + // the current point + private float cx0, cy0; // temporary storage for the current curve private final float[] curCurvepts; @@ -76,11 +81,34 @@ // flag to recycle dash array copy boolean recycleDashes; + // 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 float[] firstSegmentsBuffer; // dynamic array + private int firstSegidx; + // dashes ref (dirty) final FloatArrayCache.Reference dashes_ref; // firstSegmentsBuffer ref (dirty) final FloatArrayCache.Reference firstSegmentsBuffer_ref; + // Bounds of the drawing region, at pixel precision. + private float[] clipRect; + + // the outcode of the current point + private int cOutCode = 0; + + private boolean subdivide = DO_CLIP_SUBDIVIDER; + + private final LengthIterator li = new LengthIterator(); + + private final CurveClipSplitter curveSplitter; + + private float cycleLen; + private boolean outside; + private float totalSkipLen; + /** * Constructs a Dasher. * @param rdrCtx per-thread renderer context @@ -96,6 +124,8 @@ // we need curCurvepts to be able to contain 2 curves because when // dashing curves, we need to subdivide it curCurvepts = new float[8 * 2]; + + this.curveSplitter = rdrCtx.curveClipSplitter; } /** @@ -122,6 +152,8 @@ for (int i = 0; i < dashLen; i++) { sum += dash[i]; } + this.cycleLen = sum; + float cycles = phase / sum; if (phase < 0.0f) { if (-cycles >= MAX_CYCLES) { @@ -170,6 +202,12 @@ this.recycleDashes = recycleDashes; + if (rdrCtx.doClip) { + this.clipRect = rdrCtx.clipRect; + } else { + this.clipRect = null; + this.cOutCode = 0; + } return this; // fluent API } @@ -207,33 +245,42 @@ @Override public void moveTo(final float x0, final float y0) { if (firstSegidx != 0) { - out.moveTo(sx, sy); + out.moveTo(sx0, sy0); emitFirstSegments(); } - needsMoveTo = true; + this.needsMoveTo = true; this.idx = startIdx; this.dashOn = this.startDashOn; this.phase = this.startPhase; - this.sx = x0; - this.sy = y0; - this.x0 = x0; - this.y0 = y0; + this.cx0 = x0; + this.cy0 = y0; + + // update starting point: + this.sx0 = x0; + this.sy0 = y0; this.starting = true; + + if (clipRect != null) { + final int outcode = Helpers.outcode(x0, y0, clipRect); + this.cOutCode = outcode; + this.outside = false; + this.totalSkipLen = 0.0f; + } } private void emitSeg(float[] buf, int off, int type) { switch (type) { + case 4: + out.lineTo(buf[off], buf[off + 1]); + return; case 8: - out.curveTo(buf[off+0], buf[off+1], - buf[off+2], buf[off+3], - buf[off+4], buf[off+5]); + out.curveTo(buf[off ], 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]); + out.quadTo(buf[off ], buf[off + 1], + buf[off + 2], buf[off + 3]); return; default: } @@ -249,12 +296,6 @@ } 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 float[] firstSegmentsBuffer; // dynamic array - private int firstSegidx; // precondition: pts must be in relative coordinates (relative to x0,y0) private void goTo(final float[] pts, final int off, final int type, @@ -270,7 +311,7 @@ } else { if (needsMoveTo) { needsMoveTo = false; - out.moveTo(x0, y0); + out.moveTo(cx0, cy0); } emitSeg(pts, off, type); } @@ -281,8 +322,8 @@ } needsMoveTo = true; } - this.x0 = x; - this.y0 = y; + this.cx0 = x; + this.cy0 = y; } private void goTo_starting(final float[] pts, final int off, final int type) { @@ -308,10 +349,56 @@ @Override public void lineTo(final float x1, final float y1) { - final float dx = x1 - x0; - final float dy = y1 - y0; + final int outcode0 = this.cOutCode; + + if (clipRect != null) { + final int outcode1 = Helpers.outcode(x1, y1, clipRect); + + // Should clip + final int orCode = (outcode0 | outcode1); + + if (orCode != 0) { + final int sideCode = outcode0 & outcode1; + + // basic rejection criteria: + if (sideCode == 0) { + // ovelap clip: + if (subdivide) { + // avoid reentrance + subdivide = false; + // subdivide curve => callback with subdivided parts: + boolean ret = curveSplitter.splitLine(cx0, cy0, x1, y1, + orCode, this); + // reentrance is done: + subdivide = true; + if (ret) { + return; + } + } + // already subdivided so render it + } else { + this.cOutCode = outcode1; + skipLineTo(x1, y1); + return; + } + } + + this.cOutCode = outcode1; + + if (this.outside) { + this.outside = false; + // Adjust current index, phase & dash: + skipLen(); + } + } + _lineTo(x1, y1); + } + + private void _lineTo(final float x1, final float y1) { + final float dx = x1 - cx0; + final float dy = y1 - cy0; - float len = dx*dx + dy*dy; + float len = dx * dx + dy * dy; if (len == 0.0f) { return; } @@ -330,8 +417,7 @@ boolean _dashOn = dashOn; float _phase = phase; - float leftInThisDashSegment; - float d, dashdx, dashdy, p; + float leftInThisDashSegment, d; while (true) { d = _dash[_idx]; @@ -352,24 +438,15 @@ _idx = (_idx + 1) % _dashLen; _dashOn = !_dashOn; } - - // Save local state: - idx = _idx; - dashOn = _dashOn; - phase = _phase; - return; + break; } - dashdx = d * cx; - dashdy = d * cy; - if (_phase == 0.0f) { - _curCurvepts[0] = x0 + dashdx; - _curCurvepts[1] = y0 + dashdy; + _curCurvepts[0] = cx0 + d * cx; + _curCurvepts[1] = cy0 + d * cy; } else { - p = leftInThisDashSegment / d; - _curCurvepts[0] = x0 + p * dashdx; - _curCurvepts[1] = y0 + p * dashdy; + _curCurvepts[0] = cx0 + leftInThisDashSegment * cx; + _curCurvepts[1] = cy0 + leftInThisDashSegment * cy; } goTo(_curCurvepts, 0, 4, _dashOn); @@ -380,19 +457,95 @@ _dashOn = !_dashOn; _phase = 0.0f; } + // Save local state: + idx = _idx; + dashOn = _dashOn; + phase = _phase; } - // shared instance in Dasher - private final LengthIterator li = new LengthIterator(); + private void skipLineTo(final float x1, final float y1) { + final float dx = x1 - cx0; + final float dy = y1 - cy0; + + float len = dx * dx + dy * dy; + if (len != 0.0f) { + len = (float)Math.sqrt(len); + } + + // Accumulate skipped length: + this.outside = true; + this.totalSkipLen += len; + + // Fix initial move: + this.needsMoveTo = true; + this.starting = false; + + this.cx0 = x1; + this.cy0 = y1; + } + + public void skipLen() { + float len = this.totalSkipLen; + this.totalSkipLen = 0.0f; + + final float[] _dash = dash; + final int _dashLen = this.dashLen; + + int _idx = idx; + boolean _dashOn = dashOn; + float _phase = phase; + + // -2 to ensure having 2 iterations of the post-loop + // to compensate the remaining phase + final long fullcycles = (long)Math.floor(len / cycleLen) - 2L; + + if (fullcycles > 0L) { + len -= cycleLen * fullcycles; + + final long iterations = fullcycles * _dashLen; + _idx = (int) (iterations + _idx) % _dashLen; + _dashOn = (iterations + (_dashOn ? 1L : 0L) & 1L) == 1L; + } + + float leftInThisDashSegment, d; + + while (true) { + d = _dash[_idx]; + leftInThisDashSegment = d - _phase; + + if (len <= leftInThisDashSegment) { + // Advance phase within current dash segment + _phase += len; + + // TODO: compare float values using epsilon: + if (len == leftInThisDashSegment) { + _phase = 0.0f; + _idx = (_idx + 1) % _dashLen; + _dashOn = !_dashOn; + } + break; + } + + len -= leftInThisDashSegment; + // Advance to next dash segment + _idx = (_idx + 1) % _dashLen; + _dashOn = !_dashOn; + _phase = 0.0f; + } + // Save local state: + idx = _idx; + dashOn = _dashOn; + phase = _phase; + } // preconditions: curCurvepts 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(final int type) { - if (pointCurve(curCurvepts, type)) { + final float[] _curCurvepts = curCurvepts; + if (pointCurve(_curCurvepts, type)) { return; } final LengthIterator _li = li; - final float[] _curCurvepts = curCurvepts; final float[] _dash = dash; final int _dashLen = this.dashLen; @@ -404,17 +557,16 @@ // initially the current curve is at curCurvepts[0...type] int curCurveoff = 0; - float lastSplitT = 0.0f; + float prevT = 0.0f; float t; float leftInThisDashSegment = _dash[_idx] - _phase; while ((t = _li.next(leftInThisDashSegment)) < 1.0f) { if (t != 0.0f) { - Helpers.subdivideAt((t - lastSplitT) / (1.0f - lastSplitT), + Helpers.subdivideAt((t - prevT) / (1.0f - prevT), _curCurvepts, curCurveoff, - _curCurvepts, 0, - _curCurvepts, type, type); - lastSplitT = t; + _curCurvepts, 0, type); + prevT = t; goTo(_curCurvepts, 2, type, _dashOn); curCurveoff = type; } @@ -442,7 +594,29 @@ _li.reset(); } - private static boolean pointCurve(float[] curve, int type) { + private void skipSomethingTo(final int type) { + final float[] _curCurvepts = curCurvepts; + if (pointCurve(_curCurvepts, type)) { + return; + } + final LengthIterator _li = li; + + _li.initializeIterationOnCurve(_curCurvepts, type); + + // In contrary to somethingTo(), + // just estimate properly the curve length: + final float len = _li.totalLength(); + + // Accumulate skipped length: + this.outside = true; + this.totalSkipLen += len; + + // Fix initial move: + this.needsMoveTo = true; + this.starting = false; + } + + private static boolean pointCurve(final float[] curve, final int type) { for (int i = 2; i < type; i++) { if (curve[i] != curve[i-2]) { return false; @@ -465,15 +639,14 @@ // tree; however, the trees we are interested in have the property that // every non leaf node has exactly 2 children static final class LengthIterator { - private enum Side {LEFT, RIGHT} // Holds the curves at various levels of the recursion. The root // (i.e. the original curve) is at recCurveStack[0] (but then it // gets subdivided, the left half is put at 1, so most of the time // only the right half of the original curve is at 0) private final float[][] recCurveStack; // dirty - // sides[i] indicates whether the node at level i+1 in the path from + // sidesRight[i] indicates whether the node at level i+1 in the path from // the root to the current leaf is a left or right child of its parent. - private final Side[] sides; // dirty + private final boolean[] sidesRight; // dirty private int curveType; // lastT and nextT delimit the current leaf. private float nextT; @@ -494,7 +667,7 @@ LengthIterator() { this.recCurveStack = new float[REC_LIMIT + 1][8]; - this.sides = new Side[REC_LIMIT]; + this.sidesRight = new boolean[REC_LIMIT]; // if any methods are called without first initializing this object // on a curve, we want it to fail ASAP. this.nextT = Float.MAX_VALUE; @@ -516,7 +689,7 @@ for (int i = recLimit; i >= 0; i--) { Arrays.fill(recCurveStack[i], 0.0f); } - Arrays.fill(sides, Side.LEFT); + Arrays.fill(sidesRight, false); Arrays.fill(curLeafCtrlPolyLengths, 0.0f); Arrays.fill(nextRoots, 0.0f); Arrays.fill(flatLeafCoefCache, 0.0f); @@ -524,7 +697,7 @@ } } - void initializeIterationOnCurve(float[] pts, int type) { + void initializeIterationOnCurve(final float[] pts, final int type) { // optimize arraycopy (8 values faster than 6 = type): System.arraycopy(pts, 0, recCurveStack[0], 0, 8); this.curveType = type; @@ -536,11 +709,11 @@ goLeft(); // initializes nextT and lenAtNextT properly this.lenAtLastSplit = 0.0f; if (recLevel > 0) { - this.sides[0] = Side.LEFT; + this.sidesRight[0] = false; this.done = false; } else { // the root of the tree is a leaf so we're done. - this.sides[0] = Side.RIGHT; + this.sidesRight[0] = true; this.done = true; } this.lastSegLen = 0.0f; @@ -549,7 +722,7 @@ // 0 == false, 1 == true, -1 == invalid cached value. private int cachedHaveLowAcceleration = -1; - private boolean haveLowAcceleration(float err) { + private boolean haveLowAcceleration(final float err) { if (cachedHaveLowAcceleration == -1) { final float len1 = curLeafCtrlPolyLengths[0]; final float len2 = curLeafCtrlPolyLengths[1]; @@ -638,7 +811,7 @@ // 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 = Helpers.cubicRootsInAB(a, b, c, d, nextRoots, 0, 0.0f, 1.0f); + final int n = Helpers.cubicRootsInAB(a, b, c, d, nextRoots, 0, 0.0f, 1.0f); if (n == 1 && !Float.isNaN(nextRoots[0])) { t = nextRoots[0]; } @@ -659,6 +832,16 @@ return t; } + float totalLength() { + while (!done) { + goToNextLeaf(); + } + // reset LengthIterator: + reset(); + + return lenAtNextT; + } + float lastSegLen() { return lastSegLen; } @@ -668,11 +851,11 @@ private void goToNextLeaf() { // We must go to the first ancestor node that has an unvisited // right child. + final boolean[] _sides = sidesRight; int _recLevel = recLevel; - final Side[] _sides = sides; - _recLevel--; - while(_sides[_recLevel] == Side.RIGHT) { + + while(_sides[_recLevel]) { if (_recLevel == 0) { recLevel = 0; done = true; @@ -681,19 +864,17 @@ _recLevel--; } - _sides[_recLevel] = Side.RIGHT; + _sides[_recLevel] = true; // optimize arraycopy (8 values faster than 6 = type): - System.arraycopy(recCurveStack[_recLevel], 0, - recCurveStack[_recLevel+1], 0, 8); - _recLevel++; - + System.arraycopy(recCurveStack[_recLevel++], 0, + recCurveStack[_recLevel], 0, 8); recLevel = _recLevel; goLeft(); } // go to the leftmost node from the current node. Return its length. private void goLeft() { - float len = onLeaf(); + final float len = onLeaf(); if (len >= 0.0f) { lastT = nextT; lenAtLastT = lenAtNextT; @@ -703,10 +884,11 @@ flatLeafCoefCache[2] = -1.0f; cachedHaveLowAcceleration = -1; } else { - Helpers.subdivide(recCurveStack[recLevel], 0, - recCurveStack[recLevel+1], 0, - recCurveStack[recLevel], 0, curveType); - sides[recLevel] = Side.LEFT; + Helpers.subdivide(recCurveStack[recLevel], + recCurveStack[recLevel + 1], + recCurveStack[recLevel], curveType); + + sidesRight[recLevel] = false; recLevel++; goLeft(); } @@ -721,7 +903,7 @@ float x0 = curve[0], y0 = curve[1]; for (int i = 2; i < _curveType; i += 2) { - final float x1 = curve[i], y1 = curve[i+1]; + final float x1 = curve[i], y1 = curve[i + 1]; final float len = Helpers.linelen(x0, y0, x1, y1); polyLen += len; curLeafCtrlPolyLengths[(i >> 1) - 1] = len; @@ -729,10 +911,9 @@ y0 = y1; } - final float lineLen = Helpers.linelen(curve[0], curve[1], - curve[_curveType-2], - curve[_curveType-1]); - if ((polyLen - lineLen) < ERR || recLevel == REC_LIMIT) { + final float lineLen = Helpers.linelen(curve[0], curve[1], x0, y0); + + if ((polyLen - lineLen) < CURVE_LEN_ERR || recLevel == REC_LIMIT) { return (polyLen + lineLen) / 2.0f; } return -1.0f; @@ -744,41 +925,190 @@ final float x2, final float y2, final float x3, final float y3) { + final int outcode0 = this.cOutCode; + + if (clipRect != null) { + final int outcode1 = Helpers.outcode(x1, y1, clipRect); + final int outcode2 = Helpers.outcode(x2, y2, clipRect); + final int outcode3 = Helpers.outcode(x3, y3, clipRect); + + // Should clip + final int orCode = (outcode0 | outcode1 | outcode2 | outcode3); + if (orCode != 0) { + final int sideCode = outcode0 & outcode1 & outcode2 & outcode3; + + // basic rejection criteria: + if (sideCode == 0) { + // ovelap clip: + if (subdivide) { + // avoid reentrance + subdivide = false; + // subdivide curve => callback with subdivided parts: + boolean ret = curveSplitter.splitCurve(cx0, cy0, x1, y1, x2, y2, x3, y3, + orCode, this); + // reentrance is done: + subdivide = true; + if (ret) { + return; + } + } + // already subdivided so render it + } else { + this.cOutCode = outcode3; + skipCurveTo(x1, y1, x2, y2, x3, y3); + return; + } + } + + this.cOutCode = outcode3; + + if (this.outside) { + this.outside = false; + // Adjust current index, phase & dash: + skipLen(); + } + } + _curveTo(x1, y1, x2, y2, x3, y3); + } + + private void _curveTo(final float x1, final float y1, + final float x2, final float y2, + final float x3, final float y3) + { final float[] _curCurvepts = curCurvepts; - _curCurvepts[0] = x0; _curCurvepts[1] = y0; - _curCurvepts[2] = x1; _curCurvepts[3] = y1; - _curCurvepts[4] = x2; _curCurvepts[5] = y2; - _curCurvepts[6] = x3; _curCurvepts[7] = y3; - somethingTo(8); + + // monotonize curve: + final CurveBasicMonotonizer monotonizer + = rdrCtx.monotonizer.curve(cx0, cy0, x1, y1, x2, y2, x3, y3); + + final int nSplits = monotonizer.nbSplits; + final float[] mid = monotonizer.middle; + + for (int i = 0, off = 0; i <= nSplits; i++, off += 6) { + // optimize arraycopy (8 values faster than 6 = type): + System.arraycopy(mid, off, _curCurvepts, 0, 8); + + somethingTo(8); + } + } + + private void skipCurveTo(final float x1, final float y1, + final float x2, final float y2, + final float x3, final float y3) + { + final float[] _curCurvepts = curCurvepts; + _curCurvepts[0] = cx0; _curCurvepts[1] = cy0; + _curCurvepts[2] = x1; _curCurvepts[3] = y1; + _curCurvepts[4] = x2; _curCurvepts[5] = y2; + _curCurvepts[6] = x3; _curCurvepts[7] = y3; + + skipSomethingTo(8); + + this.cx0 = x3; + this.cy0 = y3; } @Override public void quadTo(final float x1, final float y1, final float x2, final float y2) { + final int outcode0 = this.cOutCode; + + if (clipRect != null) { + final int outcode1 = Helpers.outcode(x1, y1, clipRect); + final int outcode2 = Helpers.outcode(x2, y2, clipRect); + + // Should clip + final int orCode = (outcode0 | outcode1 | outcode2); + if (orCode != 0) { + final int sideCode = outcode0 & outcode1 & outcode2; + + // basic rejection criteria: + if (sideCode == 0) { + // ovelap clip: + if (subdivide) { + // avoid reentrance + subdivide = false; + // subdivide curve => call lineTo() with subdivided curves: + boolean ret = curveSplitter.splitQuad(cx0, cy0, x1, y1, + x2, y2, orCode, this); + // reentrance is done: + subdivide = true; + if (ret) { + return; + } + } + // already subdivided so render it + } else { + this.cOutCode = outcode2; + skipQuadTo(x1, y1, x2, y2); + return; + } + } + + this.cOutCode = outcode2; + + if (this.outside) { + this.outside = false; + // Adjust current index, phase & dash: + skipLen(); + } + } + _quadTo(x1, y1, x2, y2); + } + + private void _quadTo(final float x1, final float y1, + final float x2, final float y2) + { final float[] _curCurvepts = curCurvepts; - _curCurvepts[0] = x0; _curCurvepts[1] = y0; - _curCurvepts[2] = x1; _curCurvepts[3] = y1; - _curCurvepts[4] = x2; _curCurvepts[5] = y2; - somethingTo(6); + + // monotonize quad: + final CurveBasicMonotonizer monotonizer + = rdrCtx.monotonizer.quad(cx0, cy0, x1, y1, x2, y2); + + final int nSplits = monotonizer.nbSplits; + final float[] mid = monotonizer.middle; + + for (int i = 0, off = 0; i <= nSplits; i++, off += 4) { + // optimize arraycopy (8 values faster than 6 = type): + System.arraycopy(mid, off, _curCurvepts, 0, 8); + + somethingTo(6); + } + } + + private void skipQuadTo(final float x1, final float y1, + final float x2, final float y2) + { + final float[] _curCurvepts = curCurvepts; + _curCurvepts[0] = cx0; _curCurvepts[1] = cy0; + _curCurvepts[2] = x1; _curCurvepts[3] = y1; + _curCurvepts[4] = x2; _curCurvepts[5] = y2; + + skipSomethingTo(6); + + this.cx0 = x2; + this.cy0 = y2; } @Override public void closePath() { - lineTo(sx, sy); + if (cx0 != sx0 || cy0 != sy0) { + lineTo(sx0, sy0); + } if (firstSegidx != 0) { if (!dashOn || needsMoveTo) { - out.moveTo(sx, sy); + out.moveTo(sx0, sy0); } emitFirstSegments(); } - moveTo(sx, sy); + moveTo(sx0, sy0); } @Override public void pathDone() { if (firstSegidx != 0) { - out.moveTo(sx, sy); + out.moveTo(sx0, sy0); emitFirstSegments(); } out.pathDone(); --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/DoubleArrayCache.java 2018-06-08 16:12:49.355686542 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/DoubleArrayCache.java 2018-06-08 16:12:49.187686544 +0200 @@ -99,7 +99,7 @@ Reference(final DoubleArrayCache cache, final int initialSize) { this.cache = cache; this.clean = cache.clean; - this.initial = createArray(initialSize, clean); + this.initial = createArray(initialSize); if (DO_STATS) { cache.stats.totalInitial += initialSize; } @@ -116,7 +116,7 @@ logInfo(getLogPrefix(clean) + "DoubleArrayCache: " + "getArray[oversize]: length=\t" + length); } - return createArray(length, clean); + return createArray(length); } double[] widenArray(final double[] array, final int usedSize, @@ -202,7 +202,7 @@ if (DO_STATS) { stats.createOp++; } - return createArray(arraySize, clean); + return createArray(arraySize); } void putArray(final double[] array) @@ -229,7 +229,7 @@ } } - static double[] createArray(final int length, final boolean clean) { + static double[] createArray(final int length) { return new double[length]; } --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/FloatArrayCache.java 2018-06-08 16:12:49.699686537 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/FloatArrayCache.java 2018-06-08 16:12:49.539686540 +0200 @@ -99,7 +99,7 @@ Reference(final FloatArrayCache cache, final int initialSize) { this.cache = cache; this.clean = cache.clean; - this.initial = createArray(initialSize, clean); + this.initial = createArray(initialSize); if (DO_STATS) { cache.stats.totalInitial += initialSize; } @@ -116,7 +116,7 @@ logInfo(getLogPrefix(clean) + "FloatArrayCache: " + "getArray[oversize]: length=\t" + length); } - return createArray(length, clean); + return createArray(length); } float[] widenArray(final float[] array, final int usedSize, @@ -202,7 +202,7 @@ if (DO_STATS) { stats.createOp++; } - return createArray(arraySize, clean); + return createArray(arraySize); } void putArray(final float[] array) @@ -229,7 +229,7 @@ } } - static float[] createArray(final int length, final boolean clean) { + static float[] createArray(final int length) { return new float[length]; } --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/Helpers.java 2018-06-08 16:12:50.027686533 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/Helpers.java 2018-06-08 16:12:49.867686535 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,7 +26,6 @@ package com.sun.marlin; import com.sun.javafx.geom.PathConsumer2D; -import static java.lang.Math.PI; import java.util.Arrays; import com.sun.marlin.stats.Histogram; import com.sun.marlin.stats.StatLong; @@ -47,13 +46,25 @@ return (d <= err && d >= -err); } - static int quadraticRoots(final float a, final float b, - final float c, float[] zeroes, final int off) + static float evalCubic(final float a, final float b, + final float c, final float d, + final float t) + { + return t * (t * (t * a + b) + c) + d; + } + + static float evalQuad(final float a, final float b, + final float c, final float t) + { + return t * (t * a + b) + c; + } + + static int quadraticRoots(final float a, final float b, final float c, + final float[] zeroes, final int off) { int ret = off; - float t; if (a != 0.0f) { - final float dis = b*b - 4*a*c; + final float dis = b*b - 4.0f * a * c; if (dis > 0.0f) { final float sqrtDis = (float) Math.sqrt(dis); // depending on the sign of b we use a slightly different @@ -68,37 +79,38 @@ zeroes[ret++] = (2.0f * c) / (-b + sqrtDis); } } else if (dis == 0.0f) { - t = (-b) / (2.0f * a); - zeroes[ret++] = t; - } - } else { - if (b != 0.0f) { - t = (-c) / b; - zeroes[ret++] = t; + zeroes[ret++] = -b / (2.0f * a); } + } else if (b != 0.0f) { + zeroes[ret++] = -c / b; } return ret - off; } // find the roots of g(t) = d*t^3 + a*t^2 + b*t + c in [A,B) - static int cubicRootsInAB(float d, float a, float b, float c, - float[] pts, final int off, + static int cubicRootsInAB(final float d0, float a0, float b0, float c0, + final float[] pts, final int off, final float A, final float B) { - if (d == 0.0f) { - int num = quadraticRoots(a, b, c, pts, off); + if (d0 == 0.0f) { + final int num = quadraticRoots(a0, b0, c0, pts, off); return filterOutNotInAB(pts, off, num, A, B) - off; } // From Graphics Gems: - // http://tog.acm.org/resources/GraphicsGems/gems/Roots3And4.c + // https://github.com/erich666/GraphicsGems/blob/master/gems/Roots3And4.c // (also from awt.geom.CubicCurve2D. 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; + + // 2018.1: Need double precision if d is very small (flat curve) ! + /* + * TODO: cleanup all that code after reading Roots3And4.c + */ + final double a = ((double)a0) / d0; + final double b = ((double)b0) / d0; + final double c = ((double)c0) / d0; // substitute x = y - A/3 to eliminate quadratic term: // x^3 +Px + Q = 0 @@ -108,63 +120,45 @@ // p = P/3 // q = Q/2 // instead and use those values for simplicity of the code. - double sq_A = a * a; - double p = (1.0d/3.0d) * ((-1.0d/3.0d) * sq_A + b); - double q = (1.0d/2.0d) * ((2.0d/27.0d) * a * sq_A - (1.0d/3.0d) * a * b + c); + final double sub = (1.0d / 3.0d) * a; + final double sq_A = a * a; + final double p = (1.0d / 3.0d) * ((-1.0d / 3.0d) * sq_A + b); + final double q = (1.0d / 2.0d) * ((2.0d / 27.0d) * a * sq_A - sub * b + c); // use Cardano's formula - double cb_p = p * p * p; - double D = q * q + cb_p; + final double cb_p = p * p * p; + final double D = q * q + cb_p; int num; if (D < 0.0d) { // see: http://en.wikipedia.org/wiki/Cubic_function#Trigonometric_.28and_hyperbolic.29_method - final double phi = (1.0d/3.0d) * Math.acos(-q / Math.sqrt(-cb_p)); + final double phi = (1.0d / 3.0d) * Math.acos(-q / Math.sqrt(-cb_p)); final double t = 2.0d * Math.sqrt(-p); - pts[ off+0 ] = (float) ( t * Math.cos(phi)); - pts[ off+1 ] = (float) (-t * Math.cos(phi + (PI / 3.0d))); - pts[ off+2 ] = (float) (-t * Math.cos(phi - (PI / 3.0d))); + pts[off ] = (float) ( t * Math.cos(phi) - sub); + pts[off + 1] = (float) (-t * Math.cos(phi + (Math.PI / 3.0d)) - sub); + pts[off + 2] = (float) (-t * Math.cos(phi - (Math.PI / 3.0d)) - sub); num = 3; } else { final double sqrt_D = Math.sqrt(D); final double u = Math.cbrt(sqrt_D - q); final double v = - Math.cbrt(sqrt_D + q); - pts[ off ] = (float) (u + v); + pts[off ] = (float) (u + v - sub); num = 1; if (within(D, 0.0d, 1e-8d)) { - pts[off+1] = -(pts[off] / 2.0f); + pts[off + 1] = (float)((-1.0d / 2.0d) * (u + v) - sub); num = 2; } } - final float sub = (1.0f/3.0f) * a; - - for (int i = 0; i < num; ++i) { - pts[ off+i ] -= sub; - } - return filterOutNotInAB(pts, off, num, A, B) - off; } - static float evalCubic(final float a, final float b, - final float c, final float d, - final float t) - { - return t * (t * (t * a + b) + c) + d; - } - - static float evalQuad(final float a, final float b, - final float c, final float t) - { - return t * (t * a + b) + c; - } - // returns the index 1 past the last valid element remaining after filtering - static int filterOutNotInAB(float[] nums, final int off, final int len, + static int filterOutNotInAB(final float[] nums, final int off, final int len, final float a, final float b) { int ret = off; @@ -176,35 +170,190 @@ return ret; } - static float linelen(float x1, float y1, float x2, float y2) { - final float dx = x2 - x1; - final float dy = y2 - y1; - return (float) Math.sqrt(dx*dx + dy*dy); + static float fastLineLen(final float x0, final float y0, + final float x1, final float y1) + { + final float dx = x1 - x0; + final float dy = y1 - y0; + + // use manhattan norm: + return Math.abs(dx) + Math.abs(dy); + } + + static float linelen(final float x0, final float y0, + final float x1, final float y1) + { + final float dx = x1 - x0; + final float dy = y1 - y0; + return (float) Math.sqrt(dx * dx + dy * dy); + } + + static float fastQuadLen(final float x0, final float y0, + final float x1, final float y1, + final float x2, final float y2) + { + final float dx1 = x1 - x0; + final float dx2 = x2 - x1; + final float dy1 = y1 - y0; + final float dy2 = y2 - y1; + + // use manhattan norm: + return Math.abs(dx1) + Math.abs(dx2) + + Math.abs(dy1) + Math.abs(dy2); + } + + static float quadlen(final float x0, final float y0, + final float x1, final float y1, + final float x2, final float y2) + { + return (linelen(x0, y0, x1, y1) + + linelen(x1, y1, x2, y2) + + linelen(x0, y0, x2, y2)) / 2.0f; + } + + + static float fastCurvelen(final float x0, final float y0, + final float x1, final float y1, + final float x2, final float y2, + final float x3, final float y3) + { + final float dx1 = x1 - x0; + final float dx2 = x2 - x1; + final float dx3 = x3 - x2; + final float dy1 = y1 - y0; + final float dy2 = y2 - y1; + final float dy3 = y3 - y2; + + // use manhattan norm: + return Math.abs(dx1) + Math.abs(dx2) + Math.abs(dx3) + + Math.abs(dy1) + Math.abs(dy2) + Math.abs(dy3); + } + + static float curvelen(final float x0, final float y0, + final float x1, final float y1, + final float x2, final float y2, + final float x3, final float y3) + { + return (linelen(x0, y0, x1, y1) + + linelen(x1, y1, x2, y2) + + linelen(x2, y2, x3, y3) + + linelen(x0, y0, x3, y3)) / 2.0f; + } + + // 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. + static int findSubdivPoints(final Curve c, final float[] pts, + final float[] ts, final int type, + final float w2) + { + final float x12 = pts[2] - pts[0]; + final float y12 = pts[3] - pts[1]; + // if the curve is already parallel to either axis we gain nothing + // from rotating it. + if ((y12 != 0.0f && x12 != 0.0f)) { + // 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 float hypot = (float)Math.sqrt(x12 * x12 + y12 * y12); + final float cos = x12 / hypot; + final float sin = y12 / hypot; + final float x1 = cos * pts[0] + sin * pts[1]; + final float y1 = cos * pts[1] - sin * pts[0]; + final float x2 = cos * pts[2] + sin * pts[3]; + final float y2 = cos * pts[3] - sin * pts[2]; + final float x3 = cos * pts[4] + sin * pts[5]; + final float y3 = cos * pts[5] - sin * pts[4]; + + switch(type) { + case 8: + final float x4 = cos * pts[6] + sin * pts[7]; + final float 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, w2, 0.0001f); + + ret = filterOutNotInAB(ts, 0, ret, 0.0001f, 0.9999f); + isort(ts, ret); + return ret; + } + + // finds values of t where the curve in pts should be subdivided in order + // to get intersections with the given clip rectangle. + // Stores the points in ts, and returns how many of them there were. + static int findClipPoints(final Curve curve, final float[] pts, + final float[] ts, final int type, + final int outCodeOR, + final float[] clipRect) + { + curve.set(pts, type); + + // clip rectangle (ymin, ymax, xmin, xmax) + int ret = 0; + + if ((outCodeOR & OUTCODE_LEFT) != 0) { + ret += curve.xPoints(ts, ret, clipRect[2]); + } + if ((outCodeOR & OUTCODE_RIGHT) != 0) { + ret += curve.xPoints(ts, ret, clipRect[3]); + } + if ((outCodeOR & OUTCODE_TOP) != 0) { + ret += curve.yPoints(ts, ret, clipRect[0]); + } + if ((outCodeOR & OUTCODE_BOTTOM) != 0) { + ret += curve.yPoints(ts, ret, clipRect[1]); + } + isort(ts, ret); + return ret; } - static void subdivide(float[] src, int srcoff, float[] left, int leftoff, - float[] right, int rightoff, int type) + static void subdivide(final float[] src, + final float[] left, final float[] right, + final int type) { switch(type) { - case 6: - Helpers.subdivideQuad(src, srcoff, left, leftoff, right, rightoff); - return; case 8: - Helpers.subdivideCubic(src, srcoff, left, leftoff, right, rightoff); + subdivideCubic(src, left, right); + return; + case 6: + subdivideQuad(src, left, right); return; default: throw new InternalError("Unsupported curve type"); } } - static void isort(float[] a, int off, int len) { - for (int i = off + 1, end = off + len; i < end; i++) { - float ai = a[i]; - int j = i - 1; - for (; j >= off && a[j] > ai; j--) { - a[j+1] = a[j]; + static void isort(final float[] a, final int len) { + for (int i = 1, j; i < len; i++) { + final float ai = a[i]; + j = i - 1; + for (; j >= 0 && a[j] > ai; j--) { + a[j + 1] = a[j]; } - a[j+1] = ai; + a[j + 1] = ai; } } @@ -227,206 +376,216 @@ * 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(float[] src, int srcoff, - float[] left, int leftoff, - float[] right, int rightoff) - { - float x1 = src[srcoff + 0]; - float y1 = src[srcoff + 1]; - float ctrlx1 = src[srcoff + 2]; - float ctrly1 = src[srcoff + 3]; - float ctrlx2 = src[srcoff + 4]; - float ctrly2 = src[srcoff + 5]; - float x2 = src[srcoff + 6]; - float 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) / 2.0f; - y1 = (y1 + ctrly1) / 2.0f; - x2 = (x2 + ctrlx2) / 2.0f; - y2 = (y2 + ctrly2) / 2.0f; - float centerx = (ctrlx1 + ctrlx2) / 2.0f; - float centery = (ctrly1 + ctrly2) / 2.0f; - ctrlx1 = (x1 + centerx) / 2.0f; - ctrly1 = (y1 + centery) / 2.0f; - ctrlx2 = (x2 + centerx) / 2.0f; - ctrly2 = (y2 + centery) / 2.0f; - centerx = (ctrlx1 + ctrlx2) / 2.0f; - centery = (ctrly1 + ctrly2) / 2.0f; - 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(float t, float[] src, int srcoff, - float[] left, int leftoff, - float[] right, int rightoff) - { - float x1 = src[srcoff + 0]; - float y1 = src[srcoff + 1]; - float ctrlx1 = src[srcoff + 2]; - float ctrly1 = src[srcoff + 3]; - float ctrlx2 = src[srcoff + 4]; - float ctrly2 = src[srcoff + 5]; - float x2 = src[srcoff + 6]; - float 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); - float centerx = ctrlx1 + t * (ctrlx2 - ctrlx1); - float 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(float[] src, int srcoff, - float[] left, int leftoff, - float[] right, int rightoff) - { - float x1 = src[srcoff + 0]; - float y1 = src[srcoff + 1]; - float ctrlx = src[srcoff + 2]; - float ctrly = src[srcoff + 3]; - float x2 = src[srcoff + 4]; - float 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) / 2.0f; - y1 = (y1 + ctrly) / 2.0f; - x2 = (x2 + ctrlx) / 2.0f; - y2 = (y2 + ctrly) / 2.0f; - ctrlx = (x1 + x2) / 2.0f; - ctrly = (y1 + y2) / 2.0f; - 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(float t, float[] src, int srcoff, - float[] left, int leftoff, - float[] right, int rightoff) - { - float x1 = src[srcoff + 0]; - float y1 = src[srcoff + 1]; - float ctrlx = src[srcoff + 2]; - float ctrly = src[srcoff + 3]; - float x2 = src[srcoff + 4]; - float 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(float t, float[] src, int srcoff, - float[] left, int leftoff, - float[] 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; + static void subdivideCubic(final float[] src, + final float[] left, + final float[] right) + { + float x1 = src[0]; + float y1 = src[1]; + float cx1 = src[2]; + float cy1 = src[3]; + float cx2 = src[4]; + float cy2 = src[5]; + float x2 = src[6]; + float y2 = src[7]; + + left[0] = x1; + left[1] = y1; + + right[6] = x2; + right[7] = y2; + + x1 = (x1 + cx1) / 2.0f; + y1 = (y1 + cy1) / 2.0f; + x2 = (x2 + cx2) / 2.0f; + y2 = (y2 + cy2) / 2.0f; + + float cx = (cx1 + cx2) / 2.0f; + float cy = (cy1 + cy2) / 2.0f; + + cx1 = (x1 + cx) / 2.0f; + cy1 = (y1 + cy) / 2.0f; + cx2 = (x2 + cx) / 2.0f; + cy2 = (y2 + cy) / 2.0f; + cx = (cx1 + cx2) / 2.0f; + cy = (cy1 + cy2) / 2.0f; + + left[2] = x1; + left[3] = y1; + left[4] = cx1; + left[5] = cy1; + left[6] = cx; + left[7] = cy; + + right[0] = cx; + right[1] = cy; + right[2] = cx2; + right[3] = cy2; + right[4] = x2; + right[5] = y2; + } + + static void subdivideCubicAt(final float t, + final float[] src, final int offS, + final float[] pts, final int offL, final int offR) + { + float x1 = src[offS ]; + float y1 = src[offS + 1]; + float cx1 = src[offS + 2]; + float cy1 = src[offS + 3]; + float cx2 = src[offS + 4]; + float cy2 = src[offS + 5]; + float x2 = src[offS + 6]; + float y2 = src[offS + 7]; + + pts[offL ] = x1; + pts[offL + 1] = y1; + + pts[offR + 6] = x2; + pts[offR + 7] = y2; + + x1 = x1 + t * (cx1 - x1); + y1 = y1 + t * (cy1 - y1); + x2 = cx2 + t * (x2 - cx2); + y2 = cy2 + t * (y2 - cy2); + + float cx = cx1 + t * (cx2 - cx1); + float cy = cy1 + t * (cy2 - cy1); + + cx1 = x1 + t * (cx - x1); + cy1 = y1 + t * (cy - y1); + cx2 = cx + t * (x2 - cx); + cy2 = cy + t * (y2 - cy); + cx = cx1 + t * (cx2 - cx1); + cy = cy1 + t * (cy2 - cy1); + + pts[offL + 2] = x1; + pts[offL + 3] = y1; + pts[offL + 4] = cx1; + pts[offL + 5] = cy1; + pts[offL + 6] = cx; + pts[offL + 7] = cy; + + pts[offR ] = cx; + pts[offR + 1] = cy; + pts[offR + 2] = cx2; + pts[offR + 3] = cy2; + pts[offR + 4] = x2; + pts[offR + 5] = y2; + } + + static void subdivideQuad(final float[] src, + final float[] left, + final float[] right) + { + float x1 = src[0]; + float y1 = src[1]; + float cx = src[2]; + float cy = src[3]; + float x2 = src[4]; + float y2 = src[5]; + + left[0] = x1; + left[1] = y1; + + right[4] = x2; + right[5] = y2; + + x1 = (x1 + cx) / 2.0f; + y1 = (y1 + cy) / 2.0f; + x2 = (x2 + cx) / 2.0f; + y2 = (y2 + cy) / 2.0f; + cx = (x1 + x2) / 2.0f; + cy = (y1 + y2) / 2.0f; + + left[2] = x1; + left[3] = y1; + left[4] = cx; + left[5] = cy; + + right[0] = cx; + right[1] = cy; + right[2] = x2; + right[3] = y2; + } + + static void subdivideQuadAt(final float t, + final float[] src, final int offS, + final float[] pts, final int offL, final int offR) + { + float x1 = src[offS ]; + float y1 = src[offS + 1]; + float cx = src[offS + 2]; + float cy = src[offS + 3]; + float x2 = src[offS + 4]; + float y2 = src[offS + 5]; + + pts[offL ] = x1; + pts[offL + 1] = y1; + + pts[offR + 4] = x2; + pts[offR + 5] = y2; + + x1 = x1 + t * (cx - x1); + y1 = y1 + t * (cy - y1); + x2 = cx + t * (x2 - cx); + y2 = cy + t * (y2 - cy); + cx = x1 + t * (x2 - x1); + cy = y1 + t * (y2 - y1); + + pts[offL + 2] = x1; + pts[offL + 3] = y1; + pts[offL + 4] = cx; + pts[offL + 5] = cy; + + pts[offR ] = cx; + pts[offR + 1] = cy; + pts[offR + 2] = x2; + pts[offR + 3] = y2; + } + + static void subdivideLineAt(final float t, + final float[] src, final int offS, + final float[] pts, final int offL, final int offR) + { + float x1 = src[offS ]; + float y1 = src[offS + 1]; + float x2 = src[offS + 2]; + float y2 = src[offS + 3]; + + pts[offL ] = x1; + pts[offL + 1] = y1; + + pts[offR + 2] = x2; + pts[offR + 3] = y2; + + x1 = x1 + t * (x2 - x1); + y1 = y1 + t * (y2 - y1); + + pts[offL + 2] = x1; + pts[offL + 3] = y1; + + pts[offR ] = x1; + pts[offR + 1] = y1; + } + + static void subdivideAt(final float t, + final float[] src, final int offS, + final float[] pts, final int offL, final int type) + { + // if instead of switch (perf + most probable cases first) + if (type == 8) { + subdivideCubicAt(t, src, offS, pts, offL, offL + type); + } else if (type == 4) { + subdivideLineAt(t, src, offS, pts, offL, offL + type); + } else { + subdivideQuadAt(t, src, offS, pts, offL, offL + type); } } @@ -613,17 +772,17 @@ io.lineTo(_curves[e], _curves[e+1]); e += 2; continue; - case TYPE_QUADTO: - io.quadTo(_curves[e+0], _curves[e+1], - _curves[e+2], _curves[e+3]); - e += 4; - continue; case TYPE_CUBICTO: - io.curveTo(_curves[e+0], _curves[e+1], + io.curveTo(_curves[e], _curves[e+1], _curves[e+2], _curves[e+3], _curves[e+4], _curves[e+5]); e += 6; continue; + case TYPE_QUADTO: + io.quadTo(_curves[e], _curves[e+1], + _curves[e+2], _curves[e+3]); + e += 4; + continue; default: } } @@ -655,17 +814,17 @@ 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], + io.curveTo(_curves[e], _curves[e+1], _curves[e+2], _curves[e+3], _curves[e+4], _curves[e+5]); continue; + case TYPE_QUADTO: + e -= 4; + io.quadTo(_curves[e], _curves[e+1], + _curves[e+2], _curves[e+3]); + continue; default: } } --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/IntArrayCache.java 2018-06-08 16:12:50.383686528 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/IntArrayCache.java 2018-06-08 16:12:50.211686530 +0200 @@ -99,7 +99,7 @@ Reference(final IntArrayCache cache, final int initialSize) { this.cache = cache; this.clean = cache.clean; - this.initial = createArray(initialSize, clean); + this.initial = createArray(initialSize); if (DO_STATS) { cache.stats.totalInitial += initialSize; } @@ -116,7 +116,7 @@ logInfo(getLogPrefix(clean) + "IntArrayCache: " + "getArray[oversize]: length=\t" + length); } - return createArray(length, clean); + return createArray(length); } int[] widenArray(final int[] array, final int usedSize, @@ -202,7 +202,7 @@ if (DO_STATS) { stats.createOp++; } - return createArray(arraySize, clean); + return createArray(arraySize); } void putArray(final int[] array) @@ -229,7 +229,7 @@ } } - static int[] createArray(final int length, final boolean clean) { + static int[] createArray(final int length) { return new int[length]; } --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/MarlinConst.java 2018-06-08 16:12:50.723686523 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/MarlinConst.java 2018-06-08 16:12:50.555686526 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -74,23 +74,31 @@ // do clean dirty array static final boolean DO_CLEAN_DIRTY = false; - // flag to use line simplifier + // flag to use collinear simplifier static final boolean USE_SIMPLIFIER = MarlinProperties.isUseSimplifier(); + // flag to use path simplifier + static final boolean USE_PATH_SIMPLIFIER = MarlinProperties.isUsePathSimplifier(); + + static final boolean DO_CLIP_SUBDIVIDER = MarlinProperties.isDoClipSubdivider(); + // flag to enable logs related bounds checks static final boolean DO_LOG_BOUNDS = ENABLE_LOGS && false; // Initial Array sizing (initial context capacity) ~ 450K - // 2048 pixel (width x height) for initial capacity - static final int INITIAL_PIXEL_DIM - = MarlinProperties.getInitialImageSize(); + // 4096 pixels (width) for initial capacity + static final int INITIAL_PIXEL_WIDTH + = MarlinProperties.getInitialPixelWidth(); + // 2176 pixels (height) for initial capacity + static final int INITIAL_PIXEL_HEIGHT + = MarlinProperties.getInitialPixelHeight(); // typical array sizes: only odd numbers allowed below static final int INITIAL_ARRAY = 256; // alpha row dimension - static final int INITIAL_AA_ARRAY = INITIAL_PIXEL_DIM; + static final int INITIAL_AA_ARRAY = INITIAL_PIXEL_WIDTH; // 4096 edges for initial capacity static final int INITIAL_EDGES_COUNT = MarlinProperties.getInitialEdges(); @@ -112,20 +120,21 @@ public static final int SUBPIXEL_LG_POSITIONS_Y = MarlinProperties.getSubPixel_Log2_Y(); + public static final int MIN_SUBPIXEL_LG_POSITIONS + = Math.min(SUBPIXEL_LG_POSITIONS_X, SUBPIXEL_LG_POSITIONS_Y); + // number of subpixels public static final int SUBPIXEL_POSITIONS_X = 1 << (SUBPIXEL_LG_POSITIONS_X); public static final int SUBPIXEL_POSITIONS_Y = 1 << (SUBPIXEL_LG_POSITIONS_Y); - // 2048 (pixelSize) pixels (height) x 8 subpixels = 64K - static final int INITIAL_BUCKET_ARRAY - = INITIAL_PIXEL_DIM * SUBPIXEL_POSITIONS_Y; + public static final float MIN_SUBPIXELS = 1 << MIN_SUBPIXEL_LG_POSITIONS; - public static final float NORM_SUBPIXELS - = (float) Math.sqrt(( SUBPIXEL_POSITIONS_X * SUBPIXEL_POSITIONS_X - + SUBPIXEL_POSITIONS_Y * SUBPIXEL_POSITIONS_Y) / 2.0d); + // 2176 pixels (height) x 8 subpixels = 68K + static final int INITIAL_BUCKET_ARRAY + = INITIAL_PIXEL_HEIGHT * SUBPIXEL_POSITIONS_Y; public static final int MAX_AA_ALPHA - = SUBPIXEL_POSITIONS_X * SUBPIXEL_POSITIONS_Y; + = (SUBPIXEL_POSITIONS_X * SUBPIXEL_POSITIONS_Y); public static final int BLOCK_SIZE_LG = MarlinProperties.getBlockSize_Log2(); public static final int BLOCK_SIZE = 1 << BLOCK_SIZE_LG; --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/MarlinProperties.java 2018-06-08 16:12:51.059686519 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/MarlinProperties.java 2018-06-08 16:12:50.895686521 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -54,35 +54,68 @@ } /** - * Return the initial pixel size used to define initial arrays - * (tile AA chunk, alpha line, buckets) + * Return the initial pixel width used to define initial arrays + * (tile AA chunk, alpha line) * - * @return 64 < initial pixel size < 32768 (2048 by default) + * @return 64 < initial pixel size < 32768 (4096 by default) */ - public static int getInitialImageSize() { + public static int getInitialPixelWidth() { return align( - getInteger("prism.marlin.pixelsize", 2048, 64, 32 * 1024), + getInteger("prism.marlin.pixelWidth", 4096, 64, 32 * 1024), 64); } /** - * Return the log(2) corresponding to subpixel on x-axis ( + * Return the initial pixel height used to define initial arrays + * (buckets) + * + * @return 64 < initial pixel size < 32768 (2176 by default) + */ + public static int getInitialPixelHeight() { + return align( + getInteger("prism.marlin.pixelHeight", 2176, 64, 32 * 1024), + 64); + } + + /** + * Return true if the profile is 'quality' (default) over 'speed' + * + * @return true if the profile is 'quality' (default), false otherwise + */ + public static boolean isProfileQuality() { + final String key = "prism.marlin.profile"; + final String profile = getString(key, "quality"); + if ("quality".equals(profile)) { + return true; + } + if ("speed".equals(profile)) { + return false; + } + logInfo("Invalid value for " + key + " = " + profile + + "; expect value in [quality, speed] !"); + return true; + } + + /** + * Return the log(2) corresponding to subpixel on x-axis * * @return 0 (1 subpixels) < initial pixel size < 8 (256 subpixels) - * (3 by default ie 8 subpixels) + * (8 by default ie 256 subpixels) */ public static int getSubPixel_Log2_X() { - return getInteger("prism.marlin.subPixel_log2_X", 3, 0, 8); + return getInteger("prism.marlin.subPixel_log2_X", 8, 0, 8); } /** - * Return the log(2) corresponding to subpixel on y-axis ( + * Return the log(2) corresponding to subpixel on y-axis * * @return 0 (1 subpixels) < initial pixel size < 8 (256 subpixels) - * (3 by default ie 8 subpixels) + * (3 by default ie 8 subpixels for the quality profile) + * (2 by default ie 4 subpixels for the speed profile) */ public static int getSubPixel_Log2_Y() { - return getInteger("prism.marlin.subPixel_log2_Y", 3, 0, 8); + final int def = isProfileQuality() ? 3 : 2; + return getInteger("prism.marlin.subPixel_log2_Y", def, 0, 8); } /** @@ -124,6 +157,18 @@ return getBoolean("prism.marlin.useSimplifier", "false"); } + public static boolean isUsePathSimplifier() { + return getBoolean("prism.marlin.usePathSimplifier", "false"); + } + + public static float getPathSimplifierPixelTolerance() { + // default: MIN_PEN_SIZE or less ? + return getFloat("prism.marlin.pathSimplifier.pixTol", + (1.0f / MarlinConst.MIN_SUBPIXELS), + 1e-3f, + 10.0f); + } + public static boolean isDoClip() { return getBoolean("prism.marlin.clip", "true"); } @@ -136,6 +181,14 @@ return getBoolean("prism.marlin.clip.runtime", "true"); } + public static boolean isDoClipSubdivider() { + return getBoolean("prism.marlin.clip.subdivider", "true"); + } + + public static float getSubdividerMinLength() { + return getFloat("prism.marlin.clip.subdivider.minLength", 100.0f, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY); + } + // debugging parameters public static boolean isDoStats() { @@ -169,20 +222,34 @@ } // quality settings + public static float getCurveLengthError() { + return getFloat("prism.marlin.curve_len_err", 0.01f, 1e-6f, 1.0f); + } public static float getCubicDecD2() { - return getFloat("prism.marlin.cubic_dec_d2", 1.0f, 0.01f, 4.0f); + final float def = isProfileQuality() ? 1.0f : 2.5f; + return getFloat("prism.marlin.cubic_dec_d2", def, 1e-5f, 4.0f); } public static float getCubicIncD1() { - return getFloat("prism.marlin.cubic_inc_d1", 0.4f, 0.01f, 2.0f); + final float def = isProfileQuality() ? 0.2f : 0.5f; + return getFloat("prism.marlin.cubic_inc_d1", def, 1e-6f, 1.0f); } public static float getQuadDecD2() { - return getFloat("prism.marlin.quad_dec_d2", 0.5f, 0.01f, 4.0f); + final float def = isProfileQuality() ? 0.5f : 1.0f; + return getFloat("prism.marlin.quad_dec_d2", def, 1e-5f, 4.0f); } // system property utilities + static String getString(final String key, final String def) { + return AccessController.doPrivileged( + (PrivilegedAction) () -> { + String value = System.getProperty(key); + return (value == null) ? def : value; + }); + } + static boolean getBoolean(final String key, final String def) { return Boolean.valueOf(AccessController.doPrivileged( (PrivilegedAction) () -> { --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/MarlinRenderingEngine.java 2018-06-08 16:12:51.395686514 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/MarlinRenderingEngine.java 2018-06-08 16:12:51.235686516 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -151,8 +151,14 @@ logInfo("prism.marlin.edges = " + MarlinConst.INITIAL_EDGES_COUNT); - logInfo("prism.marlin.pixelsize = " - + MarlinConst.INITIAL_PIXEL_DIM); + logInfo("prism.marlin.pixelWidth = " + + MarlinConst.INITIAL_PIXEL_WIDTH); + logInfo("prism.marlin.pixelHeight = " + + MarlinConst.INITIAL_PIXEL_HEIGHT); + + logInfo("prism.marlin.profile = " + + (MarlinProperties.isProfileQuality() ? + "quality" : "speed")); logInfo("prism.marlin.subPixel_log2_X = " + MarlinConst.SUBPIXEL_LG_POSITIONS_X); @@ -178,11 +184,21 @@ // optimisation parameters logInfo("prism.marlin.useSimplifier = " + MarlinConst.USE_SIMPLIFIER); + logInfo("prism.marlin.usePathSimplifier= " + + MarlinConst.USE_PATH_SIMPLIFIER); + logInfo("prism.marlin.pathSimplifier.pixTol = " + + MarlinProperties.getPathSimplifierPixelTolerance()); + logInfo("prism.marlin.clip = " + MarlinProperties.isDoClip()); logInfo("prism.marlin.clip.runtime.enable = " + MarlinProperties.isDoClipRuntimeFlag()); + logInfo("prism.marlin.clip.subdivider = " + + MarlinProperties.isDoClipSubdivider()); + logInfo("prism.marlin.clip.subdivider.minLength = " + + MarlinProperties.getSubdividerMinLength()); + // debugging parameters logInfo("prism.marlin.doStats = " + MarlinConst.DO_STATS); @@ -202,6 +218,8 @@ + MarlinConst.LOG_UNSAFE_MALLOC); // quality settings + logInfo("prism.marlin.curve_len_err = " + + MarlinProperties.getCurveLengthError()); logInfo("prism.marlin.cubic_dec_d2 = " + MarlinProperties.getCubicDecD2()); logInfo("prism.marlin.cubic_inc_d1 = " --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/MarlinUtils.java 2018-06-08 16:12:51.723686509 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/MarlinUtils.java 2018-06-08 16:12:51.563686512 +0200 @@ -86,9 +86,4 @@ static java.lang.ref.Cleaner getCleaner() { return cleaner; } -/* - static jdk.internal.ref.Cleaner getCleaner() { - return jdk.internal.ref.CleanerFactory.cleaner(); - } -*/ } --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/MergeSort.java 2018-06-08 16:12:52.055686505 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/MergeSort.java 2018-06-08 16:12:51.895686507 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -61,7 +61,6 @@ // Merge sorted parts (auxX/auxY) into x/y arrays if ((insertionSortIndex == 0) || (auxX[insertionSortIndex - 1] <= auxX[insertionSortIndex])) { -// System.out.println("mergeSortNoCopy: ordered"); // 34 occurences // no initial left part or both sublists (auxX, auxY) are sorted: // copy back data into (x, y): @@ -135,7 +134,6 @@ // If arrays are inverted ie all(A) > all(B) do swap A and B to dst if (srcX[high - 1] <= srcX[low]) { -// System.out.println("mergeSort: inverse ordered"); // 1561 occurences final int left = mid - low; final int right = high - mid; @@ -151,7 +149,6 @@ // If arrays are already sorted, just copy from src to dest. This is an // optimization that results in faster sorts for nearly ordered lists. if (srcX[mid - 1] <= srcX[mid]) { -// System.out.println("mergeSort: ordered"); // 14 occurences System.arraycopy(srcX, low, dstX, low, length); System.arraycopy(srcY, low, dstY, low, length); --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/OffHeapArray.java 2018-06-08 16:12:52.383686500 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/OffHeapArray.java 2018-06-08 16:12:52.223686502 +0200 @@ -33,7 +33,6 @@ /** * - * @author bourgesl */ final class OffHeapArray { --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/Renderer.java 2018-06-08 16:12:52.727686496 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/Renderer.java 2018-06-08 16:12:52.559686498 +0200 @@ -62,13 +62,17 @@ // curve break into lines // cubic error in subpixels to decrement step private static final float CUB_DEC_ERR_SUBPIX - = MarlinProperties.getCubicDecD2() * (NORM_SUBPIXELS / 8.0f); // 1 pixel + = MarlinProperties.getCubicDecD2() * (SUBPIXEL_POSITIONS_X / 8.0f); // 1.0 / 8th pixel // cubic error in subpixels to increment step private static final float CUB_INC_ERR_SUBPIX - = MarlinProperties.getCubicIncD1() * (NORM_SUBPIXELS / 8.0f); // 0.4 pixel + = MarlinProperties.getCubicIncD1() * (SUBPIXEL_POSITIONS_X / 8.0f); // 0.4 / 8th pixel + // scale factor for Y-axis contribution to quad / cubic errors: + public static final float SCALE_DY = ((float) SUBPIXEL_POSITIONS_X) / SUBPIXEL_POSITIONS_Y; // TestNonAARasterization (JDK-8170879): cubics // bad paths (59294/100000 == 59,29%, 94335 bad pixels (avg = 1,59), 3966 warnings (avg = 0,07) +// 2018 + // 1.0 / 0.2: bad paths (67194/100000 == 67,19%, 117394 bad pixels (avg = 1,75 - max = 9), 4042 warnings (avg = 0,06) // cubic bind length to decrement step public static final float CUB_DEC_BND @@ -95,10 +99,12 @@ // quad break into lines // quadratic error in subpixels private static final float QUAD_DEC_ERR_SUBPIX - = MarlinProperties.getQuadDecD2() * (NORM_SUBPIXELS / 8.0f); // 0.5 pixel + = MarlinProperties.getQuadDecD2() * (SUBPIXEL_POSITIONS_X / 8.0f); // 0.5 / 8th pixel // TestNonAARasterization (JDK-8170879): quads // bad paths (62916/100000 == 62,92%, 103818 bad pixels (avg = 1,65), 6514 warnings (avg = 0,10) +// 2018 + // 0.50px = bad paths (62915/100000 == 62,92%, 103810 bad pixels (avg = 1,65), 6512 warnings (avg = 0,10) // quadratic bind length to decrement step public static final float QUAD_DEC_BND @@ -167,7 +173,7 @@ int count = 1; // dt = 1 / count // maximum(ddX|Y) = norm(dbx, dby) * dt^2 (= 1) - float maxDD = Math.abs(c.dbx) + Math.abs(c.dby); + float maxDD = Math.abs(c.dbx) + Math.abs(c.dby) * SCALE_DY; final float _DEC_BND = QUAD_DEC_BND; @@ -181,7 +187,8 @@ } } - int nL = 0; // line count + final int nL = count; // line count + if (count > 1) { final float icount = 1.0f / count; // dt final float icount2 = icount * icount; // dt^2 @@ -191,17 +198,12 @@ float dx = c.bx * icount2 + c.cx * icount; float dy = c.by * icount2 + c.cy * icount; - float x1, y1; - - while (--count > 0) { - x1 = x0 + dx; - dx += ddx; - y1 = y0 + dy; - dy += ddy; + // we use x0, y0 to walk the line + for (float x1 = x0, y1 = y0; --count > 0; dx += ddx, dy += ddy) { + x1 += dx; + y1 += dy; addLine(x0, y0, x1, y1); - - if (DO_STATS) { nL++; } x0 = x1; y0 = y1; } @@ -209,7 +211,7 @@ addLine(x0, y0, x2, y2); if (DO_STATS) { - rdrCtx.stats.stat_rdr_quadBreak.add(nL + 1); + rdrCtx.stats.stat_rdr_quadBreak.add(nL); } } @@ -237,34 +239,20 @@ 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 - float x1 = x0, y1 = y0; int nL = 0; // line count final float _DEC_BND = CUB_DEC_BND; final float _INC_BND = CUB_INC_BND; + final float _SCALE_DY = SCALE_DY; - while (count > 0) { - // divide step by half: - while (Math.abs(ddx) + Math.abs(ddy) >= _DEC_BND) { - dddx /= 8.0f; - dddy /= 8.0f; - ddx = ddx / 4.0f - dddx; - ddy = ddy / 4.0f - dddy; - dx = (dx - ddx) / 2.0f; - dy = (dy - ddy) / 2.0f; - - count <<= 1; - if (DO_STATS) { - rdrCtx.stats.stat_rdr_curveBreak_dec.add(count); - } - } + // we use x0, y0 to walk the line + for (float x1 = x0, y1 = y0; count > 0; ) { + // inc / dec => ratio ~ 5 to minimize upscale / downscale but minimize edges - // double step: + // float step: // can only do this on even "count" values, because we must divide count by 2 - while (count % 2 == 0 - && Math.abs(dx) + Math.abs(dy) <= _INC_BND) - { + while ((count % 2 == 0) + && ((Math.abs(ddx) + Math.abs(ddy) * _SCALE_DY) <= _INC_BND)) { dx = 2.0f * dx + ddx; dy = 2.0f * dy + ddy; ddx = 4.0f * (ddx + dddx); @@ -277,26 +265,40 @@ 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; + + // divide step by half: + while ((Math.abs(ddx) + Math.abs(ddy) * _SCALE_DY) >= _DEC_BND) { + dddx /= 8.0f; + dddy /= 8.0f; + ddx = ddx / 4.0f - dddx; + ddy = ddy / 4.0f - dddy; + dx = (dx - ddx) / 2.0f; + dy = (dy - ddy) / 2.0f; + + count <<= 1; + if (DO_STATS) { + rdrCtx.stats.stat_rdr_curveBreak_dec.add(count); + } + } + if (--count == 0) { + break; } - addLine(x0, y0, x1, y1); + x1 += dx; + y1 += dy; + dx += ddx; + dy += ddy; + ddx += dddx; + ddy += dddy; - if (DO_STATS) { nL++; } + addLine(x0, y0, x1, y1); x0 = x1; y0 = y1; } + addLine(x0, y0, x3, y3); + if (DO_STATS) { - rdrCtx.stats.stat_rdr_curveBreak.add(nL); + rdrCtx.stats.stat_rdr_curveBreak.add(nL + 1); } } @@ -680,8 +682,10 @@ { final float xe = tosubpixx(pix_x3); final float ye = tosubpixy(pix_y3); - curve.set(x0, y0, tosubpixx(pix_x1), tosubpixy(pix_y1), - tosubpixx(pix_x2), tosubpixy(pix_y2), xe, ye); + curve.set(x0, y0, + tosubpixx(pix_x1), tosubpixy(pix_y1), + tosubpixx(pix_x2), tosubpixy(pix_y2), + xe, ye); curveBreakIntoLinesAndAdd(x0, y0, curve, xe, ye); x0 = xe; y0 = ye; @@ -693,7 +697,9 @@ { final float xe = tosubpixx(pix_x2); final float ye = tosubpixy(pix_y2); - curve.set(x0, y0, tosubpixx(pix_x1), tosubpixy(pix_y1), xe, ye); + curve.set(x0, y0, + tosubpixx(pix_x1), tosubpixy(pix_y1), + xe, ye); quadBreakIntoLinesAndAdd(x0, y0, curve, xe, ye); x0 = xe; y0 = ye; --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/RendererContext.java 2018-06-08 16:12:53.071686491 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/RendererContext.java 2018-06-08 16:12:52.907686493 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,12 +25,14 @@ package com.sun.marlin; -import com.sun.javafx.geom.Path2D; +import java.lang.ref.WeakReference; import java.util.concurrent.atomic.AtomicInteger; -import com.sun.util.reentrant.ReentrantContext; +import com.sun.javafx.geom.Path2D; import com.sun.javafx.geom.Rectangle; import com.sun.marlin.ArrayCacheConst.CacheStats; -import java.lang.ref.WeakReference; +import com.sun.marlin.TransformingPathConsumer2D.CurveBasicMonotonizer; +import com.sun.marlin.TransformingPathConsumer2D.CurveClipSplitter; +import com.sun.util.reentrant.ReentrantContext; /** * This class is a renderer context dedicated to a single thread @@ -62,13 +64,12 @@ public final TransformingPathConsumer2D transformerPC2D; // recycled Path2D instance (weak) private WeakReference refPath2D = null; - // shared memory between renderer instances: - final RendererSharedMemory rdrMem; public final Renderer renderer; - private RendererNoAA rendererNoAA = null; public final Stroker stroker; // Simplifies out collinear lines public final CollinearSimplifier simplifier = new CollinearSimplifier(); + // Simplifies path + public final PathSimplifier pathSimplifier = new PathSimplifier(); public final Dasher dasher; // flag indicating the shape is stroked (1) or filled (0) int stroking = 0; @@ -78,8 +79,15 @@ boolean closedPath = false; // clip rectangle (ymin, ymax, xmin, xmax): public final float[] clipRect = new float[4]; + // CurveBasicMonotonizer instance + public final CurveBasicMonotonizer monotonizer; + // CurveClipSplitter instance + final CurveClipSplitter curveClipSplitter; // MarlinFX specific: + // shared memory between renderer instances: + final RendererSharedMemory rdrMem; + private RendererNoAA rendererNoAA = null; // dirty bbox rectangle public final Rectangle clip = new Rectangle(); // dirty MaskMarlinAlphaConsumer @@ -120,6 +128,10 @@ stats = null; } + // curve monotonizer & clip subdivider (before transformerPC2D init) + monotonizer = new CurveBasicMonotonizer(this); + curveClipSplitter = new CurveClipSplitter(this); + // MarlinRenderingEngine.TransformingPathConsumer2D transformerPC2D = new TransformingPathConsumer2D(this); @@ -241,8 +253,8 @@ edgeBuckets_ref = rdrCtx.newCleanIntArrayRef(INITIAL_BUCKET_ARRAY); // 64K edgeBucketCounts_ref = rdrCtx.newCleanIntArrayRef(INITIAL_BUCKET_ARRAY); // 64K - // 2048 (pixelsize) pixel large - alphaLine_ref = rdrCtx.newCleanIntArrayRef(INITIAL_AA_ARRAY); // 8K + // 4096 pixels large + alphaLine_ref = rdrCtx.newCleanIntArrayRef(INITIAL_AA_ARRAY); // 16K crossings_ref = rdrCtx.newDirtyIntArrayRef(INITIAL_CROSSING_COUNT); // 2K aux_crossings_ref = rdrCtx.newDirtyIntArrayRef(INITIAL_CROSSING_COUNT); // 2K --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/RendererNoAA.java 2018-06-08 16:12:53.407686486 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/RendererNoAA.java 2018-06-08 16:12:53.247686488 +0200 @@ -63,6 +63,8 @@ // TestNonAARasterization (JDK-8170879): cubics // bad paths (59294/100000 == 59,29%, 94335 bad pixels (avg = 1,59), 3966 warnings (avg = 0,07) +// 2018 + // 1.0 / 0.2: bad paths (67194/100000 == 67,19%, 117394 bad pixels (avg = 1,75 - max = 9), 4042 warnings (avg = 0,06) // cubic bind length to decrement step public static final float CUB_DEC_BND @@ -93,6 +95,8 @@ // TestNonAARasterization (JDK-8170879): quads // bad paths (62916/100000 == 62,92%, 103818 bad pixels (avg = 1,65), 6514 warnings (avg = 0,10) +// 2018 + // 0.50px = bad paths (62915/100000 == 62,92%, 103810 bad pixels (avg = 1,65), 6512 warnings (avg = 0,10) // quadratic bind length to decrement step public static final float QUAD_DEC_BND @@ -175,7 +179,8 @@ } } - int nL = 0; // line count + final int nL = count; // line count + if (count > 1) { final float icount = 1.0f / count; // dt final float icount2 = icount * icount; // dt^2 @@ -185,17 +190,12 @@ float dx = c.bx * icount2 + c.cx * icount; float dy = c.by * icount2 + c.cy * icount; - float x1, y1; - - while (--count > 0) { - x1 = x0 + dx; - dx += ddx; - y1 = y0 + dy; - dy += ddy; + // we use x0, y0 to walk the line + for (float x1 = x0, y1 = y0; --count > 0; dx += ddx, dy += ddy) { + x1 += dx; + y1 += dy; addLine(x0, y0, x1, y1); - - if (DO_STATS) { nL++; } x0 = x1; y0 = y1; } @@ -203,7 +203,7 @@ addLine(x0, y0, x2, y2); if (DO_STATS) { - rdrCtx.stats.stat_rdr_quadBreak.add(nL + 1); + rdrCtx.stats.stat_rdr_quadBreak.add(nL); } } @@ -231,34 +231,19 @@ 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 - float x1 = x0, y1 = y0; int nL = 0; // line count final float _DEC_BND = CUB_DEC_BND; final float _INC_BND = CUB_INC_BND; - while (count > 0) { - // divide step by half: - while (Math.abs(ddx) + Math.abs(ddy) >= _DEC_BND) { - dddx /= 8.0f; - dddy /= 8.0f; - ddx = ddx / 4.0f - dddx; - ddy = ddy / 4.0f - dddy; - dx = (dx - ddx) / 2.0f; - dy = (dy - ddy) / 2.0f; - - count <<= 1; - if (DO_STATS) { - rdrCtx.stats.stat_rdr_curveBreak_dec.add(count); - } - } + // we use x0, y0 to walk the line + for (float x1 = x0, y1 = y0; count > 0; ) { + // inc / dec => ratio ~ 5 to minimize upscale / downscale but minimize edges - // double step: + // float step: // can only do this on even "count" values, because we must divide count by 2 - while (count % 2 == 0 - && Math.abs(dx) + Math.abs(dy) <= _INC_BND) - { + while ((count % 2 == 0) + && ((Math.abs(ddx) + Math.abs(ddy)) <= _INC_BND)) { dx = 2.0f * dx + ddx; dy = 2.0f * dy + ddy; ddx = 4.0f * (ddx + dddx); @@ -271,26 +256,40 @@ 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; + + // divide step by half: + while ((Math.abs(ddx) + Math.abs(ddy)) >= _DEC_BND) { + dddx /= 8.0f; + dddy /= 8.0f; + ddx = ddx / 4.0f - dddx; + ddy = ddy / 4.0f - dddy; + dx = (dx - ddx) / 2.0f; + dy = (dy - ddy) / 2.0f; + + count <<= 1; + if (DO_STATS) { + rdrCtx.stats.stat_rdr_curveBreak_dec.add(count); + } + } + if (--count == 0) { + break; } - addLine(x0, y0, x1, y1); + x1 += dx; + y1 += dy; + dx += ddx; + dy += ddy; + ddx += dddx; + ddy += dddy; - if (DO_STATS) { nL++; } + addLine(x0, y0, x1, y1); x0 = x1; y0 = y1; } + addLine(x0, y0, x3, y3); + if (DO_STATS) { - rdrCtx.stats.stat_rdr_curveBreak.add(nL); + rdrCtx.stats.stat_rdr_curveBreak.add(nL + 1); } } @@ -672,8 +671,10 @@ { final float xe = tosubpixx(pix_x3); final float ye = tosubpixy(pix_y3); - curve.set(x0, y0, tosubpixx(pix_x1), tosubpixy(pix_y1), - tosubpixx(pix_x2), tosubpixy(pix_y2), xe, ye); + curve.set(x0, y0, + tosubpixx(pix_x1), tosubpixy(pix_y1), + tosubpixx(pix_x2), tosubpixy(pix_y2), + xe, ye); curveBreakIntoLinesAndAdd(x0, y0, curve, xe, ye); x0 = xe; y0 = ye; @@ -685,7 +686,9 @@ { final float xe = tosubpixx(pix_x2); final float ye = tosubpixy(pix_y2); - curve.set(x0, y0, tosubpixx(pix_x1), tosubpixy(pix_y1), xe, ye); + curve.set(x0, y0, + tosubpixx(pix_x1), tosubpixy(pix_y1), + xe, ye); quadBreakIntoLinesAndAdd(x0, y0, curve, xe, ye); x0 = xe; y0 = ye; --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/RendererStats.java 2018-06-08 16:12:53.863686480 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/RendererStats.java 2018-06-08 16:12:53.707686482 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/Stroker.java 2018-06-08 16:12:54.311686474 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/Stroker.java 2018-06-08 16:12:54.147686476 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,6 +29,8 @@ import com.sun.javafx.geom.PathConsumer2D; import com.sun.marlin.Helpers.PolyStack; +import com.sun.marlin.TransformingPathConsumer2D.CurveBasicMonotonizer; +import com.sun.marlin.TransformingPathConsumer2D.CurveClipSplitter; // 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 @@ -39,10 +41,9 @@ private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad private static final int CLOSE = 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 floating point, so that's why the divisions by 2^16 are there. - private static final float ROUND_JOIN_THRESHOLD = 1000.0f/65536.0f; + // round join threshold = 1 subpixel + private static final float ERR_JOIN = (1.0f / MIN_SUBPIXELS); + private static final float ROUND_JOIN_THRESHOLD = ERR_JOIN * ERR_JOIN; // kappa = (4/3) * (SQRT(2) - 1) private static final float C = (float)(4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d); @@ -50,8 +51,6 @@ // SQRT(2) private static final float SQRT_2 = (float)Math.sqrt(2.0d); - private static final int MAX_N_CURVES = 11; - private PathConsumer2D out; private int capStyle; @@ -82,12 +81,8 @@ 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 float[] middle = new float[MAX_N_CURVES * 6 + 2]; private final float[] lp = new float[8]; private final float[] rp = new float[8]; - private final float[] subdivTs = new float[MAX_N_CURVES - 1]; // per-thread renderer context final RendererContext rdrCtx; @@ -108,6 +103,11 @@ private boolean opened = false; // flag indicating if the starting point's cap is done private boolean capStart = false; + // flag indicating to monotonize curves + private boolean monotonize; + + private boolean subdivide = DO_CLIP_SUBDIVIDER; + private final CurveClipSplitter curveSplitter; /** * Constructs a Stroker. @@ -126,6 +126,7 @@ : new PolyStack(rdrCtx); this.curve = rdrCtx.curve; + this.curveSplitter = rdrCtx.curveClipSplitter; } /** @@ -143,6 +144,7 @@ * @param scale scaling factor applied to clip boundaries * @param rdrOffX renderer's coordinate offset on X axis * @param rdrOffY renderer's coordinate offset on Y axis + * @param subdivideCurves true to indicate to subdivide curves, false if dasher does * @return this instance */ public Stroker init(final PathConsumer2D pc2d, @@ -152,12 +154,15 @@ final float miterLimit, final float scale, double rdrOffX, - double rdrOffY) + double rdrOffY, + final boolean subdivideCurves) { this.out = pc2d; this.lineWidth2 = lineWidth / 2.0f; this.invHalfLineWidth2Sq = 1.0f / (2.0f * lineWidth2 * lineWidth2); + this.monotonize = subdivideCurves; + this.capStyle = capStyle; this.joinStyle = joinStyle; @@ -194,6 +199,15 @@ _clipRect[2] -= margin - rdrOffX; _clipRect[3] += margin + rdrOffX; this.clipRect = _clipRect; + + // initialize curve splitter here for stroker & dasher: + if (DO_CLIP_SUBDIVIDER) { + subdivide = subdivideCurves; + // adjust padded clip rectangle: + curveSplitter.init(); + } else { + subdivide = false; + } } else { this.clipRect = null; this.cOutCode = 0; @@ -202,6 +216,12 @@ return this; // fluent API } + public void disableClipping() { + this.clipRect = null; + this.cOutCode = 0; + this.sOutCode = 0; + } + /** * Disposes this stroker: * clean up before reusing this instance @@ -218,10 +238,8 @@ Arrays.fill(offset1, 0.0f); Arrays.fill(offset2, 0.0f); Arrays.fill(miter, 0.0f); - Arrays.fill(middle, 0.0f); Arrays.fill(lp, 0.0f); Arrays.fill(rp, 0.0f); - Arrays.fill(subdivTs, 0.0f); } } @@ -253,19 +271,20 @@ return dx1 * dy2 <= dy1 * dx2; } - private void drawRoundJoin(float x, float y, - float omx, float omy, float mx, float my, - boolean rev, - float threshold) + private void mayDrawRoundJoin(float cx, float cy, + float omx, float omy, + float mx, float my, + boolean rev) { if ((omx == 0.0f && omy == 0.0f) || (mx == 0.0f && my == 0.0f)) { return; } - float domx = omx - mx; - float domy = omy - my; - float len = domx*domx + domy*domy; - if (len < threshold) { + final float domx = omx - mx; + final float domy = omy - my; + final float lenSq = domx*domx + domy*domy; + + if (lenSq < ROUND_JOIN_THRESHOLD) { return; } @@ -275,7 +294,7 @@ mx = -mx; my = -my; } - drawRoundJoin(x, y, omx, omy, mx, my, rev); + drawRoundJoin(cx, cy, omx, omy, mx, my, rev); } private void drawRoundJoin(float cx, float cy, @@ -290,13 +309,9 @@ // 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 numCurves = (cosext >= 0.0f) ? 1 : 2; - - switch (numCurves) { - case 1: + if (cosext >= 0.0f) { drawBezApproxForArc(cx, cy, omx, omy, mx, my, rev); - break; - case 2: + } else { // 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 @@ -325,8 +340,6 @@ } drawBezApproxForArc(cx, cy, omx, omy, mmx, mmy, rev); drawBezApproxForArc(cx, cy, mmx, mmy, mx, my, rev); - break; - default: } } @@ -386,7 +399,7 @@ final float x1, final float y1, final float x0p, final float y0p, final float x1p, final float y1p, - final float[] m, int off) + final float[] m) { float x10 = x1 - x0; float y10 = y1 - y0; @@ -405,8 +418,8 @@ float den = x10*y10p - x10p*y10; float t = x10p*(y0-y0p) - y10p*(x0-x0p); t /= den; - m[off++] = x0 + t*x10; - m[off] = y0 + t*y10; + m[0] = x0 + t*x10; + m[1] = y0 + t*y10; } // Return the intersection point of the lines (x0, y0) -> (x1, y1) @@ -415,7 +428,7 @@ final float x1, final float y1, final float x0p, final float y0p, final float x1p, final float y1p, - final float[] m, int off) + final float[] m) { float x10 = x1 - x0; float y10 = y1 - y0; @@ -433,20 +446,21 @@ // immediately). float den = x10*y10p - x10p*y10; if (den == 0.0f) { - m[off++] = (x0 + x0p) / 2.0f; - m[off] = (y0 + y0p) / 2.0f; - return; + m[2] = (x0 + x0p) / 2.0f; + m[3] = (y0 + y0p) / 2.0f; + } else { + float t = x10p*(y0-y0p) - y10p*(x0-x0p); + t /= den; + m[2] = x0 + t*x10; + m[3] = y0 + t*y10; } - float t = x10p*(y0-y0p) - y10p*(x0-x0p); - t /= den; - m[off++] = x0 + t*x10; - m[off] = y0 + t*y10; } private void drawMiter(final float pdx, final float pdy, final float x0, final float y0, final float dx, final float dy, - float omx, float omy, float mx, float my, + float omx, float omy, + float mx, float my, boolean rev) { if ((mx == omx && my == omy) || @@ -464,8 +478,7 @@ } computeMiter((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy, - (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my, - miter, 0); + (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my, miter); final float miterX = miter[0]; final float miterY = miter[1]; @@ -483,7 +496,7 @@ @Override public void moveTo(final float x0, final float y0) { - moveTo(x0, y0, cOutCode); + _moveTo(x0, y0, cOutCode); // update starting point: this.sx0 = x0; this.sy0 = y0; @@ -499,7 +512,7 @@ } } - private void moveTo(final float x0, final float y0, + private void _moveTo(final float x0, final float y0, final int outcode) { if (prev == MOVE_TO) { @@ -526,16 +539,40 @@ final boolean force) { final int outcode0 = this.cOutCode; + if (!force && clipRect != null) { final int outcode1 = Helpers.outcode(x1, y1, clipRect); - this.cOutCode = outcode1; - // basic rejection criteria - if ((outcode0 & outcode1) != 0) { - moveTo(x1, y1, outcode0); - opened = true; - return; + // Should clip + final int orCode = (outcode0 | outcode1); + if (orCode != 0) { + final int sideCode = outcode0 & outcode1; + + // basic rejection criteria: + if (sideCode == 0) { + // ovelap clip: + if (subdivide) { + // avoid reentrance + subdivide = false; + // subdivide curve => callback with subdivided parts: + boolean ret = curveSplitter.splitLine(cx0, cy0, x1, y1, + orCode, this); + // reentrance is done: + subdivide = true; + if (ret) { + return; + } + } + // already subdivided so render it + } else { + this.cOutCode = outcode1; + _moveTo(x1, y1, outcode0); + opened = true; + return; + } } + + this.cOutCode = outcode1; } float dx = x1 - cx0; @@ -757,10 +794,7 @@ 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); + mayDrawRoundJoin(x0, y0, omx, omy, mx, my, cw); } } emitLineTo(x0, y0, !cw); @@ -770,18 +804,19 @@ private static boolean within(final float x1, final float y1, final float x2, final float y2, - final float ERR) + final float err) { - assert ERR > 0 : ""; + assert err > 0 : ""; // compare taxicab distance. ERR will always be small, so using // true distance won't give much benefit - return (Helpers.within(x1, x2, ERR) && // we want to avoid calling Math.abs - Helpers.within(y1, y2, ERR)); // this is just as good. + return (Helpers.within(x1, x2, err) && // we want to avoid calling Math.abs + Helpers.within(y1, y2, err)); // this is just as good. } - private void getLineOffsets(float x1, float y1, - float x2, float y2, - float[] left, float[] right) { + private void getLineOffsets(final float x1, final float y1, + final float x2, final float y2, + final float[] left, final float[] right) + { computeOffset(x2 - x1, y2 - y1, lineWidth2, offset0); final float mx = offset0[0]; final float my = offset0[1]; @@ -789,14 +824,16 @@ 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(float[] pts, final int off, - float[] leftOff, float[] rightOff) + private int computeOffsetCubic(final float[] pts, final int off, + final float[] leftOff, + final float[] rightOff) { // if p1=p2 or p3=p4 it means that the derivative at the endpoint // vanishes, which creates problems with computeOffset. Usually @@ -805,7 +842,7 @@ // 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 float x1 = pts[off + 0], y1 = pts[off + 1]; + final float x1 = pts[off ], y1 = pts[off + 1]; final float x2 = pts[off + 2], y2 = pts[off + 3]; final float x3 = pts[off + 4], y3 = pts[off + 5]; final float x4 = pts[off + 6], y4 = pts[off + 7]; @@ -819,6 +856,7 @@ // in which case ignore if p1 == p2 final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0f * Math.ulp(y2)); final boolean p3eqp4 = within(x3, y3, x4, y4, 6.0f * Math.ulp(y4)); + if (p1eqp2 && p3eqp4) { getLineOffsets(x1, y1, x4, y4, leftOff, rightOff); return 4; @@ -834,6 +872,7 @@ float dotsq = (dx1 * dx4 + dy1 * dy4); dotsq *= dotsq; float l1sq = dx1 * dx1 + dy1 * dy1, l4sq = dx4 * dx4 + dy4 * dy4; + if (Helpers.within(dotsq, l1sq * l4sq, 4.0f * Math.ulp(dotsq))) { getLineOffsets(x1, y1, x4, y4, leftOff, rightOff); return 4; @@ -947,10 +986,11 @@ // compute offset curves using bezier spline through t=0.5 (i.e. // ComputedCurve(0.5) == IdealParallelCurve(0.5)) // return the kind of curve in the right and left arrays. - private int computeOffsetQuad(float[] pts, final int off, - float[] leftOff, float[] rightOff) + private int computeOffsetQuad(final float[] pts, final int off, + final float[] leftOff, + final float[] rightOff) { - final float x1 = pts[off + 0], y1 = pts[off + 1]; + final float x1 = pts[off ], y1 = pts[off + 1]; final float x2 = pts[off + 2], y2 = pts[off + 3]; final float x3 = pts[off + 4], y3 = pts[off + 5]; @@ -971,6 +1011,7 @@ // in which case ignore. final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0f * Math.ulp(y2)); final boolean p2eqp3 = within(x2, y2, x3, y3, 6.0f * Math.ulp(y3)); + if (p1eqp2 || p2eqp3) { getLineOffsets(x1, y1, x3, y3, leftOff, rightOff); return 4; @@ -980,6 +1021,7 @@ float dotsq = (dx1 * dx3 + dy1 * dy3); dotsq *= dotsq; float l1sq = dx1 * dx1 + dy1 * dy1, l3sq = dx3 * dx3 + dy3 * dy3; + if (Helpers.within(dotsq, l1sq * l3sq, 4.0f * Math.ulp(dotsq))) { getLineOffsets(x1, y1, x3, y3, leftOff, rightOff); return 4; @@ -995,151 +1037,111 @@ float y1p = y1 + offset0[1]; // point float x3p = x3 + offset1[0]; // end float y3p = y3 + offset1[1]; // point - safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, leftOff, 2); + safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, leftOff); 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); + safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, rightOff); rightOff[0] = x1p; rightOff[1] = y1p; rightOff[4] = x3p; rightOff[5] = y3p; return 6; } - // 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 Curve c, float[] pts, float[] ts, - final int type, final float w) - { - final float x12 = pts[2] - pts[0]; - final float y12 = pts[3] - pts[1]; - // if the curve is already parallel to either axis we gain nothing - // from rotating it. - if (y12 != 0.0f && x12 != 0.0f) { - // 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 float hypot = (float) Math.sqrt(x12 * x12 + y12 * y12); - final float cos = x12 / hypot; - final float sin = y12 / hypot; - final float x1 = cos * pts[0] + sin * pts[1]; - final float y1 = cos * pts[1] - sin * pts[0]; - final float x2 = cos * pts[2] + sin * pts[3]; - final float y2 = cos * pts[3] - sin * pts[2]; - final float x3 = cos * pts[4] + sin * pts[5]; - final float y3 = cos * pts[5] - sin * pts[4]; - - switch(type) { - case 8: - final float x4 = cos * pts[6] + sin * pts[7]; - final float 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.0001f); - - ret = Helpers.filterOutNotInAB(ts, 0, ret, 0.0001f, 0.9999f); - Helpers.isort(ts, 0, ret); - return ret; - } - @Override public void curveTo(final float x1, final float y1, final float x2, final float y2, final float x3, final float y3) { final int outcode0 = this.cOutCode; + if (clipRect != null) { + final int outcode1 = Helpers.outcode(x1, y1, clipRect); + final int outcode2 = Helpers.outcode(x2, y2, clipRect); final int outcode3 = Helpers.outcode(x3, y3, clipRect); - this.cOutCode = outcode3; - if ((outcode0 & outcode3) != 0) { - final int outcode1 = Helpers.outcode(x1, y1, clipRect); - final int outcode2 = Helpers.outcode(x2, y2, clipRect); - - // basic rejection criteria - if ((outcode0 & outcode1 & outcode2 & outcode3) != 0) { - moveTo(x3, y3, outcode0); + // Should clip + final int orCode = (outcode0 | outcode1 | outcode2 | outcode3); + if (orCode != 0) { + final int sideCode = outcode0 & outcode1 & outcode2 & outcode3; + + // basic rejection criteria: + if (sideCode == 0) { + // ovelap clip: + if (subdivide) { + // avoid reentrance + subdivide = false; + // subdivide curve => callback with subdivided parts: + boolean ret = curveSplitter.splitCurve(cx0, cy0, x1, y1, + x2, y2, x3, y3, + orCode, this); + // reentrance is done: + subdivide = true; + if (ret) { + return; + } + } + // already subdivided so render it + } else { + this.cOutCode = outcode3; + _moveTo(x3, y3, outcode0); opened = true; return; } } - } - final float[] 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; + this.cOutCode = outcode3; + } + _curveTo(x1, y1, x2, y2, x3, y3, outcode0); + } + private void _curveTo(final float x1, final float y1, + final float x2, final float y2, + final float x3, final float y3, + final int outcode0) + { // need these so we can update the state at the end of this method - final float xf = x3, yf = y3; - float dxs = mid[2] - mid[0]; - float dys = mid[3] - mid[1]; - float dxf = mid[6] - mid[4]; - float dyf = mid[7] - mid[5]; - - boolean p1eqp2 = (dxs == 0.0f && dys == 0.0f); - boolean p3eqp4 = (dxf == 0.0f && dyf == 0.0f); - if (p1eqp2) { - dxs = mid[4] - mid[0]; - dys = mid[5] - mid[1]; - if (dxs == 0.0f && dys == 0.0f) { - dxs = mid[6] - mid[0]; - dys = mid[7] - mid[1]; - } - } - if (p3eqp4) { - dxf = mid[6] - mid[2]; - dyf = mid[7] - mid[3]; - if (dxf == 0.0f && dyf == 0.0f) { - dxf = mid[6] - mid[0]; - dyf = mid[7] - mid[1]; + float dxs = x1 - cx0; + float dys = y1 - cy0; + float dxf = x3 - x2; + float dyf = y3 - y2; + + if ((dxs == 0.0f) && (dys == 0.0f)) { + dxs = x2 - cx0; + dys = y2 - cy0; + if ((dxs == 0.0f) && (dys == 0.0f)) { + dxs = x3 - cx0; + dys = y3 - cy0; + } + } + if ((dxf == 0.0f) && (dyf == 0.0f)) { + dxf = x3 - x1; + dyf = y3 - y1; + if ((dxf == 0.0f) && (dyf == 0.0f)) { + dxf = x3 - cx0; + dyf = y3 - cy0; } } - if (dxs == 0.0f && dys == 0.0f) { + if ((dxs == 0.0f) && (dys == 0.0f)) { // this happens if the "curve" is just a point // fix outcode0 for lineTo() call: if (clipRect != null) { this.cOutCode = outcode0; } - lineTo(mid[0], mid[1]); + lineTo(cx0, cy0); return; } // if these vectors are too small, normalize them, to avoid future // precision problems. if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) { - float len = (float) Math.sqrt(dxs*dxs + dys*dys); + final float len = (float)Math.sqrt(dxs * dxs + dys * dys); dxs /= len; dys /= len; } if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) { - float len = (float) Math.sqrt(dxf*dxf + dyf*dyf); + final float len = (float)Math.sqrt(dxf * dxf + dyf * dyf); dxf /= len; dyf /= len; } @@ -1147,17 +1149,25 @@ computeOffset(dxs, dys, lineWidth2, offset0); drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0); - final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2); + int nSplits = 0; + final float[] mid; + final float[] l = lp; - float prevT = 0.0f; - for (int i = 0, off = 0; i < nSplits; i++, off += 6) { - final float t = subdivTs[i]; - Helpers.subdivideCubicAt((t - prevT) / (1.0f - prevT), - mid, off, mid, off, mid, off + 6); - prevT = t; - } + if (monotonize) { + // monotonize curve: + final CurveBasicMonotonizer monotonizer + = rdrCtx.monotonizer.curve(cx0, cy0, x1, y1, x2, y2, x3, y3); - final float[] l = lp; + nSplits = monotonizer.nbSplits; + mid = monotonizer.middle; + } else { + // use left instead: + mid = l; + mid[0] = cx0; mid[1] = cy0; + mid[2] = x1; mid[3] = y1; + mid[4] = x2; mid[5] = y2; + mid[6] = x3; mid[7] = y3; + } final float[] r = rp; int kind = 0; @@ -1181,8 +1191,8 @@ } this.prev = DRAWING_OP_TO; - this.cx0 = xf; - this.cy0 = yf; + this.cx0 = x3; + this.cy0 = y3; this.cdx = dxf; this.cdy = dyf; this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f; @@ -1194,74 +1204,101 @@ final float x2, final float y2) { final int outcode0 = this.cOutCode; + if (clipRect != null) { + final int outcode1 = Helpers.outcode(x1, y1, clipRect); final int outcode2 = Helpers.outcode(x2, y2, clipRect); - this.cOutCode = outcode2; - - if ((outcode0 & outcode2) != 0) { - final int outcode1 = Helpers.outcode(x1, y1, clipRect); - // basic rejection criteria - if ((outcode0 & outcode1 & outcode2) != 0) { - moveTo(x2, y2, outcode0); + // Should clip + final int orCode = (outcode0 | outcode1 | outcode2); + if (orCode != 0) { + final int sideCode = outcode0 & outcode1 & outcode2; + + // basic rejection criteria: + if (sideCode == 0) { + // ovelap clip: + if (subdivide) { + // avoid reentrance + subdivide = false; + // subdivide curve => call lineTo() with subdivided curves: + boolean ret = curveSplitter.splitQuad(cx0, cy0, x1, y1, + x2, y2, orCode, this); + // reentrance is done: + subdivide = true; + if (ret) { + return; + } + } + // already subdivided so render it + } else { + this.cOutCode = outcode2; + _moveTo(x2, y2, outcode0); opened = true; return; } } - } - - final float[] mid = middle; - mid[0] = cx0; mid[1] = cy0; - mid[2] = x1; mid[3] = y1; - mid[4] = x2; mid[5] = y2; + this.cOutCode = outcode2; + } + _quadTo(x1, y1, x2, y2, outcode0); + } + private void _quadTo(final float x1, final float y1, + final float x2, final float y2, + final int outcode0) + { // need these so we can update the state at the end of this method - final float xf = x2, yf = y2; - float dxs = mid[2] - mid[0]; - float dys = mid[3] - mid[1]; - float dxf = mid[4] - mid[2]; - float dyf = mid[5] - mid[3]; - if ((dxs == 0.0f && dys == 0.0f) || (dxf == 0.0f && dyf == 0.0f)) { - dxs = dxf = mid[4] - mid[0]; - dys = dyf = mid[5] - mid[1]; + float dxs = x1 - cx0; + float dys = y1 - cy0; + float dxf = x2 - x1; + float dyf = y2 - y1; + + if (((dxs == 0.0f) && (dys == 0.0f)) || ((dxf == 0.0f) && (dyf == 0.0f))) { + dxs = dxf = x2 - cx0; + dys = dyf = y2 - cy0; } - if (dxs == 0.0f && dys == 0.0f) { + if ((dxs == 0.0f) && (dys == 0.0f)) { // this happens if the "curve" is just a point // fix outcode0 for lineTo() call: if (clipRect != null) { this.cOutCode = outcode0; } - lineTo(mid[0], mid[1]); + lineTo(cx0, cy0); return; } // if these vectors are too small, normalize them, to avoid future // precision problems. if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) { - float len = (float) Math.sqrt(dxs*dxs + dys*dys); + final float len = (float)Math.sqrt(dxs * dxs + dys * dys); dxs /= len; dys /= len; } if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) { - float len = (float) Math.sqrt(dxf*dxf + dyf*dyf); + final float len = (float)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], outcode0); - int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2); + int nSplits = 0; + final float[] mid; + final float[] l = lp; - float prevt = 0.0f; - for (int i = 0, off = 0; i < nSplits; i++, off += 4) { - final float t = subdivTs[i]; - Helpers.subdivideQuadAt((t - prevt) / (1.0f - prevt), - mid, off, mid, off, mid, off + 4); - prevt = t; - } + if (monotonize) { + // monotonize quad: + final CurveBasicMonotonizer monotonizer + = rdrCtx.monotonizer.quad(cx0, cy0, x1, y1, x2, y2); - final float[] l = lp; + nSplits = monotonizer.nbSplits; + mid = monotonizer.middle; + } else { + // use left instead: + mid = l; + mid[0] = cx0; mid[1] = cy0; + mid[2] = x1; mid[3] = y1; + mid[4] = x2; mid[5] = y2; + } final float[] r = rp; int kind = 0; @@ -1285,12 +1322,11 @@ } this.prev = DRAWING_OP_TO; - this.cx0 = xf; - this.cy0 = yf; + this.cx0 = x2; + this.cy0 = y2; this.cdx = dxf; this.cdy = dyf; this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f; this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f; } - } --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/TransformingPathConsumer2D.java 2018-06-08 16:12:54.675686469 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/TransformingPathConsumer2D.java 2018-06-08 16:12:54.515686471 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,9 +30,13 @@ import com.sun.javafx.geom.transform.BaseTransform; import com.sun.marlin.Helpers.IndexStack; import com.sun.marlin.Helpers.PolyStack; +import java.util.Arrays; public final class TransformingPathConsumer2D { + // higher uncertainty in float variant for huge shapes > 10^7 + static final float CLIP_RECT_PADDING = 1.0f; + private final RendererContext rdrCtx; // recycled ClosedPathDetector instance from detectClosedPath() @@ -57,6 +61,7 @@ private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector"); private final PathTracer tracerFiller = new PathTracer("Filler"); private final PathTracer tracerStroker = new PathTracer("Stroker"); + private final PathTracer tracerDasher = new PathTracer("Dasher"); TransformingPathConsumer2D(final RendererContext rdrCtx) { // used by RendererContext @@ -85,6 +90,10 @@ return tracerStroker.init(out); } + public PathConsumer2D traceDasher(PathConsumer2D out) { + return tracerDasher.init(out); + } + public PathConsumer2D detectClosedPath(PathConsumer2D out) { return cpDetector.init(out); } @@ -486,11 +495,19 @@ private boolean outside = false; - // The current point OUTSIDE + // The current point (TODO stupid repeated info) private float cx0, cy0; + // The current point OUTSIDE + private float cox0, coy0; + + private boolean subdivide = MarlinConst.DO_CLIP_SUBDIVIDER; + private final CurveClipSplitter curveSplitter; + PathClipFilter(final RendererContext rdrCtx) { this.clipRect = rdrCtx.clipRect; + this.curveSplitter = rdrCtx.curveClipSplitter; + this.stack = (rdrCtx.stats != null) ? new IndexStack(rdrCtx, rdrCtx.stats.stat_pcf_idxstack_indices, @@ -515,6 +532,11 @@ _clipRect[2] -= margin - rdrOffX; _clipRect[3] += margin + rdrOffX; + if (MarlinConst.DO_CLIP_SUBDIVIDER) { + // adjust padded clip rectangle: + curveSplitter.init(); + } + this.init_corners = true; this.gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R; @@ -565,7 +587,9 @@ } stack.pullAll(corners, out); } - out.lineTo(cx0, cy0); + out.lineTo(cox0, coy0); + this.cx0 = cox0; + this.cy0 = coy0; } @Override @@ -590,38 +614,68 @@ public void moveTo(final float x0, final float y0) { finishPath(); - final int outcode = Helpers.outcode(x0, y0, clipRect); - this.cOutCode = outcode; + this.cOutCode = Helpers.outcode(x0, y0, clipRect); this.outside = false; out.moveTo(x0, y0); + this.cx0 = x0; + this.cy0 = y0; } @Override public void lineTo(final float xe, final float ye) { final int outcode0 = this.cOutCode; final int outcode1 = Helpers.outcode(xe, ye, clipRect); - this.cOutCode = outcode1; - final int sideCode = (outcode0 & outcode1); + // Should clip + final int orCode = (outcode0 | outcode1); + if (orCode != 0) { + final int sideCode = (outcode0 & outcode1); - // basic rejection criteria: - if (sideCode == 0) { - this.gOutCode = 0; - } else { - this.gOutCode &= sideCode; - // keep last point coordinate before entering the clip again: - this.outside = true; - this.cx0 = xe; - this.cy0 = ye; + // basic rejection criteria: + if (sideCode == 0) { + // ovelap clip: + if (subdivide) { + // avoid reentrance + subdivide = false; + boolean ret; + // subdivide curve => callback with subdivided parts: + if (outside) { + ret = curveSplitter.splitLine(cox0, coy0, xe, ye, + orCode, this); + } else { + ret = curveSplitter.splitLine(cx0, cy0, xe, ye, + orCode, this); + } + // reentrance is done: + subdivide = true; + if (ret) { + return; + } + } + // already subdivided so render it + } else { + this.cOutCode = outcode1; + this.gOutCode &= sideCode; + // keep last point coordinate before entering the clip again: + this.outside = true; + this.cox0 = xe; + this.coy0 = ye; - clip(sideCode, outcode0, outcode1); - return; + clip(sideCode, outcode0, outcode1); + return; + } } + + this.cOutCode = outcode1; + this.gOutCode = 0; + if (outside) { finish(); } // clipping disabled: out.lineTo(xe, ye); + this.cx0 = xe; + this.cy0 = ye; } private void clip(final int sideCode, @@ -641,22 +695,18 @@ // add corners to outside stack: switch (tbCode) { case MarlinConst.OUTCODE_TOP: -// System.out.println("TOP "+ ((off == 0) ? "LEFT" : "RIGHT")); stack.push(off); // top return; case MarlinConst.OUTCODE_BOTTOM: -// System.out.println("BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT")); stack.push(off + 1); // bottom return; default: // both TOP / BOTTOM: if ((outcode0 & MarlinConst.OUTCODE_TOP) != 0) { -// System.out.println("TOP + BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT")); // top to bottom stack.push(off); // top stack.push(off + 1); // bottom } else { -// System.out.println("BOTTOM + TOP "+ ((off == 0) ? "LEFT" : "RIGHT")); // bottom to top stack.push(off + 1); // bottom stack.push(off); // top @@ -671,34 +721,62 @@ final float xe, final float ye) { final int outcode0 = this.cOutCode; + final int outcode1 = Helpers.outcode(x1, y1, clipRect); + final int outcode2 = Helpers.outcode(x2, y2, clipRect); final int outcode3 = Helpers.outcode(xe, ye, clipRect); - this.cOutCode = outcode3; - - int sideCode = outcode0 & outcode3; - if (sideCode == 0) { - this.gOutCode = 0; - } else { - sideCode &= Helpers.outcode(x1, y1, clipRect); - sideCode &= Helpers.outcode(x2, y2, clipRect); - this.gOutCode &= sideCode; + // Should clip + final int orCode = (outcode0 | outcode1 | outcode2 | outcode3); + if (orCode != 0) { + final int sideCode = outcode0 & outcode1 & outcode2 & outcode3; // basic rejection criteria: - if (sideCode != 0) { + if (sideCode == 0) { + // ovelap clip: + if (subdivide) { + // avoid reentrance + subdivide = false; + // subdivide curve => callback with subdivided parts: + boolean ret; + if (outside) { + ret = curveSplitter.splitCurve(cox0, coy0, x1, y1, + x2, y2, xe, ye, + orCode, this); + } else { + ret = curveSplitter.splitCurve(cx0, cy0, x1, y1, + x2, y2, xe, ye, + orCode, this); + } + // reentrance is done: + subdivide = true; + if (ret) { + return; + } + } + // already subdivided so render it + } else { + this.cOutCode = outcode3; + this.gOutCode &= sideCode; // keep last point coordinate before entering the clip again: this.outside = true; - this.cx0 = xe; - this.cy0 = ye; + this.cox0 = xe; + this.coy0 = ye; clip(sideCode, outcode0, outcode3); return; } } + + this.cOutCode = outcode3; + this.gOutCode = 0; + if (outside) { finish(); } // clipping disabled: out.curveTo(x1, y1, x2, y2, xe, ye); + this.cx0 = xe; + this.cy0 = ye; } @Override @@ -706,33 +784,314 @@ final float xe, final float ye) { final int outcode0 = this.cOutCode; + final int outcode1 = Helpers.outcode(x1, y1, clipRect); final int outcode2 = Helpers.outcode(xe, ye, clipRect); - this.cOutCode = outcode2; - - int sideCode = outcode0 & outcode2; - if (sideCode == 0) { - this.gOutCode = 0; - } else { - sideCode &= Helpers.outcode(x1, y1, clipRect); - this.gOutCode &= sideCode; + // Should clip + final int orCode = (outcode0 | outcode1 | outcode2); + if (orCode != 0) { + final int sideCode = outcode0 & outcode1 & outcode2; // basic rejection criteria: - if (sideCode != 0) { + if (sideCode == 0) { + // ovelap clip: + if (subdivide) { + // avoid reentrance + subdivide = false; + // subdivide curve => callback with subdivided parts: + boolean ret; + if (outside) { + ret = curveSplitter.splitQuad(cox0, coy0, x1, y1, + xe, ye, orCode, this); + } else { + ret = curveSplitter.splitQuad(cx0, cy0, x1, y1, + xe, ye, orCode, this); + } + // reentrance is done: + subdivide = true; + if (ret) { + return; + } + } + // already subdivided so render it + } else { + this.cOutCode = outcode2; + this.gOutCode &= sideCode; // keep last point coordinate before entering the clip again: this.outside = true; - this.cx0 = xe; - this.cy0 = ye; + this.cox0 = xe; + this.coy0 = ye; clip(sideCode, outcode0, outcode2); return; } } + + this.cOutCode = outcode2; + this.gOutCode = 0; + if (outside) { finish(); } // clipping disabled: out.quadTo(x1, y1, xe, ye); + this.cx0 = xe; + this.cy0 = ye; + } + } + + static final class CurveClipSplitter { + + static final float LEN_TH = MarlinProperties.getSubdividerMinLength(); + static final boolean DO_CHECK_LENGTH = (LEN_TH > 0.0f); + + private static final boolean TRACE = false; + + private static final int MAX_N_CURVES = 3 * 4; + + // clip rectangle (ymin, ymax, xmin, xmax): + final float[] clipRect; + + // clip rectangle (ymin, ymax, xmin, xmax) including padding: + final float[] clipRectPad = new float[4]; + private boolean init_clipRectPad = false; + + // This is where the curve to be processed is put. We give it + // enough room to store all curves. + final float[] middle = new float[MAX_N_CURVES * 8 + 2]; + // t values at subdivision points + private final float[] subdivTs = new float[MAX_N_CURVES]; + + // dirty curve + private final Curve curve; + + CurveClipSplitter(final RendererContext rdrCtx) { + this.clipRect = rdrCtx.clipRect; + this.curve = rdrCtx.curve; + } + + void init() { + this.init_clipRectPad = true; + } + + private void initPaddedClip() { + // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY + // adjust padded clip rectangle (ymin, ymax, xmin, xmax): + // add a rounding error (curve subdivision ~ 0.1px): + final float[] _clipRect = clipRect; + final float[] _clipRectPad = clipRectPad; + + _clipRectPad[0] = _clipRect[0] - CLIP_RECT_PADDING; + _clipRectPad[1] = _clipRect[1] + CLIP_RECT_PADDING; + _clipRectPad[2] = _clipRect[2] - CLIP_RECT_PADDING; + _clipRectPad[3] = _clipRect[3] + CLIP_RECT_PADDING; + + if (TRACE) { + MarlinUtils.logInfo("clip: X [" + _clipRectPad[2] + " .. " + _clipRectPad[3] +"] " + + "Y ["+ _clipRectPad[0] + " .. " + _clipRectPad[1] +"]"); + } + } + + boolean splitLine(final float x0, final float y0, + final float x1, final float y1, + final int outCodeOR, + final PathConsumer2D out) + { + if (TRACE) { + MarlinUtils.logInfo("divLine P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ")"); + } + + if (DO_CHECK_LENGTH && Helpers.fastLineLen(x0, y0, x1, y1) <= LEN_TH) { + return false; + } + + final float[] mid = middle; + mid[0] = x0; mid[1] = y0; + mid[2] = x1; mid[3] = y1; + + return subdivideAtIntersections(4, outCodeOR, out); + } + + boolean splitQuad(final float x0, final float y0, + final float x1, final float y1, + final float x2, final float y2, + final int outCodeOR, + final PathConsumer2D out) + { + if (TRACE) { + MarlinUtils.logInfo("divQuad P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ")"); + } + + if (DO_CHECK_LENGTH && Helpers.fastQuadLen(x0, y0, x1, y1, x2, y2) <= LEN_TH) { + return false; + } + + final float[] mid = middle; + mid[0] = x0; mid[1] = y0; + mid[2] = x1; mid[3] = y1; + mid[4] = x2; mid[5] = y2; + + return subdivideAtIntersections(6, outCodeOR, out); + } + + boolean splitCurve(final float x0, final float y0, + final float x1, final float y1, + final float x2, final float y2, + final float x3, final float y3, + final int outCodeOR, + final PathConsumer2D out) + { + if (TRACE) { + MarlinUtils.logInfo("divCurve P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ")"); + } + + if (DO_CHECK_LENGTH && Helpers.fastCurvelen(x0, y0, x1, y1, x2, y2, x3, y3) <= LEN_TH) { + return false; + } + + final float[] mid = middle; + mid[0] = x0; mid[1] = y0; + mid[2] = x1; mid[3] = y1; + mid[4] = x2; mid[5] = y2; + mid[6] = x3; mid[7] = y3; + + return subdivideAtIntersections(8, outCodeOR, out); + } + + private boolean subdivideAtIntersections(final int type, final int outCodeOR, + final PathConsumer2D out) + { + final float[] mid = middle; + final float[] subTs = subdivTs; + + if (init_clipRectPad) { + init_clipRectPad = false; + initPaddedClip(); + } + + final int nSplits = Helpers.findClipPoints(curve, mid, subTs, type, + outCodeOR, clipRectPad); + + if (TRACE) { + MarlinUtils.logInfo("nSplits: "+ nSplits); + MarlinUtils.logInfo("subTs: " + Arrays.toString(Arrays.copyOfRange(subTs, 0, nSplits))); + } + if (nSplits == 0) { + // only curve support shortcut + return false; + } + float prevT = 0.0f; + + for (int i = 0, off = 0; i < nSplits; i++, off += type) { + final float t = subTs[i]; + + Helpers.subdivideAt((t - prevT) / (1.0f - prevT), + mid, off, mid, off, type); + prevT = t; + } + + for (int i = 0, off = 0; i <= nSplits; i++, off += type) { + if (TRACE) { + MarlinUtils.logInfo("Part Curve " + Arrays.toString(Arrays.copyOfRange(mid, off, off + type))); + } + emitCurrent(type, mid, off, out); + } + return true; + } + + static void emitCurrent(final int type, final float[] pts, + final int off, final PathConsumer2D out) + { + // if instead of switch (perf + most probable cases first) + if (type == 8) { + out.curveTo(pts[off + 2], pts[off + 3], + pts[off + 4], pts[off + 5], + pts[off + 6], pts[off + 7]); + } else if (type == 4) { + out.lineTo(pts[off + 2], pts[off + 3]); + } else { + out.quadTo(pts[off + 2], pts[off + 3], + pts[off + 4], pts[off + 5]); + } + } + } + + public static final class CurveBasicMonotonizer { + + private static final int MAX_N_CURVES = 11; + + // squared half line width (for stroker) + private float lw2; + + // number of splitted curves + int nbSplits; + + // This is where the curve to be processed is put. We give it + // enough room to store all curves. + final float[] middle = new float[MAX_N_CURVES * 6 + 2]; + // t values at subdivision points + private final float[] subdivTs = new float[MAX_N_CURVES - 1]; + + // dirty curve + private final Curve curve; + + CurveBasicMonotonizer(final RendererContext rdrCtx) { + this.curve = rdrCtx.curve; + } + + public void init(final float lineWidth) { + this.lw2 = (lineWidth * lineWidth) / 4.0f; + } + + CurveBasicMonotonizer curve(final float x0, final float y0, + final float x1, final float y1, + final float x2, final float y2, + final float x3, final float y3) + { + final float[] mid = middle; + mid[0] = x0; mid[1] = y0; + mid[2] = x1; mid[3] = y1; + mid[4] = x2; mid[5] = y2; + mid[6] = x3; mid[7] = y3; + + final float[] subTs = subdivTs; + final int nSplits = Helpers.findSubdivPoints(curve, mid, subTs, 8, lw2); + + float prevT = 0.0f; + for (int i = 0, off = 0; i < nSplits; i++, off += 6) { + final float t = subTs[i]; + + Helpers.subdivideCubicAt((t - prevT) / (1.0f - prevT), + mid, off, mid, off, off + 6); + prevT = t; + } + + this.nbSplits = nSplits; + return this; + } + + CurveBasicMonotonizer quad(final float x0, final float y0, + final float x1, final float y1, + final float x2, final float y2) + { + final float[] mid = middle; + mid[0] = x0; mid[1] = y0; + mid[2] = x1; mid[3] = y1; + mid[4] = x2; mid[5] = y2; + + final float[] subTs = subdivTs; + final int nSplits = Helpers.findSubdivPoints(curve, mid, subTs, 6, lw2); + + float prevt = 0.0f; + for (int i = 0, off = 0; i < nSplits; i++, off += 4) { + final float t = subTs[i]; + Helpers.subdivideQuadAt((t - prevt) / (1.0f - prevt), + mid, off, mid, off, off + 4); + prevt = t; + } + + this.nbSplits = nSplits; + return this; } } @@ -789,7 +1148,7 @@ } private void log(final String message) { - System.out.println(prefix + message); + MarlinUtils.logInfo(prefix + message); } } } --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/Version.java 2018-06-08 16:12:55.135686462 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/Version.java 2018-06-08 16:12:54.971686464 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,7 +27,7 @@ public final class Version { - private static final String VERSION = "marlinFX-0.8.2-Unsafe-OpenJDK"; + private static final String VERSION = "marlinFX-0.9.2-Unsafe-OpenJDK"; public static String getVersion() { return VERSION; --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/stats/Histogram.java 2018-06-08 16:12:55.583686456 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/stats/Histogram.java 2018-06-08 16:12:55.419686458 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -42,7 +42,6 @@ for (int i = 2; i < MAX; i++) { STEPS[i] = STEPS[i - 1] * BUCKET; } -// System.out.println("Histogram.STEPS = " + Arrays.toString(STEPS)); } static int bucket(int val) { --- old/modules/javafx.graphics/src/main/java/com/sun/marlin/stats/StatLong.java 2018-06-08 16:12:55.915686451 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/stats/StatLong.java 2018-06-08 16:12:55.755686454 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -71,9 +71,7 @@ @Override public String toString() { - final StringBuilder sb = new StringBuilder(128); - toString(sb); - return sb.toString(); + return toString(new StringBuilder(128)).toString(); } public final StringBuilder toString(final StringBuilder sb) { --- old/modules/javafx.graphics/src/main/java/com/sun/prism/impl/shape/DMarlinPrismUtils.java 2018-06-08 16:12:56.243686447 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/prism/impl/shape/DMarlinPrismUtils.java 2018-06-08 16:12:56.083686449 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -44,31 +44,29 @@ 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; - - static final boolean DO_CLIP = MarlinProperties.isDoClip(); - static final boolean DO_CLIP_FILL = true; + // slightly slower ~2% if enabled stroker clipping (lines) but skipping cap / join handling is few percents faster in specific cases + static final boolean DISABLE_2ND_STROKER_CLIPPING = true; static final boolean DO_TRACE_PATH = false; + static final boolean DO_CLIP = MarlinProperties.isDoClip(); + static final boolean DO_CLIP_FILL = true; static final boolean DO_CLIP_RUNTIME_ENABLE = MarlinProperties.isDoClipRuntimeFlag(); + 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 boolean nearZero(final double num) { - return Math.abs(num) < 2.0d * Math.ulp(num); - } - private static DPathConsumer2D initStroker( final DRendererContext rdrCtx, final BasicStroke stroke, final float lineWidth, - final BaseTransform tx, + BaseTransform tx, final DPathConsumer2D out) { // We use strokerat so that in Stroker and Dasher we can work only @@ -143,6 +141,10 @@ // input will be violated. After all this, A will be applied // to stroker's output. } + } else { + // either tx is null or it's the identity. In either case + // we don't transform the path. + tx = null; } // Get renderer offsets: @@ -176,10 +178,23 @@ // stroker will adjust the clip rectangle (width / miter limit): pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(), stroke.getLineJoin(), stroke.getMiterLimit(), - scale, rdrOffX, rdrOffY); + scale, rdrOffX, rdrOffY, (dashesD == null)); + + // Curve Monotizer: + rdrCtx.monotonizer.init(width); if (dashesD != null) { - pc = rdrCtx.dasher.init(pc, dashesD, dashLen, dashphase, recycleDashes); + if (DO_TRACE_PATH) { + pc = transformerPC2D.traceDasher(pc); + } + pc = rdrCtx.dasher.init(pc, dashesD, dashLen, dashphase, + recycleDashes); + + if (DISABLE_2ND_STROKER_CLIPPING) { + // disable stoker clipping: + rdrCtx.stroker.disableClipping(); + } + } else if (rdrCtx.doClip && (stroke.getEndCap() != DStroker.CAP_BUTT)) { if (DO_TRACE_PATH) { pc = transformerPC2D.traceClosedPathDetector(pc); @@ -210,6 +225,10 @@ return pc; } + private static boolean nearZero(final double num) { + return Math.abs(num) < 2.0d * Math.ulp(num); + } + private static DPathConsumer2D initRenderer( final DRendererContext rdrCtx, final BasicStroke stroke, @@ -309,8 +328,14 @@ } private static void feedConsumer(final DRendererContext rdrCtx, final PathIterator pi, - final DPathConsumer2D pc2d) + DPathConsumer2D pc2d) { + if (MarlinConst.USE_PATH_SIMPLIFIER) { + // Use path simplifier at the first step + // to remove useless points + pc2d = rdrCtx.pathSimplifier.init(pc2d); + } + // mark context as DIRTY: rdrCtx.dirty = true; @@ -430,8 +455,14 @@ private static void feedConsumer(final DRendererContext rdrCtx, final Path2D p2d, final BaseTransform xform, - final DPathConsumer2D pc2d) + DPathConsumer2D pc2d) { + if (MarlinConst.USE_PATH_SIMPLIFIER) { + // Use path simplifier at the first step + // to remove useless points + pc2d = rdrCtx.pathSimplifier.init(pc2d); + } + // mark context as DIRTY: rdrCtx.dirty = true; --- old/modules/javafx.graphics/src/main/java/com/sun/prism/impl/shape/MarlinPrismUtils.java 2018-06-08 16:12:56.579686442 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/prism/impl/shape/MarlinPrismUtils.java 2018-06-08 16:12:56.419686444 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -44,31 +44,29 @@ 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; - - static final boolean DO_CLIP = MarlinProperties.isDoClip(); - static final boolean DO_CLIP_FILL = true; + // slightly slower ~2% if enabled stroker clipping (lines) but skipping cap / join handling is few percents faster in specific cases + static final boolean DISABLE_2ND_STROKER_CLIPPING = true; static final boolean DO_TRACE_PATH = false; + static final boolean DO_CLIP = MarlinProperties.isDoClip(); + static final boolean DO_CLIP_FILL = true; static final boolean DO_CLIP_RUNTIME_ENABLE = MarlinProperties.isDoClipRuntimeFlag(); + static final float UPPER_BND = Float.MAX_VALUE / 2.0f; + static final float LOWER_BND = -UPPER_BND; + /** * Private constructor to prevent instantiation. */ private MarlinPrismUtils() { } - private static boolean nearZero(final double num) { - return Math.abs(num) < 2.0d * Math.ulp(num); - } - private static PathConsumer2D initStroker( final RendererContext rdrCtx, final BasicStroke stroke, final float lineWidth, - final BaseTransform tx, + BaseTransform tx, final PathConsumer2D out) { // We use strokerat so that in Stroker and Dasher we can work only @@ -138,6 +136,10 @@ // input will be violated. After all this, A will be applied // to stroker's output. } + } else { + // either tx is null or it's the identity. In either case + // we don't transform the path. + tx = null; } // Get renderer offsets: @@ -171,13 +173,26 @@ // stroker will adjust the clip rectangle (width / miter limit): pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(), stroke.getLineJoin(), stroke.getMiterLimit(), - scale, rdrOffX, rdrOffY); + scale, rdrOffX, rdrOffY, (dashes == null)); + + // Curve Monotizer: + rdrCtx.monotonizer.init(width); if (dashes != null) { if (!recycleDashes) { dashLen = dashes.length; } - pc = rdrCtx.dasher.init(pc, dashes, dashLen, dashphase, recycleDashes); + if (DO_TRACE_PATH) { + pc = transformerPC2D.traceDasher(pc); + } + pc = rdrCtx.dasher.init(pc, dashes, dashLen, dashphase, + recycleDashes); + + if (DISABLE_2ND_STROKER_CLIPPING) { + // disable stoker clipping: + rdrCtx.stroker.disableClipping(); + } + } else if (rdrCtx.doClip && (stroke.getEndCap() != Stroker.CAP_BUTT)) { if (DO_TRACE_PATH) { pc = transformerPC2D.traceClosedPathDetector(pc); @@ -208,6 +223,10 @@ return pc; } + private static boolean nearZero(final double num) { + return Math.abs(num) < 2.0d * Math.ulp(num); + } + private static PathConsumer2D initRenderer( final RendererContext rdrCtx, final BasicStroke stroke, @@ -307,8 +326,14 @@ } private static void feedConsumer(final RendererContext rdrCtx, final PathIterator pi, - final PathConsumer2D pc2d) + PathConsumer2D pc2d) { + if (MarlinConst.USE_PATH_SIMPLIFIER) { + // Use path simplifier at the first step + // to remove useless points + pc2d = rdrCtx.pathSimplifier.init(pc2d); + } + // mark context as DIRTY: rdrCtx.dirty = true; @@ -428,8 +453,14 @@ private static void feedConsumer(final RendererContext rdrCtx, final Path2D p2d, final BaseTransform xform, - final PathConsumer2D pc2d) + PathConsumer2D pc2d) { + if (MarlinConst.USE_PATH_SIMPLIFIER) { + // Use path simplifier at the first step + // to remove useless points + pc2d = rdrCtx.pathSimplifier.init(pc2d); + } + // mark context as DIRTY: rdrCtx.dirty = true; --- /dev/null 2018-06-08 10:15:09.479983295 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/DPathSimplifier.java 2018-06-08 16:12:56.751686440 +0200 @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * 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 DPathSimplifier implements DPathConsumer2D { + + // distance threshold in pixels (device) + private static final double PIX_THRESHOLD = MarlinProperties.getPathSimplifierPixelTolerance(); + // squared tolerance in pixels + private static final double SQUARE_TOLERANCE = PIX_THRESHOLD * PIX_THRESHOLD; + + // members: + private DPathConsumer2D delegate; + // current reference point + private double cx, cy; + // flag indicating if the given point was skipped + private boolean skipped; + // last skipped point + private double sx, sy; + + DPathSimplifier() { + } + + public DPathSimplifier init(final DPathConsumer2D delegate) { + this.delegate = delegate; + skipped = false; + return this; // fluent API + } + + private void finishPath() { + if (skipped) { + _lineTo(sx, sy); + } + } + + @Override + public void pathDone() { + finishPath(); + delegate.pathDone(); + } + + @Override + public void closePath() { + finishPath(); + delegate.closePath(); + } + + @Override + public void moveTo(final double xe, final double ye) { + finishPath(); + delegate.moveTo(xe, ye); + cx = xe; + cy = ye; + } + + @Override + public void lineTo(final double xe, final double ye) { + // Test if segment is too small: + double dx = (xe - cx); + double dy = (ye - cy); + + if ((dx * dx + dy * dy) <= SQUARE_TOLERANCE) { + skipped = true; + sx = xe; + sy = ye; + return; + } + _lineTo(xe, ye); + } + + private void _lineTo(final double xe, final double ye) { + delegate.lineTo(xe, ye); + cx = xe; + cy = ye; + skipped = false; + } + + @Override + public void quadTo(final double x1, final double y1, + final double xe, final double ye) + { + // Test if curve is too small: + double dx = (xe - cx); + double dy = (ye - cy); + + if ((dx * dx + dy * dy) <= SQUARE_TOLERANCE) { + // check control points P1: + dx = (x1 - cx); + dy = (y1 - cy); + + if ((dx * dx + dy * dy) <= SQUARE_TOLERANCE) { + skipped = true; + sx = xe; + sy = ye; + return; + } + } + delegate.quadTo(x1, y1, xe, ye); + cx = xe; + cy = ye; + skipped = false; + } + + @Override + public void curveTo(final double x1, final double y1, + final double x2, final double y2, + final double xe, final double ye) + { + // Test if curve is too small: + double dx = (xe - cx); + double dy = (ye - cy); + + if ((dx * dx + dy * dy) <= SQUARE_TOLERANCE) { + // check control points P1: + dx = (x1 - cx); + dy = (y1 - cy); + + if ((dx * dx + dy * dy) <= SQUARE_TOLERANCE) { + // check control points P2: + dx = (x2 - cx); + dy = (y2 - cy); + + if ((dx * dx + dy * dy) <= SQUARE_TOLERANCE) { + skipped = true; + sx = xe; + sy = ye; + return; + } + } + } + delegate.curveTo(x1, y1, x2, y2, xe, ye); + cx = xe; + cy = ye; + skipped = false; + } +} --- /dev/null 2018-06-08 10:15:09.479983295 +0200 +++ new/modules/javafx.graphics/src/main/java/com/sun/marlin/PathSimplifier.java 2018-06-08 16:12:57.083686435 +0200 @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * 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.PathConsumer2D; + +public final class PathSimplifier implements PathConsumer2D { + + // distance threshold in pixels (device) + private static final float PIX_THRESHOLD = MarlinProperties.getPathSimplifierPixelTolerance(); + // squared tolerance in pixels + private static final float SQUARE_TOLERANCE = PIX_THRESHOLD * PIX_THRESHOLD; + + // members: + private PathConsumer2D delegate; + // current reference point + private float cx, cy; + // flag indicating if the given point was skipped + private boolean skipped; + // last skipped point + private float sx, sy; + + PathSimplifier() { + } + + public PathSimplifier init(final PathConsumer2D delegate) { + this.delegate = delegate; + skipped = false; + return this; // fluent API + } + + private void finishPath() { + if (skipped) { + _lineTo(sx, sy); + } + } + + @Override + public void pathDone() { + finishPath(); + delegate.pathDone(); + } + + @Override + public void closePath() { + finishPath(); + delegate.closePath(); + } + + @Override + public void moveTo(final float xe, final float ye) { + finishPath(); + delegate.moveTo(xe, ye); + cx = xe; + cy = ye; + } + + @Override + public void lineTo(final float xe, final float ye) { + // Test if segment is too small: + float dx = (xe - cx); + float dy = (ye - cy); + + if ((dx * dx + dy * dy) <= SQUARE_TOLERANCE) { + skipped = true; + sx = xe; + sy = ye; + return; + } + _lineTo(xe, ye); + } + + private void _lineTo(final float xe, final float ye) { + delegate.lineTo(xe, ye); + cx = xe; + cy = ye; + skipped = false; + } + + @Override + public void quadTo(final float x1, final float y1, + final float xe, final float ye) + { + // Test if curve is too small: + float dx = (xe - cx); + float dy = (ye - cy); + + if ((dx * dx + dy * dy) <= SQUARE_TOLERANCE) { + // check control points P1: + dx = (x1 - cx); + dy = (y1 - cy); + + if ((dx * dx + dy * dy) <= SQUARE_TOLERANCE) { + skipped = true; + sx = xe; + sy = ye; + return; + } + } + delegate.quadTo(x1, y1, xe, ye); + cx = xe; + cy = ye; + skipped = false; + } + + @Override + public void curveTo(final float x1, final float y1, + final float x2, final float y2, + final float xe, final float ye) + { + // Test if curve is too small: + float dx = (xe - cx); + float dy = (ye - cy); + + if ((dx * dx + dy * dy) <= SQUARE_TOLERANCE) { + // check control points P1: + dx = (x1 - cx); + dy = (y1 - cy); + + if ((dx * dx + dy * dy) <= SQUARE_TOLERANCE) { + // check control points P2: + dx = (x2 - cx); + dy = (y2 - cy); + + if ((dx * dx + dy * dy) <= SQUARE_TOLERANCE) { + skipped = true; + sx = xe; + sy = ye; + return; + } + } + } + delegate.curveTo(x1, y1, x2, y2, xe, ye); + cx = xe; + cy = ye; + skipped = false; + } +} --- /dev/null 2018-06-08 10:15:09.479983295 +0200 +++ new/tests/system/src/test/java/test/com/sun/marlin/ClipShapeTest.java 2018-06-08 16:12:57.423686431 +0200 @@ -0,0 +1,1307 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * 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 test.com.sun.marlin; + +import java.awt.BasicStroke; +import java.awt.Shape; +import java.awt.geom.Path2D; +import java.awt.geom.PathIterator; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Locale; +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Handler; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +import javafx.application.Application; +import javafx.application.Platform; +import javafx.collections.ObservableList; +import javafx.embed.swing.SwingFXUtils; +import javafx.geometry.Rectangle2D; +import javafx.scene.Scene; +import javafx.scene.SnapshotParameters; +import javafx.scene.image.PixelReader; +import javafx.scene.image.PixelWriter; +import javafx.scene.image.WritableImage; +import javafx.scene.layout.BorderPane; +import javafx.scene.paint.Color; +import javafx.scene.shape.ClosePath; +import javafx.scene.shape.CubicCurveTo; +import javafx.scene.shape.FillRule; +import javafx.scene.shape.LineTo; +import javafx.scene.shape.MoveTo; +import javafx.scene.shape.Path; +import javafx.scene.shape.PathElement; +import javafx.scene.shape.QuadCurveTo; +import javafx.scene.shape.StrokeLineCap; +import javafx.scene.shape.StrokeLineJoin; +import javafx.scene.text.Text; +import javafx.stage.Stage; +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.stream.ImageOutputStream; + +import junit.framework.AssertionFailedError; +import org.junit.AfterClass; +import org.junit.Assert; +import static org.junit.Assert.assertEquals; +import org.junit.BeforeClass; +import org.junit.Test; + +import static test.util.Util.TIMEOUT; + +/** + * @test + * @bug + * @summary Verifies that Marlin rendering generates the same + * images with and without clipping optimization with all possible + * stroke (cap/join) and/or dashes or fill modes (EO rules) + * for paths made of either 9 lines, 4 quads, 2 cubics (random) + */ +public final class ClipShapeTest { + + // test options: + static int NUM_TESTS; + + // shape settings: + static ShapeMode SHAPE_MODE; + + static boolean USE_DASHES; + static boolean USE_VAR_STROKE; + + static int THRESHOLD_DELTA; + static long THRESHOLD_NBPIX; + + // constants: + static final boolean DO_FAIL = true; + + static final boolean TEST_STROKER = true; + static final boolean TEST_FILLER = true; + + static final int TESTW = 100; + static final int TESTH = 100; + + static final boolean SHAPE_REPEAT = true; + + // dump path on console: + static final boolean DUMP_SHAPE = true; + + static final int MAX_SHOW_FRAMES = 10; + static final int MAX_SAVE_FRAMES = 100; + + // use fixed seed to reproduce always same polygons between tests + static final boolean FIXED_SEED = true; + + static final double RAND_SCALE = 3.0; + static final double RANDW = TESTW * RAND_SCALE; + static final double OFFW = (TESTW - RANDW) / 2.0; + static final double RANDH = TESTH * RAND_SCALE; + static final double OFFH = (TESTH - RANDH) / 2.0; + + static enum ShapeMode { + TWO_CUBICS, + FOUR_QUADS, + FIVE_LINE_POLYS, + NINE_LINE_POLYS, + FIFTY_LINE_POLYS, + MIXED + } + + static final long SEED = 1666133789L; + // Fixed seed to avoid any difference between runs: + static final Random RANDOM = new Random(SEED); + + static final File OUTPUT_DIR = new File("."); + + static final AtomicBoolean isMarlin = new AtomicBoolean(); + static final AtomicBoolean isClipRuntime = new AtomicBoolean(); + + // Used to launch the application before running any test + private static final CountDownLatch launchLatch = new CountDownLatch(1); + + // Singleton Application instance + static MyApp myApp; + + static boolean doChecksFailed = false; + + private static final Logger log; + + static { + Locale.setDefault(Locale.US); + + // FIRST: Get Marlin runtime state from its log: + + // initialize j.u.l Looger: + log = Logger.getLogger("prism.marlin"); + log.addHandler(new Handler() { + @Override + public void publish(LogRecord record) { + final String msg = record.getMessage(); + if (msg != null) { + // last space to avoid matching other settings: + if (msg.startsWith("prism.marlin ")) { + isMarlin.set(msg.contains("DRenderer")); + } + if (msg.startsWith("prism.marlin.clip.runtime.enable")) { + isClipRuntime.set(msg.contains("true")); + } + } + + final Throwable th = record.getThrown(); + // detect any Throwable: + if (th != null) { + System.out.println("Test failed:\n" + record.getMessage()); + th.printStackTrace(System.out); + + doChecksFailed = true; + + throw new RuntimeException("Test failed: ", th); + } + } + + @Override + public void flush() { + } + + @Override + public void close() throws SecurityException { + } + }); + + // enable Marlin logging & internal checks: + System.setProperty("prism.marlin.log", "true"); + System.setProperty("prism.marlin.useLogger", "true"); + + // disable static clipping setting: + System.setProperty("prism.marlin.clip", "false"); + System.setProperty("prism.marlin.clip.runtime.enable", "true"); + + // enable subdivider: + System.setProperty("prism.marlin.clip.subdivider", "true"); + + // disable min length check: always subdivide curves at clip edges + System.setProperty("prism.marlin.clip.subdivider.minLength", "-1"); + + // If any curve, increase curve accuracy: + // curve length max error: + System.setProperty("prism.marlin.curve_len_err", "1e-4"); + + // cubic min/max error: + System.setProperty("prism.marlin.cubic_dec_d2", "1e-3"); + System.setProperty("prism.marlin.cubic_inc_d1", "1e-4"); // or disabled ~ 1e-6 + + // quad max error: + System.setProperty("prism.marlin.quad_dec_d2", "5e-4"); + + System.setProperty("javafx.animation.fullspeed", "true"); // full speed + } + + // Application class. An instance is created and initialized before running + // the first test, and it lives through the execution of all tests. + public static class MyApp extends Application { + + Stage stage = null; + + public MyApp() { + super(); + } + + @Override + public void init() { + ClipShapeTest.myApp = this; + } + + @Override + public void start(Stage primaryStage) throws Exception { + this.stage = primaryStage; + + BorderPane root = new BorderPane(); + + root.setBottom(new Text("running...")); + + Scene scene = new Scene(root); + stage.setScene(scene); + stage.setTitle("Testing"); + stage.show(); + + launchLatch.countDown(); + } + } + + boolean done; + + public synchronized void signalDone() { + done = true; + notifyAll(); + } + + public synchronized void waitDone() throws InterruptedException { + while (!done) { + wait(); + } + } + + private static void resetOptions() { + NUM_TESTS = 5000; + + // shape settings: + SHAPE_MODE = ShapeMode.NINE_LINE_POLYS; + + USE_DASHES = false; + USE_VAR_STROKE = false; + } + + /** + * Test + * @param args + */ + public static void initArgs(String[] args) { + System.out.println("---------------------------------------"); + System.out.println("ClipShapeTest: image = " + TESTW + " x " + TESTH); + + resetOptions(); + + boolean runSlowTests = false; + + for (String arg : args) { + if ("-slow".equals(arg)) { + runSlowTests = true; + } else if ("-doDash".equals(arg)) { + USE_DASHES = true; + } else if ("-doVarStroke".equals(arg)) { + USE_VAR_STROKE = true; + } else { + // shape mode: + if (arg.equalsIgnoreCase("-poly")) { + SHAPE_MODE = ShapeMode.NINE_LINE_POLYS; + } else if (arg.equalsIgnoreCase("-bigpoly")) { + SHAPE_MODE = ShapeMode.FIFTY_LINE_POLYS; + } else if (arg.equalsIgnoreCase("-quad")) { + SHAPE_MODE = ShapeMode.FOUR_QUADS; + } else if (arg.equalsIgnoreCase("-cubic")) { + SHAPE_MODE = ShapeMode.TWO_CUBICS; + } else if (arg.equalsIgnoreCase("-mixed")) { + SHAPE_MODE = ShapeMode.MIXED; + } + } + } + + System.out.println("Shape mode: " + SHAPE_MODE); + + // adjust image comparison thresholds: + switch (SHAPE_MODE) { + case TWO_CUBICS: + // Define uncertainty for curves: +/* +Diff Pixels [Worst(All Test setups)][n: 647] sum: 15130 avg: 23.384 [1 | 174] { + 1 .. 2[n: 93] sum: 93 avg: 1.0 [1 | 1] + 2 .. 4[n: 92] sum: 223 avg: 2.423 [2 | 3] + 4 .. 8[n: 135] sum: 732 avg: 5.422 [4 | 7] + 8 .. 16[n: 109] sum: 1235 avg: 11.33 [8 | 15] + 16 .. 32[n: 82] sum: 1782 avg: 21.731 [16 | 31] + 32 .. 64[n: 59] sum: 2584 avg: 43.796 [32 | 62] + 64 .. 128[n: 52] sum: 4929 avg: 94.788 [64 | 127] + 128 .. 256[n: 25] sum: 3552 avg: 142.08 [129 | 174] } + +DASH: Diff Pixels [Worst(All Test setups)][n: 128] sum: 5399 avg: 42.179 [1 | 255] { + 1 .. 2[n: 54] sum: 54 avg: 1.0 [1 | 1] + 2 .. 4[n: 28] sum: 63 avg: 2.25 [2 | 3] + 4 .. 8[n: 6] sum: 33 avg: 5.5 [4 | 7] + 8 .. 16[n: 3] sum: 33 avg: 11.0 [9 | 15] + 16 .. 32[n: 4] sum: 87 avg: 21.75 [16 | 25] + 32 .. 64[n: 6] sum: 276 avg: 46.0 [37 | 60] + 64 .. 128[n: 6] sum: 568 avg: 94.666 [71 | 118] + 128 .. 256[n: 21] sum: 4285 avg: 204.047 [128 | 255] } +*/ + THRESHOLD_DELTA = 32; + THRESHOLD_NBPIX = (USE_DASHES) ? 40 : 150; + break; + case FOUR_QUADS: + case MIXED: + // Define uncertainty for quads: + // curve subdivision causes curves to be smaller + // then curve offsets are different (more accurate) +/* +Diff Pixels [Worst(All Test setups)][n: 775] sum: 57659 avg: 74.398 [1 | 251] { + 1 .. 2[n: 21] sum: 21 avg: 1.0 [1 | 1] + 2 .. 4[n: 20] sum: 52 avg: 2.6 [2 | 3] + 4 .. 8[n: 44] sum: 236 avg: 5.363 [4 | 7] + 8 .. 16[n: 52] sum: 578 avg: 11.115 [8 | 15] + 16 .. 32[n: 75] sum: 1729 avg: 23.053 [16 | 31] + 32 .. 64[n: 152] sum: 7178 avg: 47.223 [32 | 63] + 64 .. 128[n: 274] sum: 25741 avg: 93.945 [64 | 127] + 128 .. 256[n: 137] sum: 22124 avg: 161.489 [128 | 251] } + +DASH: Diff Pixels [Worst(All Test setups)][n: 354] sum: 29638 avg: 83.723 [1 | 254] { + 1 .. 2[n: 31] sum: 31 avg: 1.0 [1 | 1] + 2 .. 4[n: 45] sum: 111 avg: 2.466 [2 | 3] + 4 .. 8[n: 22] sum: 113 avg: 5.136 [4 | 7] + 8 .. 16[n: 25] sum: 247 avg: 9.88 [8 | 15] + 16 .. 32[n: 26] sum: 579 avg: 22.269 [16 | 31] + 32 .. 64[n: 39] sum: 1698 avg: 43.538 [32 | 62] + 64 .. 128[n: 56] sum: 5284 avg: 94.357 [64 | 127] + 128 .. 256[n: 110] sum: 21575 avg: 196.136 [128 | 254] } +*/ + THRESHOLD_DELTA = 64; + THRESHOLD_NBPIX = (USE_DASHES) ? 180 : 420; + break; + default: + // Define uncertainty for lines: + // float variant have higher uncertainty +/* +DASH: Diff Pixels [Worst(All Test setups)][n: 7] sum: 8 avg: 1.142 [1 | 2] { + 1 .. 2[n: 6] sum: 6 avg: 1.0 [1 | 1] + 2 .. 4[n: 1] sum: 2 avg: 2.0 [2 | 2] } +*/ + THRESHOLD_DELTA = 2; + THRESHOLD_NBPIX = 4; // very low + } + + // TODO: define one more threshold on total result (total sum) ? + + System.out.println("THRESHOLD_DELTA: " + THRESHOLD_DELTA); + System.out.println("THRESHOLD_NBPIX: " + THRESHOLD_NBPIX); + + if (runSlowTests) { + NUM_TESTS = 10000; // or 100000 (very slow) + USE_DASHES = true; + USE_VAR_STROKE = true; + } + + System.out.println("NUM_TESTS: " + NUM_TESTS); + + if (USE_DASHES) { + System.out.println("USE_DASHES: enabled."); + } + if (USE_VAR_STROKE) { + System.out.println("USE_VAR_STROKE: enabled."); + } + + System.out.println("---------------------------------------"); + } + + private void runTests() { + + final DiffContext allCtx = new DiffContext("All Test setups"); + final DiffContext allWorstCtx = new DiffContext("Worst(All Test setups)"); + + int failures = 0; + final long start = System.nanoTime(); + try { + if (TEST_STROKER) { + final float[][] dashArrays = (USE_DASHES) ? +// small +// new float[][]{new float[]{1f, 2f}} +// normal + new float[][]{new float[]{13f, 7f}} +// large (prime) +// new float[][]{new float[]{41f, 7f}} +// none + : new float[][]{null}; + + System.out.println("dashes: " + Arrays.deepToString(dashArrays)); + + final float[] strokeWidths = (USE_VAR_STROKE) + ? new float[5] : + new float[]{10f}; + + int nsw = 0; + if (USE_VAR_STROKE) { + for (float width = 0.1f; width < 110f; width *= 5f) { + strokeWidths[nsw++] = width; + } + } else { + nsw = 1; + } + + System.out.println("stroke widths: " + Arrays.toString(strokeWidths)); + + // Stroker tests: + for (int w = 0; w < nsw; w++) { + final float width = strokeWidths[w]; + + for (float[] dashes : dashArrays) { + + for (int cap = 0; cap <= 2; cap++) { + + for (int join = 0; join <= 2; join++) { + + failures += paintPaths(allCtx, allWorstCtx, new TestSetup(SHAPE_MODE, false, width, cap, join, dashes)); + failures += paintPaths(allCtx, allWorstCtx, new TestSetup(SHAPE_MODE, true, width, cap, join, dashes)); + } + } + } + } + } + + if (TEST_FILLER) { + // Filler tests: + failures += paintPaths(allCtx, allWorstCtx, new TestSetup(SHAPE_MODE, false, Path2D.WIND_NON_ZERO)); + failures += paintPaths(allCtx, allWorstCtx, new TestSetup(SHAPE_MODE, true, Path2D.WIND_NON_ZERO)); + + failures += paintPaths(allCtx, allWorstCtx, new TestSetup(SHAPE_MODE, false, Path2D.WIND_EVEN_ODD)); + failures += paintPaths(allCtx, allWorstCtx, new TestSetup(SHAPE_MODE, true, Path2D.WIND_EVEN_ODD)); + } + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + System.out.println("main: duration= " + (1e-6 * (System.nanoTime() - start)) + " ms."); + + allWorstCtx.dump(); + allCtx.dump(); + + if (DO_FAIL && (failures != 0)) { + throw new RuntimeException("Clip test failures : " + failures); + } + } + + int paintPaths(final DiffContext allCtx, final DiffContext allWorstCtx, final TestSetup ts) throws IOException { + final long start = System.nanoTime(); + + if (FIXED_SEED) { + // Reset seed for random numbers: + RANDOM.setSeed(SEED); + } + + System.out.println("paintPaths: " + NUM_TESTS + + " paths (" + SHAPE_MODE + ") - setup: " + ts); + + final Path2D p2d = new Path2D.Double(ts.windingRule); + + final Path p = makePath(ts); + + final WritableImage imgOff = new WritableImage(TESTW, TESTH); + final PixelReader prOff = imgOff.getPixelReader(); + + final WritableImage imgOn = new WritableImage(TESTW, TESTH); + final PixelReader prOn = imgOn.getPixelReader(); + + final WritableImage imgDiff = new WritableImage(TESTW, TESTH); + final PixelWriter prDiff = imgDiff.getPixelWriter(); + + final DiffContext testSetupCtx = new DiffContext("Test setup"); + final DiffContext testWorstCtx = new DiffContext("Worst"); + final DiffContext testWorstThCtx = new DiffContext("Worst(>threshold)"); + + int nd = 0; + try { + final DiffContext testCtx = new DiffContext("Test"); + final DiffContext testThCtx = new DiffContext("Test(>threshold)"); + PixelWriter diffImage; + + for (int n = 0; n < NUM_TESTS; n++) { + genShape(p2d, ts); + + // Use directly Platform.runLater() instead of Util.runAndWait() + // that has too high latency (100ms polling) + done = false; + Platform.runLater(() -> { + /* + Note: as CachingShapeRep try caching the Path mask at the second rendering pass, + then its xformBounds corresponds to the shape not the giiiiven clip. + To avoid such side-effect, perform clipping first below (or call setPath again) + */ + setPath(p, p2d); + + // Runtime clip setting ON (FIRST): + paintShape(p, imgOn, true); + + // Runtime clip setting OFF (2ND): + paintShape(p, imgOff, false); + + signalDone(); + }); + try { + waitDone(); + } catch (InterruptedException ex) { + break; + } + + /* compute image difference if possible */ + diffImage = computeDiffImage(testCtx, testThCtx, prOn, prOff, prDiff); + + // Worst (total) + if (testCtx.isDiff()) { + if (testWorstCtx.isWorse(testCtx, false)) { + testWorstCtx.set(testCtx); + } + if (testWorstThCtx.isWorse(testCtx, true)) { + testWorstThCtx.set(testCtx); + } + // accumulate data: + testSetupCtx.add(testCtx); + } + if (diffImage != null) { + nd++; + + testThCtx.dump(); + testCtx.dump(); + + if (nd < MAX_SHOW_FRAMES) { + if (nd < MAX_SAVE_FRAMES) { + if (DUMP_SHAPE) { + dumpShape(p2d); + } + + final String testName = "Setup_" + ts.id + "_test_" + n; + + saveImage(imgOff, OUTPUT_DIR, testName + "-off.png"); + saveImage(imgOn, OUTPUT_DIR, testName + "-on.png"); + saveImage(imgDiff, OUTPUT_DIR, testName + "-diff.png"); + } + } + } + } + } finally { + if (nd != 0) { + System.out.println("paintPaths: " + NUM_TESTS + " paths - " + + "Number of differences = " + nd + + " ratio = " + (100f * nd) / NUM_TESTS + " %"); + } + + if (testWorstCtx.isDiff()) { + testWorstCtx.dump(); + if (testWorstThCtx.isDiff() && testWorstThCtx.histPix.sum != testWorstCtx.histPix.sum) { + testWorstThCtx.dump(); + } + if (allWorstCtx.isWorse(testWorstThCtx, true)) { + allWorstCtx.set(testWorstThCtx); + } + } + testSetupCtx.dump(); + + // accumulate data: + allCtx.add(testSetupCtx); + } + System.out.println("paintPaths: duration= " + (1e-6 * (System.nanoTime() - start)) + " ms."); + return nd; + } + + public Path makePath(final TestSetup ts) { + final Path p = new Path(); + p.setCache(false); + p.setSmooth(true); + + if (ts.isStroke()) { + p.setFill(null); + p.setStroke(Color.BLACK); + + switch (ts.strokeCap) { + case BasicStroke.CAP_BUTT: + p.setStrokeLineCap(StrokeLineCap.BUTT); + break; + case BasicStroke.CAP_ROUND: + p.setStrokeLineCap(StrokeLineCap.ROUND); + break; + case BasicStroke.CAP_SQUARE: + p.setStrokeLineCap(StrokeLineCap.SQUARE); + break; + default: + } + + p.setStrokeLineJoin(StrokeLineJoin.MITER); + switch (ts.strokeJoin) { + case BasicStroke.JOIN_MITER: + p.setStrokeLineJoin(StrokeLineJoin.MITER); + break; + case BasicStroke.JOIN_ROUND: + p.setStrokeLineJoin(StrokeLineJoin.ROUND); + break; + case BasicStroke.JOIN_BEVEL: + p.setStrokeLineJoin(StrokeLineJoin.BEVEL); + break; + default: + } + + if (ts.dashes != null) { + ObservableList pDashes = p.getStrokeDashArray(); + pDashes.clear(); + for (float f : ts.dashes) { + pDashes.add(Double.valueOf(f)); + } + } + + p.setStrokeMiterLimit(10.0); + p.setStrokeWidth(ts.strokeWidth); + + } else { + p.setFill(Color.BLACK); + p.setStroke(null); + + switch (ts.windingRule) { + case Path2D.WIND_EVEN_ODD: + p.setFillRule(FillRule.EVEN_ODD); + break; + case Path2D.WIND_NON_ZERO: + p.setFillRule(FillRule.NON_ZERO); + break; + } + } + return p; + } + + public static void setPath(Path p, Path2D p2d) { + final ObservableList elements = p.getElements(); + elements.clear(); + + final double[] coords = new double[6]; + for (PathIterator pi = p2d.getPathIterator(null); !pi.isDone(); pi.next()) { + switch (pi.currentSegment(coords)) { + case PathIterator.SEG_MOVETO: + elements.add(new MoveTo(coords[0], coords[1])); + break; + case PathIterator.SEG_LINETO: + elements.add(new LineTo(coords[0], coords[1])); + break; + case PathIterator.SEG_QUADTO: + elements.add(new QuadCurveTo(coords[0], coords[1], + coords[2], coords[3])); + break; + case PathIterator.SEG_CUBICTO: + elements.add(new CubicCurveTo(coords[0], coords[1], + coords[2], coords[3], + coords[4], coords[5])); + break; + case PathIterator.SEG_CLOSE: + elements.add(new ClosePath()); + break; + default: + throw new InternalError("unexpected segment type"); + } + } + } + + private static void paintShape(Path p, WritableImage wimg, final boolean clip) { + // Enable or Disable clipping: + System.setProperty("prism.marlin.clip.runtime", (clip) ? "true" : "false"); + + final SnapshotParameters sp = new SnapshotParameters(); + sp.setViewport(new Rectangle2D(0, 0, TESTW, TESTH)); + + WritableImage out = p.snapshot(sp, wimg); + + if (out != wimg) { + System.out.println("different images !"); + } + // Or use (faster?) + // ShapeUtil.getMaskData(p, null, b, BaseTransform.IDENTITY_TRANSFORM, true, aa); + } + + static void genShape(final Path2D p2d, final TestSetup ts) { + p2d.reset(); + + final int end = (SHAPE_REPEAT) ? 2 : 1; + + for (int p = 0; p < end; p++) { + p2d.moveTo(randX(), randY()); + + switch (ts.shapeMode) { + case MIXED: + case FIFTY_LINE_POLYS: + case NINE_LINE_POLYS: + case FIVE_LINE_POLYS: + p2d.lineTo(randX(), randY()); + p2d.lineTo(randX(), randY()); + p2d.lineTo(randX(), randY()); + p2d.lineTo(randX(), randY()); + if (ts.shapeMode == ShapeMode.FIVE_LINE_POLYS) { + // And an implicit close makes 5 lines + break; + } + p2d.lineTo(randX(), randY()); + p2d.lineTo(randX(), randY()); + p2d.lineTo(randX(), randY()); + p2d.lineTo(randX(), randY()); + if (ts.shapeMode == ShapeMode.NINE_LINE_POLYS) { + // And an implicit close makes 9 lines + break; + } + if (ts.shapeMode == ShapeMode.FIFTY_LINE_POLYS) { + for (int i = 0; i < 41; i++) { + p2d.lineTo(randX(), randY()); + } + // And an implicit close makes 50 lines + break; + } + case TWO_CUBICS: + p2d.curveTo(randX(), randY(), randX(), randY(), randX(), randY()); + p2d.curveTo(randX(), randY(), randX(), randY(), randX(), randY()); + if (ts.shapeMode == ShapeMode.TWO_CUBICS) { + break; + } + case FOUR_QUADS: + p2d.quadTo(randX(), randY(), randX(), randY()); + p2d.quadTo(randX(), randY(), randX(), randY()); + p2d.quadTo(randX(), randY(), randX(), randY()); + p2d.quadTo(randX(), randY(), randX(), randY()); + if (ts.shapeMode == ShapeMode.FOUR_QUADS) { + break; + } + default: + } + + if (ts.closed) { + p2d.closePath(); + } + } + } + + @BeforeClass + public static void setupOnce() { + // Start the Application + new Thread(() -> Application.launch(MyApp.class, (String[]) null)).start(); + + try { + if (!launchLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)) { + throw new AssertionFailedError("Timeout waiting for Application to launch"); + } + + } catch (InterruptedException ex) { + AssertionFailedError err = new AssertionFailedError("Unexpected exception"); + err.initCause(ex); + throw err; + } + + assertEquals(0, launchLatch.getCount()); + } + + private void checkMarlin() { + if (!isMarlin.get()) { + throw new RuntimeException("Marlin renderer not used at runtime !"); + } + if (!isClipRuntime.get()) { + throw new RuntimeException("Marlin clipping not enabled at runtime !"); + } + } + + @AfterClass + public static void teardownOnce() { + Platform.exit(); + } + + @Test(timeout = 600000) + public void TestPoly() throws InterruptedException { + test(new String[]{"-poly"}); + test(new String[]{"-poly", "-doDash"}); + } + + @Test(timeout = 900000) + public void TestQuad() throws InterruptedException { + test(new String[]{"-quad"}); + test(new String[]{"-quad", "-doDash"}); + } + + @Test(timeout = 900000) + public void TestCubic() throws InterruptedException { + test(new String[]{"-cubic"}); + test(new String[]{"-cubic", "-doDash"}); + } + + private void test(String[] args) { + initArgs(args); + runTests(); + checkMarlin(); + Assert.assertFalse("Detected a problem.", doChecksFailed); + } + + private static void dumpShape(final Shape shape) { + final float[] coords = new float[6]; + + for (final PathIterator it = shape.getPathIterator(null); !it.isDone(); it.next()) { + final int type = it.currentSegment(coords); + switch (type) { + case PathIterator.SEG_MOVETO: + System.out.println("p2d.moveTo(" + coords[0] + ", " + coords[1] + ");"); + break; + case PathIterator.SEG_LINETO: + System.out.println("p2d.lineTo(" + coords[0] + ", " + coords[1] + ");"); + break; + case PathIterator.SEG_QUADTO: + System.out.println("p2d.quadTo(" + coords[0] + ", " + coords[1] + ", " + coords[2] + ", " + coords[3] + ");"); + break; + case PathIterator.SEG_CUBICTO: + System.out.println("p2d.curveTo(" + coords[0] + ", " + coords[1] + ", " + coords[2] + ", " + coords[3] + ", " + coords[4] + ", " + coords[5] + ");"); + break; + case PathIterator.SEG_CLOSE: + System.out.println("p2d.closePath();"); + break; + default: + System.out.println("// Unsupported segment type= " + type); + } + } + System.out.println("--------------------------------------------------"); + } + + static double randX() { + return RANDOM.nextDouble() * RANDW + OFFW; + } + + static double randY() { + return RANDOM.nextDouble() * RANDH + OFFH; + } + + private final static class TestSetup { + + static final AtomicInteger COUNT = new AtomicInteger(); + + final int id; + final ShapeMode shapeMode; + final boolean closed; + // stroke + final float strokeWidth; + final int strokeCap; + final int strokeJoin; + final float[] dashes; + // fill + final int windingRule; + + TestSetup(ShapeMode shapeMode, final boolean closed, + final float strokeWidth, final int strokeCap, final int strokeJoin, final float[] dashes) { + this.id = COUNT.incrementAndGet(); + this.shapeMode = shapeMode; + this.closed = closed; + this.strokeWidth = strokeWidth; + this.strokeCap = strokeCap; + this.strokeJoin = strokeJoin; + this.dashes = dashes; + this.windingRule = Path2D.WIND_NON_ZERO; + } + + TestSetup(ShapeMode shapeMode, final boolean closed, final int windingRule) { + this.id = COUNT.incrementAndGet(); + this.shapeMode = shapeMode; + this.closed = closed; + this.strokeWidth = 0f; + this.strokeCap = this.strokeJoin = -1; // invalid + this.dashes = null; + this.windingRule = windingRule; + } + + boolean isStroke() { + return this.strokeWidth > 0f; + } + + @Override + public String toString() { + if (isStroke()) { + return "TestSetup{id=" + id + ", shapeMode=" + shapeMode + ", closed=" + closed + + ", strokeWidth=" + strokeWidth + ", strokeCap=" + getCap(strokeCap) + ", strokeJoin=" + getJoin(strokeJoin) + + ((dashes != null) ? ", dashes: " + Arrays.toString(dashes) : "") + + '}'; + } + return "TestSetup{id=" + id + ", shapeMode=" + shapeMode + ", closed=" + closed + + ", fill" + + ", windingRule=" + getWindingRule(windingRule) + '}'; + } + + private static String getCap(final int cap) { + switch (cap) { + case BasicStroke.CAP_BUTT: + return "CAP_BUTT"; + case BasicStroke.CAP_ROUND: + return "CAP_ROUND"; + case BasicStroke.CAP_SQUARE: + return "CAP_SQUARE"; + default: + return ""; + } + + } + + private static String getJoin(final int join) { + switch (join) { + case BasicStroke.JOIN_MITER: + return "JOIN_MITER"; + case BasicStroke.JOIN_ROUND: + return "JOIN_ROUND"; + case BasicStroke.JOIN_BEVEL: + return "JOIN_BEVEL"; + default: + return ""; + } + + } + + private static String getWindingRule(final int rule) { + switch (rule) { + case PathIterator.WIND_EVEN_ODD: + return "WIND_EVEN_ODD"; + case PathIterator.WIND_NON_ZERO: + return "WIND_NON_ZERO"; + default: + return ""; + } + } + } + + // --- utilities --- + private static final int DCM_ALPHA_MASK = 0xff000000; + + public static BufferedImage newImage(final int w, final int h) { + return new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB_PRE); + } + + public static PixelWriter computeDiffImage(final DiffContext testCtx, + final DiffContext testThCtx, + final PixelReader tstImage, + final PixelReader refImage, + final PixelWriter diffImage) { + + // reset diff contexts: + testCtx.reset(); + testThCtx.reset(); + + int ref, tst, dg, v; + + for (int y = 0; y < TESTH; y++) { + for (int x = 0; x < TESTW; x++) { + ref = refImage.getArgb(x, y); + tst = tstImage.getArgb(x, y); + + // grayscale diff: + dg = (r(ref) + g(ref) + b(ref)) - (r(tst) + g(tst) + b(tst)); + + // max difference on grayscale values: + v = (int) Math.ceil(Math.abs(dg / 3.0)); + + if (v <= THRESHOLD_DELTA) { + diffImage.setArgb(x, y, 0); + } else { + diffImage.setArgb(x, y, toInt(v, v, v)); + testThCtx.add(v); + } + + if (v != 0) { + testCtx.add(v); + } + } + } + + if (!testThCtx.isDiff() || (testThCtx.histPix.count <= THRESHOLD_NBPIX)) { + return null; + } + + return diffImage; + } + + static void saveImage(final WritableImage image, final File resDirectory, final String imageFileName) throws IOException { + saveImage(SwingFXUtils.fromFXImage(image, null), resDirectory, imageFileName); + } + + static void saveImage(final BufferedImage image, final File resDirectory, final String imageFileName) throws IOException { + final Iterator itWriters = ImageIO.getImageWritersByFormatName("PNG"); + if (itWriters.hasNext()) { + final ImageWriter writer = itWriters.next(); + + final ImageWriteParam writerParams = writer.getDefaultWriteParam(); + writerParams.setProgressiveMode(ImageWriteParam.MODE_DISABLED); + + final File imgFile = new File(resDirectory, imageFileName); + + if (!imgFile.exists() || imgFile.canWrite()) { + System.out.println("saveImage: saving image as PNG [" + imgFile + "]..."); + imgFile.delete(); + + // disable cache in temporary files: + ImageIO.setUseCache(false); + + final long start = System.nanoTime(); + + // PNG uses already buffering: + final ImageOutputStream imgOutStream = ImageIO.createImageOutputStream(new FileOutputStream(imgFile)); + + writer.setOutput(imgOutStream); + try { + writer.write(null, new IIOImage(image, null, null), writerParams); + } finally { + imgOutStream.close(); + + final long time = System.nanoTime() - start; + System.out.println("saveImage: duration= " + (time / 1000000l) + " ms."); + } + } + } + } + + static int r(final int v) { + return (v >> 16 & 0xff); + } + + static int g(final int v) { + return (v >> 8 & 0xff); + } + + static int b(final int v) { + return (v & 0xff); + } + + static int clamp127(final int v) { + return (v < 128) ? (v > -127 ? (v + 127) : 0) : 255; + } + + static int toInt(final int r, final int g, final int b) { + return DCM_ALPHA_MASK | (r << 16) | (g << 8) | b; + } + + /* stats */ + static class StatInteger { + + public final String name; + public long count = 0l; + public long sum = 0l; + public long min = Integer.MAX_VALUE; + public long max = Integer.MIN_VALUE; + + StatInteger(String name) { + this.name = name; + } + + void reset() { + count = 0l; + sum = 0l; + min = Integer.MAX_VALUE; + max = Integer.MIN_VALUE; + } + + void add(int val) { + count++; + sum += val; + if (val < min) { + min = val; + } + if (val > max) { + max = val; + } + } + + void add(long val) { + count++; + sum += val; + if (val < min) { + min = val; + } + if (val > max) { + max = val; + } + } + + void add(StatInteger stat) { + count += stat.count; + sum += stat.sum; + if (stat.min < min) { + min = stat.min; + } + if (stat.max > max) { + max = stat.max; + } + } + + public final double average() { + return ((double) sum) / count; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(128); + toString(sb); + return sb.toString(); + } + + public final StringBuilder toString(final StringBuilder sb) { + sb.append(name).append("[n: ").append(count); + sb.append("] sum: ").append(sum).append(" avg: ").append(trimTo3Digits(average())); + sb.append(" [").append(min).append(" | ").append(max).append("]"); + return sb; + } + + } + + final static class Histogram extends StatInteger { + + static final int BUCKET = 2; + static final int MAX = 20; + static final int LAST = MAX - 1; + static final int[] STEPS = new int[MAX]; + static final int BUCKET_TH; + + static { + STEPS[0] = 0; + STEPS[1] = 1; + + for (int i = 2; i < MAX; i++) { + STEPS[i] = STEPS[i - 1] * BUCKET; + } +// System.out.println("Histogram.STEPS = " + Arrays.toString(STEPS)); + + if (THRESHOLD_DELTA % 2 != 0) { + throw new IllegalStateException("THRESHOLD_DELTA must be odd"); + } + + BUCKET_TH = bucket(THRESHOLD_DELTA); + } + + static int bucket(int val) { + for (int i = 1; i < MAX; i++) { + if (val < STEPS[i]) { + return i - 1; + } + } + return LAST; + } + + private final StatInteger[] stats = new StatInteger[MAX]; + + public Histogram(String name) { + super(name); + for (int i = 0; i < MAX; i++) { + stats[i] = new StatInteger(String.format("%5s .. %5s", STEPS[i], ((i + 1 < MAX) ? STEPS[i + 1] : "~"))); + } + } + + @Override + final void reset() { + super.reset(); + for (int i = 0; i < MAX; i++) { + stats[i].reset(); + } + } + + @Override + final void add(int val) { + super.add(val); + stats[bucket(val)].add(val); + } + + @Override + final void add(long val) { + add((int) val); + } + + void add(Histogram hist) { + super.add(hist); + for (int i = 0; i < MAX; i++) { + stats[i].add(hist.stats[i]); + } + } + + boolean isWorse(Histogram hist, boolean useTh) { + boolean worst = false; + if (!useTh && (hist.sum > sum)) { + worst = true; + } else { + long sumLoc = 0l; + long sumHist = 0l; + // use running sum: + for (int i = MAX - 1; i >= BUCKET_TH; i--) { + sumLoc += stats[i].sum; + sumHist += hist.stats[i].sum; + } + if (sumHist > sumLoc) { + worst = true; + } + } + /* + System.out.println("running sum worst:"); + System.out.println("this ? " + toString()); + System.out.println("worst ? " + hist.toString()); + */ + return worst; + } + + @Override + public final String toString() { + final StringBuilder sb = new StringBuilder(2048); + super.toString(sb).append(" { "); + + for (int i = 0; i < MAX; i++) { + if (stats[i].count != 0l) { + sb.append("\n ").append(stats[i].toString()); + } + } + + return sb.append(" }").toString(); + } + } + + /** + * Adjust the given double value to keep only 3 decimal digits + * @param value value to adjust + * @return double value with only 3 decimal digits + */ + static double trimTo3Digits(final double value) { + return ((long) (1e3d * value)) / 1e3d; + } + + static final class DiffContext { + + public final Histogram histPix; + + DiffContext(String name) { + histPix = new Histogram("Diff Pixels [" + name + "]"); + } + + void reset() { + histPix.reset(); + } + + void dump() { + if (isDiff()) { + System.out.println("Differences [" + histPix.name + "]:\n" + histPix.toString()); + } else { + System.out.println("No difference for [" + histPix.name + "]."); + } + } + + void add(int val) { + histPix.add(val); + } + + void add(DiffContext ctx) { + histPix.add(ctx.histPix); + } + + void set(DiffContext ctx) { + reset(); + add(ctx); + } + + boolean isWorse(DiffContext ctx, boolean useTh) { + return histPix.isWorse(ctx.histPix, useTh); + } + + boolean isDiff() { + return histPix.sum != 0l; + } + } +}