--- old/src/java.desktop/share/classes/sun/java2d/marlin/ByteArrayCache.java 2018-02-15 13:11:08.976283791 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/ByteArrayCache.java 2018-02-15 13:11:08.820279516 +0100 @@ -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 @@ -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,12 +229,8 @@ } } - static byte[] createArray(final int length, final boolean clean) { - if (clean) { - return new byte[length]; - } - // use JDK9 Unsafe.allocateUninitializedArray(class, length): - return (byte[]) OffHeapArray.UNSAFE.allocateUninitializedArray(byte.class, length); + static byte[] createArray(final int length) { + return new byte[length]; } static void fill(final byte[] array, final int fromIndex, --- old/src/java.desktop/share/classes/sun/java2d/marlin/Curve.java 2018-02-15 13:11:09.304292777 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/Curve.java 2018-02-15 13:11:09.152288612 +0100 @@ -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,97 @@ 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; + // useless derivatives for lines + if (false) { + 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 +134,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 +174,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 +200,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 +212,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 +232,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 +247,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 +255,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/src/java.desktop/share/classes/sun/java2d/marlin/DCurve.java 2018-02-15 13:11:09.640301982 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/DCurve.java 2018-02-15 13:11:09.484297708 +0100 @@ -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,96 @@ 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; + if (false) { + 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 +133,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 +173,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 +199,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 +211,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 +231,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 +246,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 +254,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/src/java.desktop/share/classes/sun/java2d/marlin/DDasher.java 2018-02-15 13:11:09.968310969 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/DDasher.java 2018-02-15 13:11:09.816306805 +0100 @@ -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,6 +26,8 @@ package sun.java2d.marlin; import java.util.Arrays; +import sun.java2d.marlin.DTransformingPathConsumer2D.CurveBasicMonotonizer; +import sun.java2d.marlin.DTransformingPathConsumer2D.CurveClipSplitter; /** * The DDasher class takes a series of linear commands @@ -40,8 +42,9 @@ */ 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; } /** @@ -115,10 +145,13 @@ // Normalize so 0 <= phase < dash[0] int sidx = 0; dashOn = true; + double sum = 0.0d; for (double d : dash) { sum += d; } + this.cycleLen = sum; + double cycles = phase / sum; if (phase < 0.0d) { if (-cycles >= MAX_CYCLES) { @@ -167,6 +200,12 @@ this.recycleDashes = recycleDashes; + if (rdrCtx.doClip) { + this.clipRect = rdrCtx.clipRect; + } else { + this.clipRect = null; + this.cOutCode = 0; + } return this; // fluent API } @@ -204,33 +243,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 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]); + out.quadTo(buf[off ], buf[off + 1], + buf[off + 2], buf[off + 3]); return; case 4: - out.lineTo(buf[off], buf[off+1]); + out.lineTo(buf[off], buf[off + 1]); return; default: } @@ -246,12 +294,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, @@ -267,7 +309,7 @@ } else { if (needsMoveTo) { needsMoveTo = false; - out.moveTo(x0, y0); + out.moveTo(cx0, cy0); } emitSeg(pts, off, type); } @@ -278,8 +320,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) { @@ -305,10 +347,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); - double len = dx*dx + dy*dy; + 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; if (len == 0.0d) { return; } @@ -327,8 +415,7 @@ boolean _dashOn = dashOn; double _phase = phase; - double leftInThisDashSegment; - double d, dashdx, dashdy, p; + double leftInThisDashSegment, d; while (true) { d = _dash[_idx]; @@ -349,24 +436,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); @@ -377,19 +455,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; @@ -401,17 +555,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; } @@ -439,7 +592,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; @@ -462,15 +637,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; @@ -491,7 +665,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; @@ -513,7 +687,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); @@ -521,7 +695,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; @@ -533,11 +707,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; @@ -546,7 +720,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]; @@ -613,7 +787,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; @@ -635,7 +809,8 @@ // 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); +// TODO: check NaN is impossible if (n == 1 && !Double.isNaN(nextRoots[0])) { t = nextRoots[0]; } @@ -656,6 +831,16 @@ return t; } + double totalLength() { + while (!done) { + goToNextLeaf(); + } + // reset LengthIterator: + reset(); + + return lenAtNextT; + } + double lastSegLen() { return lastSegLen; } @@ -665,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; @@ -678,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; @@ -700,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(); } @@ -718,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; @@ -726,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; @@ -741,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; + + // 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] = x0; _curCurvepts[1] = y0; - _curCurvepts[2] = x1; _curCurvepts[3] = y1; - _curCurvepts[4] = x2; _curCurvepts[5] = y2; - somethingTo(6); + _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/src/java.desktop/share/classes/sun/java2d/marlin/DHelpers.java 2018-02-15 13:11:10.308320283 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/DHelpers.java 2018-02-15 13:11:10.160316229 +0100 @@ -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 sun.java2d.marlin; -import static java.lang.Math.PI; import java.util.Arrays; import sun.java2d.marlin.stats.Histogram; import sun.java2d.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); } } @@ -608,12 +765,12 @@ e += 2; continue; case TYPE_QUADTO: - io.quadTo(_curves[e+0], _curves[e+1], + io.quadTo(_curves[e], _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; @@ -651,12 +808,12 @@ continue; case TYPE_QUADTO: e -= 4; - io.quadTo(_curves[e+0], _curves[e+1], + io.quadTo(_curves[e], _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; --- old/src/java.desktop/share/classes/sun/java2d/marlin/DMarlinRenderingEngine.java 2018-02-15 13:11:10.644329488 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/DMarlinRenderingEngine.java 2018-02-15 13:11:10.492325324 +0100 @@ -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 @@ -31,6 +31,7 @@ import java.awt.geom.Path2D; import java.awt.geom.PathIterator; import java.security.AccessController; +import sun.awt.geom.PathConsumer2D; import static sun.java2d.marlin.MarlinUtils.logInfo; import sun.java2d.ReentrantContextProvider; import sun.java2d.ReentrantContextProviderCLQ; @@ -46,7 +47,21 @@ public final class DMarlinRenderingEngine extends RenderingEngine implements MarlinConst { - private static enum NormMode { + // 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(); + + private static final float MIN_PEN_SIZE = 1.0f / MIN_SUBPIXELS; + + static final double UPPER_BND = Float.MAX_VALUE / 2.0d; + static final double LOWER_BND = -UPPER_BND; + + private enum NormMode { ON_WITH_AA { @Override PathIterator getNormalizingPathIterator(final DRendererContext rdrCtx, @@ -79,18 +94,6 @@ PathIterator src); } - private static final float MIN_PEN_SIZE = 1.0f / NORM_SUBPIXELS; - - static final double UPPER_BND = Float.MAX_VALUE / 2.0d; - static final double LOWER_BND = -UPPER_BND; - - static final boolean DO_CLIP = MarlinProperties.isDoClip(); - static final boolean DO_CLIP_FILL = true; - - static final boolean DO_TRACE_PATH = false; - - static final boolean DO_CLIP_RUNTIME_ENABLE = MarlinProperties.isDoClipRuntimeFlag(); - /** * Public constructor */ @@ -186,7 +189,7 @@ boolean thin, boolean normalize, boolean antialias, - final sun.awt.geom.PathConsumer2D consumer) + final PathConsumer2D consumer) { final NormMode norm = (normalize) ? ((antialias) ? NormMode.ON_WITH_AA : NormMode.ON_NO_AA) @@ -424,11 +427,24 @@ pc2d = transformerPC2D.deltaTransformConsumer(pc2d, strokerat); // stroker will adjust the clip rectangle (width / miter limit): - pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit, scale); + pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit, scale, + (dashesD == null)); + + // Curve Monotizer: + rdrCtx.monotonizer.init(width); if (dashesD != null) { + if (DO_TRACE_PATH) { + pc2d = transformerPC2D.traceDasher(pc2d); + } pc2d = rdrCtx.dasher.init(pc2d, dashesD, dashLen, dashphase, recycleDashes); + + if (DISABLE_2ND_STROKER_CLIPPING) { + // disable stoker clipping: + rdrCtx.stroker.disableClipping(); + } + } else if (rdrCtx.doClip && (caps != Stroker.CAP_BUTT)) { if (DO_TRACE_PATH) { pc2d = transformerPC2D.traceClosedPathDetector(pc2d); @@ -627,6 +643,12 @@ private static void pathTo(final DRendererContext rdrCtx, final PathIterator pi, DPathConsumer2D pc2d) { + if (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; @@ -1002,14 +1024,17 @@ final String refType = AccessController.doPrivileged( new GetPropertyAction("sun.java2d.renderer.useRef", "soft")); - - // Java 1.6 does not support strings in switch: - if ("hard".equalsIgnoreCase(refType)) { - REF_TYPE = ReentrantContextProvider.REF_HARD; - } else if ("weak".equalsIgnoreCase(refType)) { - REF_TYPE = ReentrantContextProvider.REF_WEAK; - } else { - REF_TYPE = ReentrantContextProvider.REF_SOFT; + switch (refType) { + default: + case "soft": + REF_TYPE = ReentrantContextProvider.REF_SOFT; + break; + case "weak": + REF_TYPE = ReentrantContextProvider.REF_WEAK; + break; + case "hard": + REF_TYPE = ReentrantContextProvider.REF_HARD; + break; } if (USE_THREAD_LOCAL) { @@ -1069,8 +1094,10 @@ logInfo("sun.java2d.renderer.edges = " + MarlinConst.INITIAL_EDGES_COUNT); - logInfo("sun.java2d.renderer.pixelsize = " - + MarlinConst.INITIAL_PIXEL_DIM); + logInfo("sun.java2d.renderer.pixelWidth = " + + MarlinConst.INITIAL_PIXEL_WIDTH); + logInfo("sun.java2d.renderer.pixelHeight = " + + MarlinConst.INITIAL_PIXEL_HEIGHT); logInfo("sun.java2d.renderer.subPixel_log2_X = " + MarlinConst.SUBPIXEL_LG_POSITIONS_X); @@ -1100,12 +1127,21 @@ // optimisation parameters logInfo("sun.java2d.renderer.useSimplifier = " + MarlinConst.USE_SIMPLIFIER); + logInfo("sun.java2d.renderer.usePathSimplifier= " + + MarlinConst.USE_PATH_SIMPLIFIER); + logInfo("sun.java2d.renderer.pathSimplifier.pixTol = " + + MarlinProperties.getPathSimplifierPixelTolerance()); logInfo("sun.java2d.renderer.clip = " + MarlinProperties.isDoClip()); logInfo("sun.java2d.renderer.clip.runtime.enable = " + MarlinProperties.isDoClipRuntimeFlag()); + logInfo("sun.java2d.renderer.clip.subdivider = " + + MarlinProperties.isDoClipSubdivider()); + logInfo("sun.java2d.renderer.clip.subdivider.minLength = " + + MarlinProperties.getSubdividerMinLength()); + // debugging parameters logInfo("sun.java2d.renderer.doStats = " + MarlinConst.DO_STATS); @@ -1123,6 +1159,8 @@ + MarlinConst.LOG_UNSAFE_MALLOC); // quality settings + logInfo("sun.java2d.renderer.curve_len_err = " + + MarlinProperties.getCurveLengthError()); logInfo("sun.java2d.renderer.cubic_dec_d2 = " + MarlinProperties.getCubicDecD2()); logInfo("sun.java2d.renderer.cubic_inc_d1 = " --- old/src/java.desktop/share/classes/sun/java2d/marlin/DRenderer.java 2018-02-15 13:11:10.980338693 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/DRenderer.java 2018-02-15 13:11:10.828334529 +0100 @@ -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 @@ -53,9 +53,9 @@ private static final int SUBPIXEL_TILE = TILE_H << SUBPIXEL_LG_POSITIONS_Y; - // 2048 (pixelSize) pixels (height) x 8 subpixels = 64K + // 2176 pixels (height) x 8 subpixels = 68K static final int INITIAL_BUCKET_ARRAY - = INITIAL_PIXEL_DIM * SUBPIXEL_POSITIONS_Y; + = INITIAL_PIXEL_HEIGHT * SUBPIXEL_POSITIONS_Y; // crossing capacity = edges count / 4 ~ 1024 static final int INITIAL_CROSSING_COUNT = INITIAL_EDGES_COUNT >> 2; @@ -76,13 +76,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 @@ -109,10 +113,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 @@ -179,7 +185,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; @@ -193,7 +199,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 @@ -203,17 +210,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; } @@ -221,7 +223,7 @@ addLine(x0, y0, x2, y2); if (DO_STATS) { - rdrCtx.stats.stat_rdr_quadBreak.add(nL + 1); + rdrCtx.stats.stat_rdr_quadBreak.add(nL); } } @@ -234,7 +236,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 @@ -249,34 +251,22 @@ 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 +// && (Math.abs(ddx + dddx) + Math.abs(ddy + dddy) * _SCALE_DY) <= _INC_BND + )) { dx = 2.0d * dx + ddx; dy = 2.0d * dy + ddy; ddx = 4.0d * (ddx + dddx); @@ -289,26 +279,42 @@ 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 +// || (Math.abs(ddx + dddx) + Math.abs(ddy + dddy) * _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); } } @@ -533,8 +539,8 @@ edgeBuckets = edgeBuckets_ref.initial; edgeBucketCounts = edgeBucketCounts_ref.initial; - // 2048 (pixelsize) pixel large - alphaLine_ref = rdrCtx.newCleanIntArrayRef(INITIAL_AA_ARRAY); // 8K + // 4096 pixels large + alphaLine_ref = rdrCtx.newCleanIntArrayRef(INITIAL_AA_ARRAY); // 16K alphaLine = alphaLine_ref.initial; crossings_ref = rdrCtx.newDirtyIntArrayRef(INITIAL_CROSSING_COUNT); // 2K @@ -692,8 +698,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; @@ -705,7 +713,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/src/java.desktop/share/classes/sun/java2d/marlin/DRendererContext.java 2018-02-15 13:11:11.324348116 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/DRendererContext.java 2018-02-15 13:11:11.172343952 +0100 @@ -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 @@ -31,6 +31,8 @@ import sun.java2d.ReentrantContext; import sun.java2d.marlin.ArrayCacheConst.CacheStats; import sun.java2d.marlin.DMarlinRenderingEngine.NormalizingPathIterator; +import sun.java2d.marlin.DTransformingPathConsumer2D.CurveBasicMonotonizer; +import sun.java2d.marlin.DTransformingPathConsumer2D.CurveClipSplitter; /** * This class is a renderer context dedicated to a single thread @@ -70,6 +72,8 @@ final DStroker stroker; // Simplifies out collinear lines final DCollinearSimplifier simplifier = new DCollinearSimplifier(); + // Simplifies path + final DPathSimplifier pathSimplifier = new DPathSimplifier(); final DDasher dasher; final MarlinTileGenerator ptg; final MarlinCache cache; @@ -81,6 +85,10 @@ boolean closedPath = false; // clip rectangle (ymin, ymax, xmin, xmax): final double[] clipRect = new double[4]; + // CurveBasicMonotonizer instance + final CurveBasicMonotonizer monotonizer; + // CurveClipSplitter instance + final CurveClipSplitter curveClipSplitter; // Array caches: /* clean int[] cache (zero-filled) = 5 refs */ @@ -124,6 +132,10 @@ nPCPathIterator = new NormalizingPathIterator.NearestPixelCenter(double6); nPQPathIterator = new NormalizingPathIterator.NearestPixelQuarter(double6); + // curve monotonizer & clip subdivider (before transformerPC2D init) + monotonizer = new CurveBasicMonotonizer(this); + curveClipSplitter = new CurveClipSplitter(this); + // MarlinRenderingEngine.TransformingPathConsumer2D transformerPC2D = new DTransformingPathConsumer2D(this); --- old/src/java.desktop/share/classes/sun/java2d/marlin/DStroker.java 2018-02-15 13:11:11.656357210 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/DStroker.java 2018-02-15 13:11:11.500352937 +0100 @@ -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 sun.java2d.marlin.DHelpers.PolyStack; +import sun.java2d.marlin.DTransformingPathConsumer2D.CurveBasicMonotonizer; +import sun.java2d.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; } /** @@ -139,6 +140,7 @@ * JOIN_BEVEL. * @param miterLimit the desired miter limit * @param scale scaling factor applied to clip boundaries + * @param subdivideCurves true to indicate to subdivide curves, false if dasher does * @return this instance */ DStroker init(final DPathConsumer2D pc2d, @@ -146,12 +148,15 @@ final int capStyle, final int joinStyle, final double miterLimit, - final double scale) + final double scale, + 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; @@ -189,6 +194,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; @@ -197,6 +211,12 @@ return this; // fluent API } + void disableClipping() { + this.clipRect = null; + this.cOutCode = 0; + this.sOutCode = 0; + } + /** * Disposes this stroker: * clean up before reusing this instance @@ -213,10 +233,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); } } @@ -248,19 +266,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; } @@ -270,7 +289,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, @@ -381,7 +400,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; @@ -400,8 +419,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) @@ -410,7 +429,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; @@ -428,20 +447,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) || @@ -459,8 +479,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]; @@ -478,7 +497,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; @@ -494,7 +513,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) { @@ -521,16 +540,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; @@ -752,10 +795,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); @@ -765,18 +805,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]; @@ -784,14 +825,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 @@ -800,7 +843,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]; @@ -814,6 +857,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; @@ -829,6 +873,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; @@ -942,10 +987,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]; @@ -966,6 +1012,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; @@ -975,6 +1022,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; @@ -990,151 +1038,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; } @@ -1142,17 +1150,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; @@ -1176,8 +1192,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; @@ -1189,74 +1205,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; @@ -1280,8 +1323,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/src/java.desktop/share/classes/sun/java2d/marlin/DTransformingPathConsumer2D.java 2018-02-15 13:11:12.004366743 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/DTransformingPathConsumer2D.java 2018-02-15 13:11:11.852362579 +0100 @@ -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,11 +27,15 @@ import java.awt.geom.AffineTransform; import java.awt.geom.Path2D; +import java.util.Arrays; import sun.java2d.marlin.DHelpers.IndexStack; import sun.java2d.marlin.DHelpers.PolyStack; 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); } + DPathConsumer2D traceDasher(DPathConsumer2D out) { + return tracerDasher.init(out); + } + DPathConsumer2D detectClosedPath(DPathConsumer2D out) { return cpDetector.init(out); } @@ -499,11 +508,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, @@ -528,6 +545,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; @@ -578,7 +600,9 @@ } stack.pullAll(corners, out); } - out.lineTo(cx0, cy0); + out.lineTo(cox0, coy0); + this.cx0 = cox0; + this.cy0 = coy0; } @Override @@ -603,38 +627,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, @@ -654,22 +708,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 @@ -684,34 +734,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 @@ -719,33 +797,59 @@ 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; } @Override @@ -754,6 +858,261 @@ } } + 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]); + } + } + } + + 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; + } + + 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; + } + } + static final class PathTracer implements DPathConsumer2D { private final String prefix; private DPathConsumer2D out; @@ -807,7 +1166,7 @@ } private void log(final String message) { - System.out.println(prefix + message); + MarlinUtils.logInfo(prefix + message); } @Override --- old/src/java.desktop/share/classes/sun/java2d/marlin/Dasher.java 2018-02-15 13:11:12.344376055 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/Dasher.java 2018-02-15 13:11:12.192371892 +0100 @@ -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 sun.awt.geom.PathConsumer2D; +import sun.java2d.marlin.TransformingPathConsumer2D.CurveBasicMonotonizer; +import sun.java2d.marlin.TransformingPathConsumer2D.CurveClipSplitter; /** * The Dasher class takes a series of linear commands @@ -41,8 +43,9 @@ */ 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; } /** @@ -116,10 +146,13 @@ // Normalize so 0 <= phase < dash[0] int sidx = 0; dashOn = true; + float sum = 0.0f; for (float d : dash) { sum += d; } + this.cycleLen = sum; + float cycles = phase / sum; if (phase < 0.0f) { if (-cycles >= MAX_CYCLES) { @@ -168,6 +201,12 @@ this.recycleDashes = recycleDashes; + if (rdrCtx.doClip) { + this.clipRect = rdrCtx.clipRect; + } else { + this.clipRect = null; + this.cOutCode = 0; + } return this; // fluent API } @@ -205,33 +244,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 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]); + out.quadTo(buf[off ], buf[off + 1], + buf[off + 2], buf[off + 3]); return; case 4: - out.lineTo(buf[off], buf[off+1]); + out.lineTo(buf[off], buf[off + 1]); return; default: } @@ -247,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 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, @@ -268,7 +310,7 @@ } else { if (needsMoveTo) { needsMoveTo = false; - out.moveTo(x0, y0); + out.moveTo(cx0, cy0); } emitSeg(pts, off, type); } @@ -279,8 +321,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) { @@ -306,10 +348,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); - float len = dx*dx + dy*dy; + 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; if (len == 0.0f) { return; } @@ -328,8 +416,7 @@ boolean _dashOn = dashOn; float _phase = phase; - float leftInThisDashSegment; - float d, dashdx, dashdy, p; + float leftInThisDashSegment, d; while (true) { d = _dash[_idx]; @@ -350,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.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); @@ -378,19 +456,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; @@ -402,17 +556,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; } @@ -440,7 +593,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; @@ -463,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 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; @@ -492,7 +666,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; @@ -514,7 +688,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); @@ -522,7 +696,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; @@ -534,11 +708,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; @@ -547,7 +721,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]; @@ -636,7 +810,8 @@ // 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); +// TODO: check NaN is impossible if (n == 1 && !Float.isNaN(nextRoots[0])) { t = nextRoots[0]; } @@ -657,6 +832,16 @@ return t; } + float totalLength() { + while (!done) { + goToNextLeaf(); + } + // reset LengthIterator: + reset(); + + return lenAtNextT; + } + float lastSegLen() { return lastSegLen; } @@ -666,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; @@ -679,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; @@ -701,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(); } @@ -719,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; @@ -727,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; @@ -742,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; + + // 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] = x0; _curCurvepts[1] = y0; - _curCurvepts[2] = x1; _curCurvepts[3] = y1; - _curCurvepts[4] = x2; _curCurvepts[5] = y2; - somethingTo(6); + _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/src/java.desktop/share/classes/sun/java2d/marlin/DoubleArrayCache.java 2018-02-15 13:11:12.696385696 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/DoubleArrayCache.java 2018-02-15 13:11:12.540381423 +0100 @@ -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 @@ -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,12 +229,8 @@ } } - static double[] createArray(final int length, final boolean clean) { - if (clean) { - return new double[length]; - } - // use JDK9 Unsafe.allocateUninitializedArray(class, length): - return (double[]) OffHeapArray.UNSAFE.allocateUninitializedArray(double.class, length); + static double[] createArray(final int length) { + return new double[length]; } static void fill(final double[] array, final int fromIndex, --- old/src/java.desktop/share/classes/sun/java2d/marlin/FloatArrayCache.java 2018-02-15 13:11:13.020394570 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/FloatArrayCache.java 2018-02-15 13:11:12.872390517 +0100 @@ -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 @@ -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,12 +229,8 @@ } } - static float[] createArray(final int length, final boolean clean) { - if (clean) { - return new float[length]; - } - // use JDK9 Unsafe.allocateUninitializedArray(class, length): - return (float[]) OffHeapArray.UNSAFE.allocateUninitializedArray(float.class, length); + static float[] createArray(final int length) { + return new float[length]; } static void fill(final float[] array, final int fromIndex, --- old/src/java.desktop/share/classes/sun/java2d/marlin/Helpers.java 2018-02-15 13:11:13.344403443 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/Helpers.java 2018-02-15 13:11:13.196399391 +0100 @@ -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 sun.java2d.marlin; -import static java.lang.Math.PI; import java.util.Arrays; import sun.awt.geom.PathConsumer2D; import sun.java2d.marlin.stats.Histogram; @@ -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 void subdivide(float[] src, int srcoff, float[] left, int leftoff, - float[] right, int rightoff, int type) + 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(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); } } @@ -614,12 +773,12 @@ e += 2; continue; case TYPE_QUADTO: - io.quadTo(_curves[e+0], _curves[e+1], + io.quadTo(_curves[e], _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; @@ -657,12 +816,12 @@ continue; case TYPE_QUADTO: e -= 4; - io.quadTo(_curves[e+0], _curves[e+1], + io.quadTo(_curves[e], _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; --- old/src/java.desktop/share/classes/sun/java2d/marlin/IntArrayCache.java 2018-02-15 13:11:13.684412755 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/IntArrayCache.java 2018-02-15 13:11:13.532408592 +0100 @@ -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 @@ -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,12 +229,8 @@ } } - static int[] createArray(final int length, final boolean clean) { - if (clean) { - return new int[length]; - } - // use JDK9 Unsafe.allocateUninitializedArray(class, length): - return (int[]) OffHeapArray.UNSAFE.allocateUninitializedArray(int.class, length); + static int[] createArray(final int length) { + return new int[length]; } static void fill(final int[] array, final int fromIndex, --- old/src/java.desktop/share/classes/sun/java2d/marlin/MarlinCache.java 2018-02-15 13:11:14.012421738 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/MarlinCache.java 2018-02-15 13:11:13.856417465 +0100 @@ -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 @@ -43,9 +43,9 @@ // values are stored as int [x|alpha] where alpha is 8 bits static final int RLE_MAX_WIDTH = 1 << (24 - 1); - // 2048 (pixelSize) alpha values (width) x 32 rows (tile) = 64K bytes + // 4096 (pixels) alpha values (width) x 64 rows / 4 (tile) = 64K bytes // x1 instead of 4 bytes (RLE) ie 1/4 capacity or average good RLE compression - static final long INITIAL_CHUNK_ARRAY = TILE_H * INITIAL_PIXEL_DIM; // 64K + static final long INITIAL_CHUNK_ARRAY = TILE_H * INITIAL_PIXEL_WIDTH >> 2; // 64K // The alpha map used by this object (taken out of our map cache) to convert // pixel coverage counts gotten from MarlinCache (which are in the range @@ -292,11 +292,11 @@ // ensure values are in [0; MAX_AA_ALPHA] range if (DO_AA_RANGE_CHECK) { if (val < 0) { - System.out.println("Invalid coverage = " + val); + MarlinUtils.logInfo("Invalid coverage = " + val); val = 0; } if (val > MAX_AA_ALPHA) { - System.out.println("Invalid coverage = " + val); + MarlinUtils.logInfo("Invalid coverage = " + val); val = MAX_AA_ALPHA; } } @@ -460,11 +460,11 @@ // ensure values are in [0; MAX_AA_ALPHA] range if (DO_AA_RANGE_CHECK) { if (val < 0) { - System.out.println("Invalid coverage = " + val); + MarlinUtils.logInfo("Invalid coverage = " + val); val = 0; } if (val > MAX_AA_ALPHA) { - System.out.println("Invalid coverage = " + val); + MarlinUtils.logInfo("Invalid coverage = " + val); val = MAX_AA_ALPHA; } } @@ -630,8 +630,6 @@ final int halfmaxalpha = maxalpha >> 2; for (int i = 0; i <= maxalpha; i++) { alMap[i] = (byte) ((i * 255 + halfmaxalpha) / maxalpha); -// System.out.println("alphaMap[" + i + "] = " -// + Byte.toUnsignedInt(alMap[i])); } return alMap; } --- old/src/java.desktop/share/classes/sun/java2d/marlin/MarlinConst.java 2018-02-15 13:11:14.344430830 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/MarlinConst.java 2018-02-15 13:11:14.188426558 +0100 @@ -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,34 @@ // 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; + // flag to enable float precision correction + static final boolean DO_FIX_FLOAT_PREC = true; + // 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(); @@ -109,16 +120,17 @@ 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); - public static final float NORM_SUBPIXELS - = (float) Math.sqrt(( SUBPIXEL_POSITIONS_X * SUBPIXEL_POSITIONS_X - + SUBPIXEL_POSITIONS_Y * SUBPIXEL_POSITIONS_Y) / 2.0d); + public static final float MIN_SUBPIXELS = 1 << MIN_SUBPIXEL_LG_POSITIONS; public static final int MAX_AA_ALPHA - = SUBPIXEL_POSITIONS_X * SUBPIXEL_POSITIONS_Y; + = (SUBPIXEL_POSITIONS_X * SUBPIXEL_POSITIONS_Y); public static final int TILE_H_LG = MarlinProperties.getTileSize_Log2(); public static final int TILE_H = 1 << TILE_H_LG; // 32 by default --- old/src/java.desktop/share/classes/sun/java2d/marlin/MarlinProperties.java 2018-02-15 13:11:14.668439703 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/MarlinProperties.java 2018-02-15 13:11:14.516435540 +0100 @@ -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 @@ -54,29 +54,41 @@ } /** - * 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("sun.java2d.renderer.pixelsize", 2048, 64, 32 * 1024), + getInteger("sun.java2d.renderer.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("sun.java2d.renderer.pixelHeight", 2176, 64, 32 * 1024), + 64); + } + + /** + * 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("sun.java2d.renderer.subPixel_log2_X", 3, 0, 8); + return getInteger("sun.java2d.renderer.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) @@ -88,22 +100,21 @@ /** * Return the log(2) corresponding to the square tile size in pixels * - * @return 3 (8x8 pixels) < tile size < 8 (256x256 pixels) - * (5 by default ie 32x32 pixels) + * @return 3 (8x8 pixels) < tile size < 10 (1024x1024 pixels) + * (6 by default ie 128x64 pixels) */ public static int getTileSize_Log2() { - return getInteger("sun.java2d.renderer.tileSize_log2", 5, 3, 10); + return getInteger("sun.java2d.renderer.tileSize_log2", 6, 3, 10); } /** * Return the log(2) corresponding to the tile width in pixels * - * @return 3 (8 pixels) < tile with < 8 (256 pixels) - * (by default is given by the square tile size) + * @return 3 (8 pixels) < tile width < 10 (1024 pixels) + * (7 by default ie 128x64 pixels) */ public static int getTileWidth_Log2() { - final int tileSize = getTileSize_Log2(); - return getInteger("sun.java2d.renderer.tileWidth_log2", tileSize, 3, 10); + return getInteger("sun.java2d.renderer.tileWidth_log2", 7, 3, 10); } /** @@ -145,6 +156,18 @@ return getBoolean("sun.java2d.renderer.useSimplifier", "false"); } + public static boolean isUsePathSimplifier() { + return getBoolean("sun.java2d.renderer.usePathSimplifier", "false"); + } + + public static float getPathSimplifierPixelTolerance() { + // default: MIN_PEN_SIZE or less ? + return getFloat("sun.java2d.renderer.pathSimplifier.pixTol", + (1.0f / MarlinConst.MIN_SUBPIXELS), + 1e-3f, + 10.0f); + } + public static boolean isDoClip() { return getBoolean("sun.java2d.renderer.clip", "true"); } @@ -157,6 +180,14 @@ return getBoolean("sun.java2d.renderer.clip.runtime", "true"); } + public static boolean isDoClipSubdivider() { + return getBoolean("sun.java2d.renderer.clip.subdivider", "true"); + } + + public static float getSubdividerMinLength() { + return getFloat("sun.java2d.renderer.clip.subdivider.minLength", 100.0f, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY); + } + // debugging parameters public static boolean isDoStats() { @@ -191,16 +222,20 @@ // quality settings + public static float getCurveLengthError() { + return getFloat("sun.java2d.renderer.curve_len_err", 0.01f, 1e-6f, 1.0f); + } + public static float getCubicDecD2() { - return getFloat("sun.java2d.renderer.cubic_dec_d2", 1.0f, 0.01f, 4.0f); + return getFloat("sun.java2d.renderer.cubic_dec_d2", 1.0f, 1e-5f, 4.0f); } public static float getCubicIncD1() { - return getFloat("sun.java2d.renderer.cubic_inc_d1", 0.4f, 0.01f, 2.0f); + return getFloat("sun.java2d.renderer.cubic_inc_d1", 0.2f, 1e-6f, 1.0f); } public static float getQuadDecD2() { - return getFloat("sun.java2d.renderer.quad_dec_d2", 0.5f, 0.01f, 4.0f); + return getFloat("sun.java2d.renderer.quad_dec_d2", 0.5f, 1e-5f, 4.0f); } // system property utilities --- old/src/java.desktop/share/classes/sun/java2d/marlin/MarlinRenderingEngine.java 2018-02-15 13:11:14.996448684 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/MarlinRenderingEngine.java 2018-02-15 13:11:14.844444522 +0100 @@ -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 @@ -47,7 +47,21 @@ public final class MarlinRenderingEngine extends RenderingEngine implements MarlinConst { - private static enum NormMode { + // 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(); + + private static final float MIN_PEN_SIZE = 1.0f / MIN_SUBPIXELS; + + static final float UPPER_BND = Float.MAX_VALUE / 2.0f; + static final float LOWER_BND = -UPPER_BND; + + private enum NormMode { ON_WITH_AA { @Override PathIterator getNormalizingPathIterator(final RendererContext rdrCtx, @@ -80,18 +94,6 @@ PathIterator src); } - private static final float MIN_PEN_SIZE = 1.0f / NORM_SUBPIXELS; - - 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; - - static final boolean DO_TRACE_PATH = false; - - static final boolean DO_CLIP_RUNTIME_ENABLE = MarlinProperties.isDoClipRuntimeFlag(); - /** * Public constructor */ @@ -419,14 +421,27 @@ pc2d = transformerPC2D.deltaTransformConsumer(pc2d, strokerat); // stroker will adjust the clip rectangle (width / miter limit): - pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit, scale); + pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit, scale, + (dashes == null)); + + // Curve Monotizer: + rdrCtx.monotonizer.init(width); if (dashes != null) { if (!recycleDashes) { dashLen = dashes.length; } + if (DO_TRACE_PATH) { + pc2d = transformerPC2D.traceDasher(pc2d); + } pc2d = rdrCtx.dasher.init(pc2d, dashes, dashLen, dashphase, recycleDashes); + + if (DISABLE_2ND_STROKER_CLIPPING) { + // disable stoker clipping + rdrCtx.stroker.disableClipping(); + } + } else if (rdrCtx.doClip && (caps != Stroker.CAP_BUTT)) { if (DO_TRACE_PATH) { pc2d = transformerPC2D.traceClosedPathDetector(pc2d); @@ -625,6 +640,12 @@ private static void pathTo(final RendererContext rdrCtx, final PathIterator pi, PathConsumer2D pc2d) { + if (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; @@ -1070,8 +1091,10 @@ logInfo("sun.java2d.renderer.edges = " + MarlinConst.INITIAL_EDGES_COUNT); - logInfo("sun.java2d.renderer.pixelsize = " - + MarlinConst.INITIAL_PIXEL_DIM); + logInfo("sun.java2d.renderer.pixelWidth = " + + MarlinConst.INITIAL_PIXEL_WIDTH); + logInfo("sun.java2d.renderer.pixelHeight = " + + MarlinConst.INITIAL_PIXEL_HEIGHT); logInfo("sun.java2d.renderer.subPixel_log2_X = " + MarlinConst.SUBPIXEL_LG_POSITIONS_X); @@ -1101,12 +1124,21 @@ // optimisation parameters logInfo("sun.java2d.renderer.useSimplifier = " + MarlinConst.USE_SIMPLIFIER); + logInfo("sun.java2d.renderer.usePathSimplifier= " + + MarlinConst.USE_PATH_SIMPLIFIER); + logInfo("sun.java2d.renderer.pathSimplifier.pixTol = " + + MarlinProperties.getPathSimplifierPixelTolerance()); logInfo("sun.java2d.renderer.clip = " + MarlinProperties.isDoClip()); logInfo("sun.java2d.renderer.clip.runtime.enable = " + MarlinProperties.isDoClipRuntimeFlag()); + logInfo("sun.java2d.renderer.clip.subdivider = " + + MarlinProperties.isDoClipSubdivider()); + logInfo("sun.java2d.renderer.clip.subdivider.minLength = " + + MarlinProperties.getSubdividerMinLength()); + // debugging parameters logInfo("sun.java2d.renderer.doStats = " + MarlinConst.DO_STATS); @@ -1124,6 +1156,8 @@ + MarlinConst.LOG_UNSAFE_MALLOC); // quality settings + logInfo("sun.java2d.renderer.curve_len_err = " + + MarlinProperties.getCurveLengthError()); logInfo("sun.java2d.renderer.cubic_dec_d2 = " + MarlinProperties.getCubicDecD2()); logInfo("sun.java2d.renderer.cubic_inc_d1 = " --- old/src/java.desktop/share/classes/sun/java2d/marlin/MarlinTileGenerator.java 2018-02-15 13:11:15.328457775 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/MarlinTileGenerator.java 2018-02-15 13:11:15.176453613 +0100 @@ -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 @@ -31,6 +31,8 @@ final class MarlinTileGenerator implements AATileGenerator, MarlinConst { + private static final boolean DISABLE_BLEND = false; + private static final int MAX_TILE_ALPHA_SUM = TILE_W * TILE_H * MAX_AA_ALPHA; private static final int TH_AA_ALPHA_FILL_EMPTY = ((MAX_AA_ALPHA + 1) / 3); // 33% @@ -43,10 +45,10 @@ throw new IllegalStateException("Invalid MAX_TILE_ALPHA_SUM: " + MAX_TILE_ALPHA_SUM); } if (DO_TRACE) { - System.out.println("MAX_AA_ALPHA : " + MAX_AA_ALPHA); - System.out.println("TH_AA_ALPHA_FILL_EMPTY : " + TH_AA_ALPHA_FILL_EMPTY); - System.out.println("TH_AA_ALPHA_FILL_FULL : " + TH_AA_ALPHA_FILL_FULL); - System.out.println("FILL_TILE_W : " + FILL_TILE_W); + MarlinUtils.logInfo("MAX_AA_ALPHA : " + MAX_AA_ALPHA); + MarlinUtils.logInfo("TH_AA_ALPHA_FILL_EMPTY : " + TH_AA_ALPHA_FILL_EMPTY); + MarlinUtils.logInfo("TH_AA_ALPHA_FILL_FULL : " + TH_AA_ALPHA_FILL_FULL); + MarlinUtils.logInfo("FILL_TILE_W : " + FILL_TILE_W); } } @@ -141,6 +143,10 @@ */ @Override public int getTypicalAlpha() { + if (DISABLE_BLEND) { + // always return empty tiles to disable blending operations + return 0x00; + } int al = cache.alphaSumInTile(x); // Note: if we have a filled rectangle that doesn't end on a tile // border, we could still return 0xff, even though al!=maxTileAlphaSum --- old/src/java.desktop/share/classes/sun/java2d/marlin/MarlinUtils.java 2018-02-15 13:11:15.656466757 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/MarlinUtils.java 2018-02-15 13:11:15.504462595 +0100 @@ -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 @@ -60,4 +60,22 @@ th.printStackTrace(System.err); } } + + // From sun.awt.util.ThreadGroupUtils + + /** + * Returns a root thread group. + * Should be called with {@link sun.security.util.SecurityConstants#MODIFY_THREADGROUP_PERMISSION} + * + * @return a root {@code ThreadGroup} + */ + public static ThreadGroup getRootThreadGroup() { + ThreadGroup currentTG = Thread.currentThread().getThreadGroup(); + ThreadGroup parentTG = currentTG.getParent(); + while (parentTG != null) { + currentTG = parentTG; + parentTG = currentTG.getParent(); + } + return currentTG; + } } --- old/src/java.desktop/share/classes/sun/java2d/marlin/MergeSort.java 2018-02-15 13:11:15.980475628 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/MergeSort.java 2018-02-15 13:11:15.832471576 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2015, 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/src/java.desktop/share/classes/sun/java2d/marlin/OffHeapArray.java 2018-02-15 13:11:16.304484498 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/OffHeapArray.java 2018-02-15 13:11:16.152480337 +0100 @@ -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 @@ -31,7 +31,6 @@ /** * - * @author bourgesl */ final class OffHeapArray { --- old/src/java.desktop/share/classes/sun/java2d/marlin/Renderer.java 2018-02-15 13:11:16.632493479 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/Renderer.java 2018-02-15 13:11:16.480489318 +0100 @@ -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 @@ -54,9 +54,9 @@ private static final int SUBPIXEL_TILE = TILE_H << SUBPIXEL_LG_POSITIONS_Y; - // 2048 (pixelSize) pixels (height) x 8 subpixels = 64K + // 2176 pixels (height) x 8 subpixels = 68K static final int INITIAL_BUCKET_ARRAY - = INITIAL_PIXEL_DIM * SUBPIXEL_POSITIONS_Y; + = INITIAL_PIXEL_HEIGHT * SUBPIXEL_POSITIONS_Y; // crossing capacity = edges count / 4 ~ 1024 static final int INITIAL_CROSSING_COUNT = INITIAL_EDGES_COUNT >> 2; @@ -77,13 +77,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 @@ -110,10 +114,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 @@ -180,7 +186,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; @@ -194,7 +200,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 @@ -204,17 +211,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; } @@ -222,7 +224,7 @@ addLine(x0, y0, x2, y2); if (DO_STATS) { - rdrCtx.stats.stat_rdr_quadBreak.add(nL + 1); + rdrCtx.stats.stat_rdr_quadBreak.add(nL); } } @@ -250,34 +252,22 @@ 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 +// && (Math.abs(ddx + dddx) + Math.abs(ddy + dddy) * _SCALE_DY) <= _INC_BND + )) { dx = 2.0f * dx + ddx; dy = 2.0f * dy + ddy; ddx = 4.0f * (ddx + dddx); @@ -290,26 +280,42 @@ 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 +// || (Math.abs(ddx + dddx) + Math.abs(ddy + dddy) * _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); } } @@ -537,8 +543,8 @@ edgeBuckets = edgeBuckets_ref.initial; edgeBucketCounts = edgeBucketCounts_ref.initial; - // 2048 (pixelsize) pixel large - alphaLine_ref = rdrCtx.newCleanIntArrayRef(INITIAL_AA_ARRAY); // 8K + // 4096 pixels large + alphaLine_ref = rdrCtx.newCleanIntArrayRef(INITIAL_AA_ARRAY); // 16K alphaLine = alphaLine_ref.initial; crossings_ref = rdrCtx.newDirtyIntArrayRef(INITIAL_CROSSING_COUNT); // 2K @@ -696,8 +702,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; @@ -709,7 +717,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/src/java.desktop/share/classes/sun/java2d/marlin/RendererContext.java 2018-02-15 13:11:16.968502679 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/RendererContext.java 2018-02-15 13:11:16.820498626 +0100 @@ -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 @@ -31,6 +31,8 @@ import sun.java2d.ReentrantContext; import sun.java2d.marlin.ArrayCacheConst.CacheStats; import sun.java2d.marlin.MarlinRenderingEngine.NormalizingPathIterator; +import sun.java2d.marlin.TransformingPathConsumer2D.CurveBasicMonotonizer; +import sun.java2d.marlin.TransformingPathConsumer2D.CurveClipSplitter; /** * This class is a renderer context dedicated to a single thread @@ -70,6 +72,8 @@ final Stroker stroker; // Simplifies out collinear lines final CollinearSimplifier simplifier = new CollinearSimplifier(); + // Simplifies path + final PathSimplifier pathSimplifier = new PathSimplifier(); final Dasher dasher; final MarlinTileGenerator ptg; final MarlinCache cache; @@ -81,6 +85,10 @@ boolean closedPath = false; // clip rectangle (ymin, ymax, xmin, xmax): final float[] clipRect = new float[4]; + // CurveBasicMonotonizer instance + final CurveBasicMonotonizer monotonizer; + // CurveClipSplitter instance + final CurveClipSplitter curveClipSplitter; // Array caches: /* clean int[] cache (zero-filled) = 5 refs */ @@ -121,6 +129,10 @@ nPCPathIterator = new NormalizingPathIterator.NearestPixelCenter(float6); nPQPathIterator = new NormalizingPathIterator.NearestPixelQuarter(float6); + // curve monotonizer & clip subdivider (before transformerPC2D init) + monotonizer = new CurveBasicMonotonizer(this); + curveClipSplitter = new CurveClipSplitter(this); + // MarlinRenderingEngine.TransformingPathConsumer2D transformerPC2D = new TransformingPathConsumer2D(this); --- old/src/java.desktop/share/classes/sun/java2d/marlin/RendererStats.java 2018-02-15 13:11:17.288511439 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/RendererStats.java 2018-02-15 13:11:17.140507387 +0100 @@ -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 @@ -36,7 +36,6 @@ import sun.java2d.marlin.stats.Histogram; import sun.java2d.marlin.stats.Monitor; import sun.java2d.marlin.stats.StatLong; -import sun.awt.util.ThreadGroupUtils; /** * This class gathers global rendering statistics for debugging purposes only @@ -359,7 +358,7 @@ AccessController.doPrivileged( (PrivilegedAction) () -> { final Thread hook = new Thread( - ThreadGroupUtils.getRootThreadGroup(), + MarlinUtils.getRootThreadGroup(), new Runnable() { @Override public void run() { --- old/src/java.desktop/share/classes/sun/java2d/marlin/Stroker.java 2018-02-15 13:11:17.624520637 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/Stroker.java 2018-02-15 13:11:17.472516476 +0100 @@ -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 sun.awt.geom.PathConsumer2D; import sun.java2d.marlin.Helpers.PolyStack; +import sun.java2d.marlin.TransformingPathConsumer2D.CurveBasicMonotonizer; +import sun.java2d.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; } /** @@ -141,6 +142,7 @@ * JOIN_BEVEL. * @param miterLimit the desired miter limit * @param scale scaling factor applied to clip boundaries + * @param subdivideCurves true to indicate to subdivide curves, false if dasher does * @return this instance */ Stroker init(final PathConsumer2D pc2d, @@ -148,12 +150,15 @@ final int capStyle, final int joinStyle, final float miterLimit, - final float scale) + final float scale, + 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; @@ -191,6 +196,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; @@ -199,6 +213,12 @@ return this; // fluent API } + void disableClipping() { + this.clipRect = null; + this.cOutCode = 0; + this.sOutCode = 0; + } + /** * Disposes this stroker: * clean up before reusing this instance @@ -215,10 +235,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); } } @@ -250,19 +268,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; } @@ -272,7 +291,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, @@ -383,7 +402,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; @@ -402,8 +421,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) @@ -412,7 +431,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; @@ -430,20 +449,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) || @@ -461,8 +481,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]; @@ -480,7 +499,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; @@ -496,7 +515,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) { @@ -523,16 +542,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; @@ -754,10 +797,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); @@ -767,18 +807,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]; @@ -786,14 +827,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 @@ -802,7 +845,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]; @@ -816,6 +859,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; @@ -831,6 +875,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; @@ -944,10 +989,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]; @@ -968,6 +1014,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; @@ -977,6 +1024,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; @@ -992,151 +1040,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; } @@ -1144,17 +1152,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; @@ -1178,8 +1194,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; @@ -1191,74 +1207,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; @@ -1282,8 +1325,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.0f; --- old/src/java.desktop/share/classes/sun/java2d/marlin/TransformingPathConsumer2D.java 2018-02-15 13:11:17.968530054 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/TransformingPathConsumer2D.java 2018-02-15 13:11:17.820526003 +0100 @@ -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 @@ -28,11 +28,15 @@ import sun.awt.geom.PathConsumer2D; import java.awt.geom.AffineTransform; import java.awt.geom.Path2D; +import java.util.Arrays; import sun.java2d.marlin.Helpers.IndexStack; import sun.java2d.marlin.Helpers.PolyStack; 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); } + PathConsumer2D traceDasher(PathConsumer2D out) { + return tracerDasher.init(out); + } + PathConsumer2D detectClosedPath(PathConsumer2D out) { return cpDetector.init(out); } @@ -500,11 +509,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, @@ -529,6 +546,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; @@ -579,7 +601,9 @@ } stack.pullAll(corners, out); } - out.lineTo(cx0, cy0); + out.lineTo(cox0, coy0); + this.cx0 = cox0; + this.cy0 = coy0; } @Override @@ -604,38 +628,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, @@ -655,22 +709,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 @@ -685,34 +735,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 @@ -720,33 +798,59 @@ 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; } @Override @@ -755,6 +859,261 @@ } } + 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]); + } + } + } + + 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; + } + + void init(final float lineWidth) { + this.lw2 = (lineWidth * lineWidth) / 4.0f; + } + + CurveBasicMonotonizer curve(final float x0, final float y0, + final float x1, final float y1, + final float x2, final float y2, + final float x3, final float y3) + { + final float[] mid = middle; + mid[0] = x0; mid[1] = y0; + mid[2] = x1; mid[3] = y1; + mid[4] = x2; mid[5] = y2; + mid[6] = x3; mid[7] = y3; + + final float[] subTs = subdivTs; + final int nSplits = Helpers.findSubdivPoints(curve, mid, subTs, 8, lw2); + + float prevT = 0.0f; + for (int i = 0, off = 0; i < nSplits; i++, off += 6) { + final float t = subTs[i]; + + Helpers.subdivideCubicAt((t - prevT) / (1.0f - prevT), + mid, off, mid, off, off + 6); + prevT = t; + } + + this.nbSplits = nSplits; + return this; + } + + CurveBasicMonotonizer quad(final float x0, final float y0, + final float x1, final float y1, + final float x2, final float y2) + { + final float[] mid = middle; + mid[0] = x0; mid[1] = y0; + mid[2] = x1; mid[3] = y1; + mid[4] = x2; mid[5] = y2; + + final float[] subTs = subdivTs; + final int nSplits = Helpers.findSubdivPoints(curve, mid, subTs, 6, lw2); + + float prevt = 0.0f; + for (int i = 0, off = 0; i < nSplits; i++, off += 4) { + final float t = subTs[i]; + Helpers.subdivideQuadAt((t - prevt) / (1.0f - prevt), + mid, off, mid, off, off + 4); + prevt = t; + } + + this.nbSplits = nSplits; + return this; + } + } + static final class PathTracer implements PathConsumer2D { private final String prefix; private PathConsumer2D out; @@ -808,7 +1167,7 @@ } private void log(final String message) { - System.out.println(prefix + message); + MarlinUtils.logInfo(prefix + message); } @Override --- old/src/java.desktop/share/classes/sun/java2d/marlin/Version.java 2018-02-15 13:11:18.400541880 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/Version.java 2018-02-15 13:11:18.252537829 +0100 @@ -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 = "marlin-0.8.2-Unsafe-OpenJDK"; + private static final String VERSION = "marlin-0.9.1-Unsafe-OpenJDK"; public static String getVersion() { return VERSION; --- old/src/java.desktop/share/classes/sun/java2d/marlin/stats/Histogram.java 2018-02-15 13:11:18.824553487 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/stats/Histogram.java 2018-02-15 13:11:18.672549325 +0100 @@ -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/src/java.desktop/share/classes/sun/java2d/marlin/stats/StatLong.java 2018-02-15 13:11:19.156562574 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/stats/StatLong.java 2018-02-15 13:11:19.008558523 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -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/src/java.desktop/share/classes/sun/java2d/pipe/RenderingEngine.java 2018-02-15 13:11:19.480571442 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/pipe/RenderingEngine.java 2018-02-15 13:11:19.332567391 +0100 @@ -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,7 +30,6 @@ import java.awt.geom.PathIterator; import java.awt.geom.AffineTransform; -import java.security.PrivilegedAction; import java.security.AccessController; import sun.security.action.GetPropertyAction; --- old/test/jdk/sun/java2d/marlin/ClipShapeTest.java 2018-02-15 13:11:19.808580420 +0100 +++ new/test/jdk/sun/java2d/marlin/ClipShapeTest.java 2018-02-15 13:11:19.656576260 +0100 @@ -54,13 +54,24 @@ * @bug 8191814 * @summary Verifies that Marlin rendering generates the same * images with and without clipping optimization with all possible - * stroke (cap/join) and fill modes (EO rules) + * stroke (cap/join) and/or dashes or fill modes (EO rules) + * for paths made of either 9 lines, 4 quads, 2 cubics (random) * Note: Use the argument -slow to run more intensive tests (too much time) - * @run main/othervm/timeout=120 -Dsun.java2d.renderer=sun.java2d.marlin.MarlinRenderingEngine ClipShapeTest - * @run main/othervm/timeout=120 -Dsun.java2d.renderer=sun.java2d.marlin.DMarlinRenderingEngine ClipShapeTest - */ + * + * @run main/othervm/timeout=120 -Dsun.java2d.renderer=sun.java2d.marlin.MarlinRenderingEngine ClipShapeTest -poly + * @run main/othervm/timeout=240 -Dsun.java2d.renderer=sun.java2d.marlin.MarlinRenderingEngine ClipShapeTest -poly -doDash + * @run main/othervm/timeout=120 -Dsun.java2d.renderer=sun.java2d.marlin.MarlinRenderingEngine ClipShapeTest -cubic + * @run main/othervm/timeout=240 -Dsun.java2d.renderer=sun.java2d.marlin.MarlinRenderingEngine ClipShapeTest -cubic -doDash + * @run main/othervm/timeout=120 -Dsun.java2d.renderer=sun.java2d.marlin.DMarlinRenderingEngine ClipShapeTest -poly + * @run main/othervm/timeout=240 -Dsun.java2d.renderer=sun.java2d.marlin.DMarlinRenderingEngine ClipShapeTest -poly -doDash + * @run main/othervm/timeout=120 -Dsun.java2d.renderer=sun.java2d.marlin.DMarlinRenderingEngine ClipShapeTest -cubic + * @run main/othervm/timeout=240 -Dsun.java2d.renderer=sun.java2d.marlin.DMarlinRenderingEngine ClipShapeTest -cubic -doDash +*/ public final class ClipShapeTest { + static boolean TX_SCALE = false; + static boolean TX_SHEAR = false; + static final boolean TEST_STROKER = true; static final boolean TEST_FILLER = true; @@ -73,18 +84,23 @@ static final int TESTH = 100; // shape settings: - static final ShapeMode SHAPE_MODE = ShapeMode.NINE_LINE_POLYS; + static ShapeMode SHAPE_MODE = ShapeMode.NINE_LINE_POLYS; + + static int THRESHOLD_DELTA; + static long THRESHOLD_NBPIX; + static final boolean SHAPE_REPEAT = true; // dump path on console: static final boolean DUMP_SHAPE = true; - static final boolean SHOW_DETAILS = true; + static final boolean SHOW_DETAILS = false; // disabled static final boolean SHOW_OUTLINE = true; static final boolean SHOW_POINTS = true; static final boolean SHOW_INFO = false; 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 = false; @@ -109,24 +125,13 @@ static final File OUTPUT_DIR = new File("."); - /** - * Test - * @param args - */ - public static void main(String[] args) { - boolean runSlowTests = (args.length != 0 && "-slow".equals(args[0])); - - if (runSlowTests) { - NUM_TESTS = 20000; // or 100000 (very slow) - USE_DASHES = true; - USE_VAR_STROKE = true; - } + static final AtomicBoolean isMarlin = new AtomicBoolean(); + static final AtomicBoolean isClipRuntime = new AtomicBoolean(); + static { Locale.setDefault(Locale.US); - // Get Marlin runtime state from its log: - final AtomicBoolean isMarlin = new AtomicBoolean(); - final AtomicBoolean isClipRuntime = new AtomicBoolean(); + // FIRST: Get Marlin runtime state from its log: // initialize j.u.l Looger: final Logger log = Logger.getLogger("sun.java2d.marlin"); @@ -171,6 +176,95 @@ System.setProperty("sun.java2d.renderer.clip", "false"); System.setProperty("sun.java2d.renderer.clip.runtime.enable", "true"); + // enable subdivider: + System.setProperty("sun.java2d.renderer.clip.subdivider", "true"); + + // disable min length check: always subdivide curves at clip edges + System.setProperty("sun.java2d.renderer.clip.subdivider.minLength", "-1"); + + // If any curve, increase curve accuracy: + // curve length max error: + System.setProperty("sun.java2d.renderer.curve_len_err", "1e-4"); + + // quad max error: + System.setProperty("sun.java2d.renderer.quad_dec_d2", "5e-4"); + + // cubic min/max error: + System.setProperty("sun.java2d.renderer.cubic_dec_d2", "1e-3"); + System.setProperty("sun.java2d.renderer.cubic_inc_d1", "1e-4"); // or disabled ~ 1e-6 + } + + /** + * Test + * @param args + */ + public static void main(String[] args) { + boolean runSlowTests = false; + + for (String arg : args) { + if ("-slow".equals(arg)) { + System.out.println("slow: enabled."); + runSlowTests = true; + } else if ("-doScale".equals(arg)) { + System.out.println("doScale: enabled."); + TX_SCALE = true; + } else if ("-doShear".equals(arg)) { + System.out.println("doShear: enabled."); + TX_SHEAR = true; + } else if ("-doDash".equals(arg)) { + System.out.println("doDash: enabled."); + USE_DASHES = true; + } else if ("-doVarStroke".equals(arg)) { + System.out.println("doVarStroke: enabled."); + USE_VAR_STROKE = true; + } + // shape mode: + else 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: + THRESHOLD_DELTA = 32; // / 256 + THRESHOLD_NBPIX = 128; // / 10000 + break; + case FOUR_QUADS: + case MIXED: + // Define uncertainty for quads: + // curve subdivision causes curves to be smaller + // then curve offsets are different (more accurate) + THRESHOLD_DELTA = 64; // 64 / 256 + THRESHOLD_NBPIX = 256; // 256 / 10000 + break; + default: + // Define uncertainty for lines: + // float variant have higher uncertainty + THRESHOLD_DELTA = 8; + THRESHOLD_NBPIX = 8; + } + + 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("ClipShapeTests: image = " + TESTW + " x " + TESTH); int failures = 0; @@ -179,14 +273,21 @@ // TODO: test affine transforms ? if (TEST_STROKER) { - final float[][] dashArrays = (USE_DASHES) - ? new float[][]{null, new float[]{1f, 2f}} + 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.toString(dashArrays)); + System.out.println("dashes: " + Arrays.deepToString(dashArrays)); final float[] strokeWidths = (USE_VAR_STROKE) - ? new float[5] : new float[]{8f}; + ? new float[5] : + new float[]{10f}; int nsw = 0; if (USE_VAR_STROKE) { @@ -290,22 +391,20 @@ final double ratio = (100.0 * testCtx.histPix.count) / testCtx.histAll.count; System.out.println("Diff ratio: " + testName + " = " + trimTo3Digits(ratio) + " %"); - if (false) { - saveImage(diffImage, OUTPUT_DIR, testName + "-diff.png"); - } - - if (DUMP_SHAPE) { - dumpShape(p2d); - } if (nd < MAX_SHOW_FRAMES) { if (SHOW_DETAILS) { paintShapeDetails(g2dOff, p2d); paintShapeDetails(g2dOn, p2d); } - saveImage(imgOff, OUTPUT_DIR, testName + "-off.png"); - saveImage(imgOn, OUTPUT_DIR, testName + "-on.png"); - saveImage(diffImage, OUTPUT_DIR, testName + "-diff.png"); + if (nd < MAX_SAVE_FRAMES) { + if (DUMP_SHAPE) { + dumpShape(p2d); + } + saveImage(imgOff, OUTPUT_DIR, testName + "-off.png"); + saveImage(imgOn, OUTPUT_DIR, testName + "-on.png"); + saveImage(diffImage, OUTPUT_DIR, testName + "-diff.png"); + } } } } @@ -351,6 +450,15 @@ } g2d.setColor(Color.GRAY); + // Test scale + if (TX_SCALE) { + g2d.scale(1.2, 1.2); + } + // Test shear + if (TX_SHEAR) { + g2d.shear(0.1, 0.2); + } + return g2d; } @@ -470,6 +578,8 @@ } break; case PathIterator.SEG_LINETO: + case PathIterator.SEG_QUADTO: + case PathIterator.SEG_CUBICTO: if (SHOW_POINTS) { g2d.setColor((nLine % 2 == 0) ? COLOR_LINETO_ODD : COLOR_LINETO_EVEN); } @@ -515,6 +625,12 @@ 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; @@ -580,10 +696,54 @@ @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 - + ", strokeWidth=" + strokeWidth + ", strokeCap=" + strokeCap + ", strokeJoin=" + strokeJoin - + ((dashes != null) ? ", dashes: " + Arrays.toString(dashes) : "") - + ", windingRule=" + windingRule + '}'; + + ", 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 ""; + } } } @@ -618,16 +778,23 @@ // max difference on grayscale values: v = (int) Math.ceil(Math.abs(dg / 3.0)); - aDifPix[i] = toInt(v, v, v); +// TODO: count warnings + if (v <= THRESHOLD_DELTA) { + aDifPix[i] = 0; + } else { + aDifPix[i] = toInt(v, v, v); - localCtx.add(v); + localCtx.add(v); + } globalCtx.add(v); } - if (!localCtx.isDiff()) { + if (!localCtx.isDiff() || (localCtx.histPix.count <= THRESHOLD_NBPIX)) { return null; } + localCtx.dump(); + return diffImage; } --- /dev/null 2018-02-15 12:15:18.181666552 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/DPathSimplifier.java 2018-02-15 13:11:19.992585456 +0100 @@ -0,0 +1,136 @@ +/* + * 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 sun.java2d.marlin; + +final class DPathSimplifier implements DPathConsumer2D { + + // distance threshold in pixels (device) + private static final double PIX_THRESHOLD = MarlinProperties.getPathSimplifierPixelTolerance(); + + private static final double SQUARE_TOLERANCE = PIX_THRESHOLD * PIX_THRESHOLD; + + // members: + private DPathConsumer2D delegate; + private double cx, cy; + + DPathSimplifier() { + } + + DPathSimplifier init(final DPathConsumer2D delegate) { + this.delegate = delegate; + return this; // fluent API + } + + @Override + public void pathDone() { + delegate.pathDone(); + } + + @Override + public void closePath() { + delegate.closePath(); + } + + @Override + public long getNativeConsumer() { + return 0; + } + + @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) { + return; + } + } + delegate.quadTo(x1, y1, xe, ye); + // final end point: + cx = xe; + cy = ye; + } + + @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) { + return; + } + } + } + delegate.curveTo(x1, y1, x2, y2, xe, ye); + // final end point: + cx = xe; + cy = ye; + } + + @Override + public void moveTo(final double xe, final double ye) { + delegate.moveTo(xe, ye); + // starting point: + 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) { + return; + } + delegate.lineTo(xe, ye); + // final end point: + cx = xe; + cy = ye; + } +} --- /dev/null 2018-02-15 12:15:18.181666552 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/PathSimplifier.java 2018-02-15 13:11:20.328594652 +0100 @@ -0,0 +1,138 @@ +/* + * 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 sun.java2d.marlin; + +import sun.awt.geom.PathConsumer2D; + +final class PathSimplifier implements PathConsumer2D { + + // distance threshold in pixels (device) + private static final float PIX_THRESHOLD = MarlinProperties.getPathSimplifierPixelTolerance(); + + private static final float SQUARE_TOLERANCE = PIX_THRESHOLD * PIX_THRESHOLD; + + // members: + private PathConsumer2D delegate; + private float cx, cy; + + PathSimplifier() { + } + + PathSimplifier init(final PathConsumer2D delegate) { + this.delegate = delegate; + return this; // fluent API + } + + @Override + public void pathDone() { + delegate.pathDone(); + } + + @Override + public void closePath() { + delegate.closePath(); + } + + @Override + public long getNativeConsumer() { + return 0; + } + + @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) { + return; + } + } + delegate.quadTo(x1, y1, xe, ye); + // final end point: + cx = xe; + cy = ye; + } + + @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) { + return; + } + } + } + delegate.curveTo(x1, y1, x2, y2, xe, ye); + // final end point: + cx = xe; + cy = ye; + } + + @Override + public void moveTo(final float xe, final float ye) { + delegate.moveTo(xe, ye); + // starting point: + 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) { + return; + } + delegate.lineTo(xe, ye); + // final end point: + cx = xe; + cy = ye; + } +}