# HG changeset patch # User dlila # Date 1297174969 18000 # Node ID e2932d8114cb28583bfa7c041c8391e5769c7985 # Parent c582063ba5bb760ed88f0224cc6ab21db5bde89c 7016856: dashing performance was reduced during latest changes to the OpenJDK rasterizer Summary: Optimized dashing, rasterizing, and the flow of transformed coordinates Reviewed-by: flar diff -r c582063ba5bb -r e2932d8114cb jdk/src/share/classes/sun/java2d/pisces/Curve.java --- a/jdk/src/share/classes/sun/java2d/pisces/Curve.java Thu Feb 03 19:15:30 2011 -0800 +++ b/jdk/src/share/classes/sun/java2d/pisces/Curve.java Tue Feb 08 09:22:49 2011 -0500 @@ -27,7 +27,7 @@ import java.util.Iterator; -class Curve { +final class Curve { float ax, ay, bx, by, cx, cy, dx, dy; float dax, day, dbx, dby; @@ -101,14 +101,6 @@ return t * (t * day + dby) + cy; } - private float ddxat(float t) { - return 2 * dax * t + dbx; - } - - private float ddyat(float t) { - return 2 * day * t + dby; - } - int dxRoots(float[] roots, int off) { return Helpers.quadraticRoots(dax, dbx, cx, roots, off); } @@ -131,17 +123,17 @@ // 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, final float err) { + private int perpendiculardfddf(float[] pts, int off) { assert pts.length >= off + 4; - // these are the coefficients of g(t): + // 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*(dax*dax + day*day); final float b = 3*(dax*dbx + day*dby); final float c = 2*(dax*cx + day*cy) + dbx*dbx + dby*dby; final float d = dbx*cx + dby*cy; - // TODO: We might want to divide the polynomial by a to make the - // coefficients smaller. This won't change the roots. - return Helpers.cubicRootsInAB(a, b, c, d, pts, off, err, 0f, 1f); + return Helpers.cubicRootsInAB(a, b, c, d, pts, off, 0f, 1f); } // Tries to find the roots of the function ROC(t)-w in [0, 1). It uses @@ -161,7 +153,7 @@ // 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, err); + int numPerpdfddf = perpendiculardfddf(roots, off); float t0 = 0, ft0 = ROCsq(t0) - w*w; roots[off + numPerpdfddf] = 1f; // always check interval end points numPerpdfddf++; @@ -189,8 +181,9 @@ // A slight modification of the false position algorithm on wikipedia. // This only works for the ROCsq-x functions. It might be nice to have // the function as an argument, but that would be awkward in java6. - // It is something to consider for java7, depending on how closures - // and function objects turn out. Same goes for the newton's method + // TODO: It is something to consider for java8 (or whenever lambda + // expressions make it into the language), depending on how closures + // and turn out. Same goes for the newton's method // algorithm in Helpers.java private float falsePositionROCsqMinusX(float x0, float x1, final float x, final float err) @@ -203,7 +196,7 @@ for (int i = 0; i < iterLimit && Math.abs(t - s) > err * Math.abs(t + s); i++) { r = (fs * t - ft * s) / (fs - ft); fr = ROCsq(r) - x; - if (fr * ft > 0) {// have the same sign + if (sameSign(fr, ft)) { ft = fr; t = r; if (side < 0) { fs /= (1 << (-side)); @@ -226,55 +219,65 @@ return r; } + private static boolean sameSign(double x, double y) { + // another way is to test if x*y > 0. This is bad for small x, y. + return (x < 0 && y < 0) || (x > 0 && y > 0); + } + // 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) { - final float dx = dxat(t); - final float dy = dyat(t); - final float ddx = ddxat(t); - final float ddy = ddyat(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 * dax * t + dbx; + final float ddy = 2 * day * t + dby; final float dx2dy2 = dx*dx + dy*dy; final float ddx2ddy2 = ddx*ddx + ddy*ddy; final float ddxdxddydy = ddx*dx + ddy*dy; - float ret = ((dx2dy2*dx2dy2) / (dx2dy2 * ddx2ddy2 - ddxdxddydy*ddxdxddydy))*dx2dy2; - return ret; + return dx2dy2*((dx2dy2*dx2dy2) / (dx2dy2 * ddx2ddy2 - ddxdxddydy*ddxdxddydy)); } - // curve to be broken should be in pts[0] - // this will change the contents of both pts and Ts + // curve to be broken should be in pts + // this will change the contents of pts but not Ts // TODO: There's no reason for Ts to be an array. All we need is a sequence // of t values at which to subdivide. An array statisfies this condition, // but is unnecessarily restrictive. Ts should be an Iterator instead. // Doing this will also make dashing easier, since we could easily make // LengthIterator an Iterator and feed it to this function to simplify // the loop in Dasher.somethingTo. - static Iterator breakPtsAtTs(final float[][] pts, final int type, + static Iterator breakPtsAtTs(final float[] pts, final int type, final float[] Ts, final int numTs) { - assert pts.length >= 2 && pts[0].length >= 8 && numTs <= Ts.length; - return new Iterator() { - int nextIdx = 0; + assert pts.length >= 2*type && numTs <= Ts.length; + return new Iterator() { + // these prevent object creation and destruction during autoboxing. + // Because of this, the compiler should be able to completely + // eliminate the boxing costs. + final Integer i0 = 0; + final Integer itype = type; int nextCurveIdx = 0; + Integer curCurveOff = i0; float prevT = 0; @Override public boolean hasNext() { return nextCurveIdx < numTs + 1; } - @Override public float[] next() { - float[] ret; + @Override public Integer next() { + Integer ret; if (nextCurveIdx < numTs) { float curT = Ts[nextCurveIdx]; float splitT = (curT - prevT) / (1 - prevT); Helpers.subdivideAt(splitT, - pts[nextIdx], 0, - pts[nextIdx], 0, - pts[1-nextIdx], 0, type); - updateTs(Ts, Ts[nextCurveIdx], nextCurveIdx + 1, numTs - nextCurveIdx - 1); - ret = pts[nextIdx]; - nextIdx = 1 - nextIdx; + pts, curCurveOff, + pts, 0, + pts, type, type); + prevT = curT; + ret = i0; + curCurveOff = itype; } else { - ret = pts[nextIdx]; + ret = curCurveOff; } nextCurveIdx++; return ret; @@ -283,12 +286,5 @@ @Override public void remove() {} }; } - - // precondition: ts[off]...ts[off+len-1] must all be greater than t. - private static void updateTs(float[] ts, final float t, final int off, final int len) { - for (int i = off; i < off + len; i++) { - ts[i] = (ts[i] - t) / (1 - t); - } - } } diff -r c582063ba5bb -r e2932d8114cb jdk/src/share/classes/sun/java2d/pisces/Dasher.java --- a/jdk/src/share/classes/sun/java2d/pisces/Dasher.java Thu Feb 03 19:15:30 2011 -0800 +++ b/jdk/src/share/classes/sun/java2d/pisces/Dasher.java Tue Feb 08 09:22:49 2011 -0500 @@ -38,7 +38,7 @@ * semantics are unclear. * */ -public class Dasher implements sun.awt.geom.PathConsumer2D { +final class Dasher implements sun.awt.geom.PathConsumer2D { private final PathConsumer2D out; private final float[] dash; @@ -169,7 +169,7 @@ float dx = x1 - x0; float dy = y1 - y0; - float len = (float) Math.hypot(dx, dy); + float len = (float) Math.sqrt(dx*dx + dy*dy); if (len == 0) { return; @@ -226,7 +226,7 @@ return; } if (li == null) { - li = new LengthIterator(4, 0.0001f); + li = new LengthIterator(4, 0.01f); } li.initializeIterationOnCurve(curCurvepts, type); @@ -237,9 +237,9 @@ while ((t = li.next(leftInThisDashSegment)) < 1) { if (t != 0) { Helpers.subdivideAt((t - lastSplitT) / (1 - lastSplitT), - curCurvepts, curCurveoff, - curCurvepts, 0, - curCurvepts, type, type); + curCurvepts, curCurveoff, + curCurvepts, 0, + curCurvepts, type, type); lastSplitT = t; goTo(curCurvepts, 2, type); curCurveoff = type; @@ -307,6 +307,11 @@ private int recLevel; private boolean done; + // the lengths of the lines of the control polygon. Only its first + // curveType/2 - 1 elements are valid. This is an optimization. See + // next(float) for more detail. + private float[] curLeafCtrlPolyLengths = new float[3]; + public LengthIterator(int reclimit, float err) { this.limit = reclimit; this.minTincrement = 1f / (1 << limit); @@ -344,11 +349,52 @@ this.lastSegLen = 0; } + // 0 == false, 1 == true, -1 == invalid cached value. + private int cachedHaveLowAcceleration = -1; + + private boolean haveLowAcceleration(float err) { + if (cachedHaveLowAcceleration == -1) { + final float len1 = curLeafCtrlPolyLengths[0]; + final float len2 = curLeafCtrlPolyLengths[1]; + // the test below is equivalent to !within(len1/len2, 1, err). + // It is using a multiplication instead of a division, so it + // should be a bit faster. + if (!Helpers.within(len1, len2, err*len2)) { + cachedHaveLowAcceleration = 0; + return false; + } + if (curveType == 8) { + final float len3 = curLeafCtrlPolyLengths[2]; + // if len1 is close to 2 and 2 is close to 3, that probably + // means 1 is close to 3 so the second part of this test might + // not be needed, but it doesn't hurt to include it. + if (!(Helpers.within(len2, len3, err*len3) && + Helpers.within(len1, len3, err*len3))) { + cachedHaveLowAcceleration = 0; + return false; + } + } + cachedHaveLowAcceleration = 1; + return true; + } + + return (cachedHaveLowAcceleration == 1); + } + + // we want to avoid allocations/gc so we keep this array so we + // can put roots in it, + private float[] nextRoots = new float[4]; + + // caches the coefficients of the current leaf in its flattened + // form (see inside next() for what that means). The cache is + // invalid when it's third element is negative, since in any + // valid flattened curve, this would be >= 0. + private float[] flatLeafCoefCache = new float[] {0, 0, -1, 0}; // returns the t value where the remaining curve should be split in // order for the left subdivided curve to have length len. If len // is >= than the length of the uniterated curve, it returns 1. - public float next(float len) { - float targetLength = lenAtLastSplit + len; + public float next(final float len) { + final float targetLength = lenAtLastSplit + len; while(lenAtNextT < targetLength) { if (done) { lastSegLen = lenAtNextT - lenAtLastSplit; @@ -357,8 +403,46 @@ goToNextLeaf(); } lenAtLastSplit = targetLength; - float t = binSearchForLen(lenAtLastSplit - lenAtLastT, - recCurveStack[recLevel], curveType, lenAtNextT - lenAtLastT, ERR); + final float leaflen = lenAtNextT - lenAtLastT; + float t = (targetLength - lenAtLastT) / leaflen; + + // cubicRootsInAB is a fairly expensive call, so we just don't do it + // if the acceleration in this section of the curve is small enough. + if (!haveLowAcceleration(0.05f)) { + // We flatten the current leaf along the x axis, so that we're + // left with a, b, c which define a 1D Bezier curve. We then + // solve this to get the parameter of the original leaf that + // gives us the desired length. + + if (flatLeafCoefCache[2] < 0) { + float x = 0+curLeafCtrlPolyLengths[0], + y = x+curLeafCtrlPolyLengths[1]; + if (curveType == 8) { + float z = y + curLeafCtrlPolyLengths[2]; + flatLeafCoefCache[0] = 3*(x - y) + z; + flatLeafCoefCache[1] = 3*(y - 2*x); + flatLeafCoefCache[2] = 3*x; + flatLeafCoefCache[3] = -z; + } else if (curveType == 6) { + flatLeafCoefCache[0] = 0f; + flatLeafCoefCache[1] = y - 2*x; + flatLeafCoefCache[2] = 2*x; + flatLeafCoefCache[3] = -y; + } + } + float a = flatLeafCoefCache[0]; + float b = flatLeafCoefCache[1]; + float c = flatLeafCoefCache[2]; + float d = t*flatLeafCoefCache[3]; + + // we use cubicRootsInAB here, because we want only roots in 0, 1, + // and our quadratic root finder doesn't filter, so it's just a + // matter of convenience. + int n = Helpers.cubicRootsInAB(a, b, c, d, nextRoots, 0, 0, 1); + if (n == 1 && !Float.isNaN(nextRoots[0])) { + t = nextRoots[0]; + } + } // t is relative to the current leaf, so we must make it a valid parameter // of the original curve. t = t * (nextT - lastT) + lastT; @@ -379,36 +463,6 @@ return lastSegLen; } - // Returns t such that if leaf is subdivided at t the left - // curve will have length len. leafLen must be the length of leaf. - private static Curve bsc = new Curve(); - private static float binSearchForLen(float len, float[] leaf, int type, - float leafLen, float err) - { - assert len <= leafLen; - bsc.set(leaf, type); - float errBound = err*len; - float left = 0, right = 1; - while (left < right) { - float m = (left + right) / 2; - if (m == left || m == right) { - return m; - } - float x = bsc.xat(m); - float y = bsc.yat(m); - float leftLen = Helpers.linelen(leaf[0], leaf[1], x, y); - if (Math.abs(leftLen - len) < errBound) { - return m; - } - if (leftLen < len) { - left = m; - } else { - right = m; - } - } - return left; - } - // go to the next leaf (in an inorder traversal) in the recursion tree // preconditions: must be on a leaf, and that leaf must not be the root. private void goToNextLeaf() { @@ -437,6 +491,9 @@ lenAtLastT = lenAtNextT; nextT += (1 << (limit - recLevel)) * minTincrement; lenAtNextT += len; + // invalidate caches + flatLeafCoefCache[2] = -1; + cachedHaveLowAcceleration = -1; } else { Helpers.subdivide(recCurveStack[recLevel], 0, recCurveStack[recLevel+1], 0, @@ -450,11 +507,24 @@ // this is a bit of a hack. It returns -1 if we're not on a leaf, and // the length of the leaf if we are on a leaf. private float onLeaf() { - float polylen = Helpers.polyLineLength(recCurveStack[recLevel], 0, curveType); - float linelen = Helpers.linelen(recCurveStack[recLevel][0], recCurveStack[recLevel][1], - recCurveStack[recLevel][curveType - 2], recCurveStack[recLevel][curveType - 1]); - return (polylen - linelen < ERR || recLevel == limit) ? - (polylen + linelen)/2 : -1; + float[] curve = recCurveStack[recLevel]; + float polyLen = 0; + + 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 len = Helpers.linelen(x0, y0, x1, y1); + polyLen += len; + curLeafCtrlPolyLengths[i/2 - 1] = len; + x0 = x1; + y0 = y1; + } + + final float lineLen = Helpers.linelen(curve[0], curve[1], curve[curveType-2], curve[curveType-1]); + if (polyLen - lineLen < ERR || recLevel == limit) { + return (polyLen + lineLen)/2; + } + return -1; } } diff -r c582063ba5bb -r e2932d8114cb jdk/src/share/classes/sun/java2d/pisces/Helpers.java --- a/jdk/src/share/classes/sun/java2d/pisces/Helpers.java Thu Feb 03 19:15:30 2011 -0800 +++ b/jdk/src/share/classes/sun/java2d/pisces/Helpers.java Tue Feb 08 09:22:49 2011 -0500 @@ -26,6 +26,12 @@ package sun.java2d.pisces; import java.util.Arrays; +import static java.lang.Math.PI; +import static java.lang.Math.cos; +import static java.lang.Math.sqrt; +import static java.lang.Math.cbrt; +import static java.lang.Math.acos; + final class Helpers { private Helpers() { @@ -75,100 +81,74 @@ return ret - off; } - // find the roots of g(t) = a*t^3 + b*t^2 + c*t + d in [A,B) - // We will not use Cardano's method, since it is complicated and - // involves too many square and cubic roots. We will use Newton's method. - // TODO: this should probably return ALL roots. Then the user can do - // his own filtering of roots outside [A,B). - static int cubicRootsInAB(final float a, final float b, - final float c, final float d, - float[] pts, final int off, final float E, + // 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, final float A, final float B) { - if (a == 0) { - return quadraticRoots(b, c, d, pts, off); + if (d == 0) { + int num = quadraticRoots(a, b, c, pts, off); + return filterOutNotInAB(pts, off, num, A, B) - off; } - // the coefficients of g'(t). no dc variable because dc=c - // we use these to get the critical points of g(t), which - // we then use to chose starting points for Newton's method. These - // should be very close to the actual roots. - final float da = 3 * a; - final float db = 2 * b; - int numCritPts = quadraticRoots(da, db, c, pts, off+1); - numCritPts = filterOutNotInAB(pts, off+1, numCritPts, A, B) - off - 1; - // need them sorted. - if (numCritPts == 2 && pts[off+1] > pts[off+2]) { - float tmp = pts[off+1]; - pts[off+1] = pts[off+2]; - pts[off+2] = tmp; + // From Graphics Gems: + // http://tog.acm.org/resources/GraphicsGems/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; + + // substitute x = y - A/3 to eliminate quadratic term: + // x^3 +Px + Q = 0 + // + // Since we actually need P/3 and Q/2 for all of the + // calculations that follow, we will calculate + // p = P/3 + // q = Q/2 + // instead and use those values for simplicity of the code. + double sq_A = a * a; + double p = 1.0/3 * (-1.0/3 * sq_A + b); + double q = 1.0/2 * (2.0/27 * a * sq_A - 1.0/3 * a * b + c); + + /* use Cardano's formula */ + + double cb_p = p * p * p; + double D = q * q + cb_p; + + int num; + if (D < 0) { + // see: http://en.wikipedia.org/wiki/Cubic_function#Trigonometric_.28and_hyperbolic.29_method + final double phi = 1.0/3 * acos(-q / sqrt(-cb_p)); + final double t = 2 * sqrt(-p); + + pts[ off+0 ] = (float)( t * cos(phi)); + pts[ off+1 ] = (float)(-t * cos(phi + PI / 3)); + pts[ off+2 ] = (float)(-t * cos(phi - PI / 3)); + num = 3; + } else { + final double sqrt_D = sqrt(D); + final double u = cbrt(sqrt_D - q); + final double v = - cbrt(sqrt_D + q); + + pts[ off ] = (float)(u + v); + num = 1; + + if (within(D, 0, 1e-8)) { + pts[off+1] = -(pts[off] / 2); + num = 2; + } } - int ret = off; - - // we don't actually care much about the extrema themselves. We - // only use them to ensure that g(t) is monotonic in each - // interval [pts[i],pts[i+1] (for i in off...off+numCritPts+1). - // This will allow us to determine intervals containing exactly - // one root. - // The end points of the interval are always local extrema. - pts[off] = A; - pts[off + numCritPts + 1] = B; - numCritPts += 2; - - float x0 = pts[off], fx0 = evalCubic(a, b, c, d, x0); - for (int i = off; i < off + numCritPts - 1; i++) { - float x1 = pts[i+1], fx1 = evalCubic(a, b, c, d, x1); - if (fx0 == 0f) { - pts[ret++] = x0; - } else if (fx1 * fx0 < 0f) { // have opposite signs - pts[ret++] = CubicNewton(a, b, c, d, - x0 + fx0 * (x1 - x0) / (fx0 - fx1), E); - } - x0 = x1; - fx0 = fx1; - } - return ret - off; - } + final float sub = 1.0f/3 * a; - // precondition: the polynomial to be evaluated must not be 0 at x0. - static float CubicNewton(final float a, final float b, - final float c, final float d, - float x0, final float err) - { - // considering how this function is used, 10 should be more than enough - final int itlimit = 10; - float fx0 = evalCubic(a, b, c, d, x0); - float x1; - int count = 0; - while(true) { - x1 = x0 - (fx0 / evalCubic(0, 3 * a, 2 * b, c, x0)); - if (Math.abs(x1 - x0) < err * Math.abs(x1 + x0) || count == itlimit) { - break; - } - x0 = x1; - fx0 = evalCubic(a, b, c, d, x0); - count++; + for (int i = 0; i < num; ++i) { + pts[ off+i ] -= sub; } - return x1; - } - // fills the input array with numbers 0, INC, 2*INC, ... - static void fillWithIdxes(final float[] data, final int[] idxes) { - if (idxes.length > 0) { - idxes[0] = 0; - for (int i = 1; i < idxes.length; i++) { - idxes[i] = idxes[i-1] + (int)data[idxes[i-1]]; - } - } - } - - static void fillWithIdxes(final int[] idxes, final int inc) { - if (idxes.length > 0) { - idxes[0] = 0; - for (int i = 1; i < idxes.length; i++) { - idxes[i] = idxes[i-1] + inc; - } - } + return filterOutNotInAB(pts, off, num, A, B) - off; } // These use a hardcoded factor of 2 for increasing sizes. Perhaps this @@ -182,6 +162,7 @@ } return Arrays.copyOf(in, 2 * (cursize + numToAdd)); } + static int[] widenArray(int[] in, final int cursize, final int numToAdd) { if (in.length >= cursize + numToAdd) { return in; @@ -208,7 +189,7 @@ { int ret = off; for (int i = off; i < off + len; i++) { - if (nums[i] > a && nums[i] < b) { + if (nums[i] >= a && nums[i] < b) { nums[ret++] = nums[i]; } } @@ -225,7 +206,9 @@ } static float linelen(float x1, float y1, float x2, float y2) { - return (float)Math.hypot(x2 - x1, y2 - y1); + final float dx = x2 - x1; + final float dy = y2 - y1; + return (float)Math.sqrt(dx*dx + dy*dy); } static void subdivide(float[] src, int srcoff, float[] left, int leftoff, diff -r c582063ba5bb -r e2932d8114cb jdk/src/share/classes/sun/java2d/pisces/PiscesCache.java --- a/jdk/src/share/classes/sun/java2d/pisces/PiscesCache.java Thu Feb 03 19:15:30 2011 -0800 +++ b/jdk/src/share/classes/sun/java2d/pisces/PiscesCache.java Tue Feb 08 09:22:49 2011 -0500 @@ -32,7 +32,7 @@ * * @see PiscesRenderer#render */ -public final class PiscesCache { +final class PiscesCache { final int bboxX0, bboxY0, bboxX1, bboxY1; diff -r c582063ba5bb -r e2932d8114cb jdk/src/share/classes/sun/java2d/pisces/PiscesRenderingEngine.java --- a/jdk/src/share/classes/sun/java2d/pisces/PiscesRenderingEngine.java Thu Feb 03 19:15:30 2011 -0800 +++ b/jdk/src/share/classes/sun/java2d/pisces/PiscesRenderingEngine.java Tue Feb 08 09:22:49 2011 -0500 @@ -27,7 +27,6 @@ import java.awt.Shape; import java.awt.BasicStroke; -import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Path2D; import java.awt.geom.AffineTransform; import java.awt.geom.PathIterator; @@ -250,7 +249,7 @@ float dashphase, PathConsumer2D pc2d) { - // We use inat and outat so that in Stroker and Dasher we can work only + // We use strokerat and outat so that in Stroker and Dasher we can work only // with the pre-transformation coordinates. This will repeat a lot of // computations done in the path iterator, but the alternative is to // work with transformed paths and compute untransformed coordinates @@ -265,7 +264,7 @@ // transformation after the path processing has been done. // We can't do this if normalization is on, because it isn't a good // idea to normalize before the transformation is applied. - AffineTransform inat = null; + AffineTransform strokerat = null; AffineTransform outat = null; PathIterator pi = null; @@ -284,9 +283,9 @@ // again so, nothing can be drawn. // Every path needs an initial moveTo and a pathDone. If these - // aren't there this causes a SIGSEV in libawt.so (at the time + // are not there this causes a SIGSEGV in libawt.so (at the time // of writing of this comment (September 16, 2010)). Actually, - // I'm not sure if the moveTo is necessary to avoid the SIGSEV + // I am not sure if the moveTo is necessary to avoid the SIGSEGV // but the pathDone is definitely needed. pc2d.moveTo(0, 0); pc2d.pathDone(); @@ -313,25 +312,32 @@ if (normalize != NormMode.OFF) { pi = new NormalizingPathIterator(pi, normalize); } - // leave inat and outat null. + // by now strokerat == null && outat == null. Input paths to + // stroker (and maybe dasher) will have the full transform at + // applied to them and nothing will happen to the output paths. } else { - // We only need the inverse if normalization is on. Otherwise - // we just don't transform the input paths, do all the stroking - // and then transform out output (instead of making PathIterator - // apply the transformation, us applying the inverse, and then - // us applying the transform again to our output). - outat = at; if (normalize != NormMode.OFF) { - try { - inat = outat.createInverse(); - } catch (NoninvertibleTransformException e) { - // we made sure this can't happen - e.printStackTrace(); - } + strokerat = at; pi = src.getPathIterator(at); pi = new NormalizingPathIterator(pi, normalize); + // by now strokerat == at && outat == null. Input paths to + // stroker (and maybe dasher) will have the full transform at + // applied to them, then they will be normalized, and then + // the inverse of *only the non translation part of at* will + // be applied to the normalized paths. This won't cause problems + // in stroker, because, suppose at = T*A, where T is just the + // translation part of at, and A is the rest. T*A has already + // been applied to Stroker/Dasher's input. Then Ainv will be + // applied. Ainv*T*A is not equal to T, but it is a translation, + // which means that none of stroker's assumptions about its + // input will be violated. After all this, A will be applied + // to stroker's output. } else { + outat = at; pi = src.getPathIterator(null); + // outat == at && strokerat == null. This is because if no + // normalization is done, we can just apply all our + // transformations to stroker's output. } } } else { @@ -343,13 +349,17 @@ } } + // by now, at least one of outat and strokerat will be null. Unless at is not + // a constant multiple of an orthogonal transformation, they will both be + // null. In other cases, outat == at if normalization is off, and if + // normalization is on, strokerat == at. pc2d = TransformingPathConsumer2D.transformConsumer(pc2d, outat); + pc2d = TransformingPathConsumer2D.deltaTransformConsumer(pc2d, strokerat); pc2d = new Stroker(pc2d, width, caps, join, miterlimit); if (dashes != null) { pc2d = new Dasher(pc2d, dashes, dashphase); } - pc2d = TransformingPathConsumer2D.transformConsumer(pc2d, inat); - + pc2d = TransformingPathConsumer2D.inverseDeltaTransformConsumer(pc2d, strokerat); pathTo(pi, pc2d); } @@ -588,9 +598,9 @@ } Renderer r = new Renderer(3, 3, - clip.getLoX(), clip.getLoY(), - clip.getWidth(), clip.getHeight(), - PathIterator.WIND_EVEN_ODD); + clip.getLoX(), clip.getLoY(), + clip.getWidth(), clip.getHeight(), + PathIterator.WIND_EVEN_ODD); r.moveTo((float) x, (float) y); r.lineTo((float) (x+dx1), (float) (y+dy1)); diff -r c582063ba5bb -r e2932d8114cb jdk/src/share/classes/sun/java2d/pisces/PiscesTileGenerator.java --- a/jdk/src/share/classes/sun/java2d/pisces/PiscesTileGenerator.java Thu Feb 03 19:15:30 2011 -0800 +++ b/jdk/src/share/classes/sun/java2d/pisces/PiscesTileGenerator.java Tue Feb 08 09:22:49 2011 -0500 @@ -30,7 +30,7 @@ import sun.java2d.pipe.AATileGenerator; -public final class PiscesTileGenerator implements AATileGenerator { +final class PiscesTileGenerator implements AATileGenerator { public static final int TILE_SIZE = PiscesCache.TILE_SIZE; // perhaps we should be using weak references here, but right now diff -r c582063ba5bb -r e2932d8114cb jdk/src/share/classes/sun/java2d/pisces/Renderer.java --- a/jdk/src/share/classes/sun/java2d/pisces/Renderer.java Thu Feb 03 19:15:30 2011 -0800 +++ b/jdk/src/share/classes/sun/java2d/pisces/Renderer.java Tue Feb 08 09:22:49 2011 -0500 @@ -25,12 +25,9 @@ package sun.java2d.pisces; -import java.util.Arrays; -import java.util.Iterator; - import sun.awt.geom.PathConsumer2D; -public class Renderer implements PathConsumer2D { +final class Renderer implements PathConsumer2D { private class ScanlineIterator { @@ -39,115 +36,81 @@ // crossing bounds. The bounds are not necessarily tight (the scan line // at minY, for example, might have no crossings). The x bounds will // be accumulated as crossings are computed. - private int minY, maxY; + private final int maxY; private int nextY; // indices into the segment pointer lists. They indicate the "active" // sublist in the segment lists (the portion of the list that contains // all the segments that cross the next scan line). - private int elo, ehi; - private final int[] edgePtrs; - private int qlo, qhi; - private final int[] quadPtrs; - private int clo, chi; - private final int[] curvePtrs; + private int edgeCount; + private int[] edgePtrs; private static final int INIT_CROSSINGS_SIZE = 10; private ScanlineIterator() { crossings = new int[INIT_CROSSINGS_SIZE]; - - edgePtrs = new int[numEdges]; - Helpers.fillWithIdxes(edgePtrs, SIZEOF_EDGE); - qsort(edges, edgePtrs, YMIN, 0, numEdges - 1); - - quadPtrs = new int[numQuads]; - Helpers.fillWithIdxes(quadPtrs, SIZEOF_QUAD); - qsort(quads, quadPtrs, YMIN, 0, numQuads - 1); - - curvePtrs = new int[numCurves]; - Helpers.fillWithIdxes(curvePtrs, SIZEOF_CURVE); - qsort(curves, curvePtrs, YMIN, 0, numCurves - 1); + edgePtrs = new int[INIT_CROSSINGS_SIZE]; // We don't care if we clip some of the line off with ceil, since // no scan line crossings will be eliminated (in fact, the ceil is // the y of the first scan line crossing). - nextY = minY = Math.max(boundsMinY, (int)Math.ceil(edgeMinY)); - maxY = Math.min(boundsMaxY, (int)Math.ceil(edgeMaxY)); - - for (elo = 0; elo < numEdges && edges[edgePtrs[elo]+YMAX] <= minY; elo++) - ; - // the active list is *edgePtrs[lo] (inclusive) *edgePtrs[hi] (exclusive) - for (ehi = elo; ehi < numEdges && edges[edgePtrs[ehi]+YMIN] <= minY; ehi++) - edgeSetCurY(edgePtrs[ehi], minY);// TODO: make minY a float to avoid casts - - for (qlo = 0; qlo < numQuads && quads[quadPtrs[qlo]+YMAX] <= minY; qlo++) - ; - for (qhi = qlo; qhi < numQuads && quads[quadPtrs[qhi]+YMIN] <= minY; qhi++) - quadSetCurY(quadPtrs[qhi], minY); - - for (clo = 0; clo < numCurves && curves[curvePtrs[clo]+YMAX] <= minY; clo++) - ; - for (chi = clo; chi < numCurves && curves[curvePtrs[chi]+YMIN] <= minY; chi++) - curveSetCurY(curvePtrs[chi], minY); + final int minY = getFirstScanLineCrossing(); + nextY = minY; + maxY = getScanLineCrossingEnd()-1; + edgeCount = 0; } private int next() { - // we go through the active lists and remove segments that don't cross - // the nextY scanline. - int crossingIdx = 0; - for (int i = elo; i < ehi; i++) { - if (edges[edgePtrs[i]+YMAX] <= nextY) { - edgePtrs[i] = edgePtrs[elo++]; + int cury = nextY++; + int bucket = cury - boundsMinY; + int count = this.edgeCount; + int ptrs[] = this.edgePtrs; + int bucketcount = edgeBucketCounts[bucket]; + if ((bucketcount & 0x1) != 0) { + int newCount = 0; + for (int i = 0; i < count; i++) { + int ecur = ptrs[i]; + if (edges[ecur+YMAX] > cury) { + ptrs[newCount++] = ecur; + } } + count = newCount; } - for (int i = qlo; i < qhi; i++) { - if (quads[quadPtrs[i]+YMAX] <= nextY) { - quadPtrs[i] = quadPtrs[qlo++]; - } - } - for (int i = clo; i < chi; i++) { - if (curves[curvePtrs[i]+YMAX] <= nextY) { - curvePtrs[i] = curvePtrs[clo++]; - } + ptrs = Helpers.widenArray(ptrs, count, bucketcount >> 1); + for (int ecur = edgeBuckets[bucket]; ecur != NULL; ecur = (int)edges[ecur+NEXT]) { + ptrs[count++] = ecur; + // REMIND: Adjust start Y if necessary } - - crossings = Helpers.widenArray(crossings, 0, ehi-elo+qhi-qlo+chi-clo); - - // Now every edge between lo and hi crosses nextY. Compute it's - // crossing and put it in the crossings array. - for (int i = elo; i < ehi; i++) { - int ptr = edgePtrs[i]; - addCrossing(nextY, (int)edges[ptr+CURX], edges[ptr+OR], crossingIdx); - edgeGoToNextY(ptr); - crossingIdx++; - } - for (int i = qlo; i < qhi; i++) { - int ptr = quadPtrs[i]; - addCrossing(nextY, (int)quads[ptr+CURX], quads[ptr+OR], crossingIdx); - quadGoToNextY(ptr); - crossingIdx++; + this.edgePtrs = ptrs; + this.edgeCount = count; +// if ((count & 0x1) != 0) { +// System.out.println("ODD NUMBER OF EDGES!!!!"); +// } + int xings[] = this.crossings; + if (xings.length < count) { + this.crossings = xings = new int[ptrs.length]; } - for (int i = clo; i < chi; i++) { - int ptr = curvePtrs[i]; - addCrossing(nextY, (int)curves[ptr+CURX], curves[ptr+OR], crossingIdx); - curveGoToNextY(ptr); - crossingIdx++; + for (int i = 0; i < count; i++) { + int ecur = ptrs[i]; + float curx = edges[ecur+CURX]; + int cross = ((int) curx) << 1; + edges[ecur+CURX] = curx + edges[ecur+SLOPE]; + if (edges[ecur+OR] > 0) { + cross |= 1; + } + int j = i; + while (--j >= 0) { + int jcross = xings[j]; + if (jcross <= cross) { + break; + } + xings[j+1] = jcross; + ptrs[j+1] = ptrs[j]; + } + xings[j+1] = cross; + ptrs[j+1] = ecur; } - - nextY++; - // Expand active lists to include new edges. - for (; ehi < numEdges && edges[edgePtrs[ehi]+YMIN] <= nextY; ehi++) { - edgeSetCurY(edgePtrs[ehi], nextY); - } - for (; qhi < numQuads && quads[quadPtrs[qhi]+YMIN] <= nextY; qhi++) { - quadSetCurY(quadPtrs[qhi], nextY); - } - for (; chi < numCurves && curves[curvePtrs[chi]+YMIN] <= nextY; chi++) { - curveSetCurY(curvePtrs[chi], nextY); - } - Arrays.sort(crossings, 0, crossingIdx); - return crossingIdx; + return count; } private boolean hasNext() { @@ -157,51 +120,7 @@ private int curY() { return nextY - 1; } - - private void addCrossing(int y, int x, float or, int idx) { - x <<= 1; - crossings[idx] = ((or > 0) ? (x | 0x1) : x); - } } - // quicksort implementation for sorting the edge indices ("pointers") - // by increasing y0. first, last are indices into the "pointer" array - // It sorts the pointer array from first (inclusive) to last (inclusive) - private static void qsort(final float[] data, final int[] ptrs, - final int fieldForCmp, int first, int last) - { - if (last > first) { - int p = partition(data, ptrs, fieldForCmp, first, last); - if (first < p - 1) { - qsort(data, ptrs, fieldForCmp, first, p - 1); - } - if (p < last) { - qsort(data, ptrs, fieldForCmp, p, last); - } - } - } - - // i, j are indices into edgePtrs. - private static int partition(final float[] data, final int[] ptrs, - final int fieldForCmp, int i, int j) - { - int pivotValFieldForCmp = ptrs[i]+fieldForCmp; - while (i <= j) { - // edges[edgePtrs[i]+1] is equivalent to (*(edgePtrs[i])).y0 in C - while (data[ptrs[i]+fieldForCmp] < data[pivotValFieldForCmp]) - i++; - while (data[ptrs[j]+fieldForCmp] > data[pivotValFieldForCmp]) - j--; - if (i <= j) { - int tmp = ptrs[i]; - ptrs[i] = ptrs[j]; - ptrs[j] = tmp; - i++; - j--; - } - } - return i; - } -//============================================================================ ////////////////////////////////////////////////////////////////////////////// @@ -209,269 +128,89 @@ ////////////////////////////////////////////////////////////////////////////// // TODO(maybe): very tempting to use fixed point here. A lot of opportunities // for shifts and just removing certain operations altogether. -// TODO: it might be worth it to make an EdgeList class. It would probably -// clean things up a bit and not impact performance much. // common to all types of input path segments. - private static final int YMIN = 0; - private static final int YMAX = 1; - private static final int CURX = 2; - // this and OR are meant to be indeces into "int" fields, but arrays must + private static final int YMAX = 0; + private static final int CURX = 1; + // NEXT and OR are meant to be indices into "int" fields, but arrays must // be homogenous, so every field is a float. However floats can represent // exactly up to 26 bit ints, so we're ok. - private static final int CURY = 3; - private static final int OR = 4; - - // for straight lines only: - private static final int SLOPE = 5; - - // for quads and cubics: - private static final int X0 = 5; - private static final int Y0 = 6; - private static final int XL = 7; - private static final int COUNT = 8; - private static final int CURSLOPE = 9; - private static final int DX = 10; - private static final int DY = 11; - private static final int DDX = 12; - private static final int DDY = 13; - - // for cubics only - private static final int DDDX = 14; - private static final int DDDY = 15; + private static final int OR = 2; + private static final int SLOPE = 3; + private static final int NEXT = 4; private float edgeMinY = Float.POSITIVE_INFINITY; private float edgeMaxY = Float.NEGATIVE_INFINITY; private float edgeMinX = Float.POSITIVE_INFINITY; private float edgeMaxX = Float.NEGATIVE_INFINITY; - private static final int SIZEOF_EDGE = 6; + private static final int SIZEOF_EDGE = 5; + // don't just set NULL to -1, because we want NULL+NEXT to be negative. + private static final int NULL = -SIZEOF_EDGE; private float[] edges = null; + private int[] edgeBuckets = null; + private int[] edgeBucketCounts = null; // 2*newedges + (1 if pruning needed) private int numEdges; - // these are static because we need them to be usable from ScanlineIterator - private void edgeSetCurY(final int idx, int y) { - edges[idx+CURX] += (y - edges[idx+CURY]) * edges[idx+SLOPE]; - edges[idx+CURY] = y; - } - private void edgeGoToNextY(final int idx) { - edges[idx+CURY] += 1; - edges[idx+CURX] += edges[idx+SLOPE]; - } - - - private static final int SIZEOF_QUAD = 14; - private float[] quads = null; - private int numQuads; - // This function should be called exactly once, to set the first scanline - // of the curve. Before it is called, the curve should think its first - // scanline is CEIL(YMIN). - private void quadSetCurY(final int idx, final int y) { - assert y < quads[idx+YMAX]; - assert (quads[idx+CURY] > y); - assert (quads[idx+CURY] == Math.ceil(quads[idx+CURY])); - - while (quads[idx+CURY] < ((float)y)) { - quadGoToNextY(idx); - } - } - private void quadGoToNextY(final int idx) { - quads[idx+CURY] += 1; - // this will get overriden if the while executes. - quads[idx+CURX] += quads[idx+CURSLOPE]; - int count = (int)quads[idx+COUNT]; - // this loop should never execute more than once because our - // curve is monotonic in Y. Still we put it in because you can - // never be too sure when dealing with floating point. - while(quads[idx+CURY] >= quads[idx+Y0] && count > 0) { - float x0 = quads[idx+X0], y0 = quads[idx+Y0]; - count = executeQuadAFDIteration(idx); - float x1 = quads[idx+X0], y1 = quads[idx+Y0]; - // our quads are monotonic, so this shouldn't happen, but - // it is conceivable that for very flat quads with different - // y values at their endpoints AFD might give us a horizontal - // segment. - if (y1 == y0) { - continue; - } - quads[idx+CURSLOPE] = (x1 - x0) / (y1 - y0); - quads[idx+CURX] = x0 + (quads[idx+CURY] - y0) * quads[idx+CURSLOPE]; - } - } - - - private static final int SIZEOF_CURVE = 16; - private float[] curves = null; - private int numCurves; - private void curveSetCurY(final int idx, final int y) { - assert y < curves[idx+YMAX]; - assert (curves[idx+CURY] > y); - assert (curves[idx+CURY] == Math.ceil(curves[idx+CURY])); - - while (curves[idx+CURY] < ((float)y)) { - curveGoToNextY(idx); - } - } - private void curveGoToNextY(final int idx) { - curves[idx+CURY] += 1; - // this will get overriden if the while executes. - curves[idx+CURX] += curves[idx+CURSLOPE]; - int count = (int)curves[idx+COUNT]; - // this loop should never execute more than once because our - // curve is monotonic in Y. Still we put it in because you can - // never be too sure when dealing with floating point. - while(curves[idx+CURY] >= curves[idx+Y0] && count > 0) { - float x0 = curves[idx+X0], y0 = curves[idx+Y0]; - count = executeCurveAFDIteration(idx); - float x1 = curves[idx+X0], y1 = curves[idx+Y0]; - // our curves are monotonic, so this shouldn't happen, but - // it is conceivable that for very flat curves with different - // y values at their endpoints AFD might give us a horizontal - // segment. - if (y1 == y0) { - continue; - } - curves[idx+CURSLOPE] = (x1 - x0) / (y1 - y0); - curves[idx+CURX] = x0 + (curves[idx+CURY] - y0) * curves[idx+CURSLOPE]; - } - } - private static final float DEC_BND = 20f; private static final float INC_BND = 8f; + + // each bucket is a linked list. this method adds eptr to the + // start "bucket"th linked list. + private void addEdgeToBucket(final int eptr, final int bucket) { + edges[eptr+NEXT] = edgeBuckets[bucket]; + edgeBuckets[bucket] = eptr; + edgeBucketCounts[bucket] += 2; + } + // Flattens using adaptive forward differencing. This only carries out // one iteration of the AFD loop. All it does is update AFD variables (i.e. // X0, Y0, D*[X|Y], COUNT; not variables used for computing scanline crossings). - private int executeQuadAFDIteration(int idx) { - int count = (int)quads[idx+COUNT]; - float ddx = quads[idx+DDX]; - float ddy = quads[idx+DDY]; - float dx = quads[idx+DX]; - float dy = quads[idx+DY]; - - while (Math.abs(ddx) > DEC_BND || Math.abs(ddy) > DEC_BND) { - ddx = ddx / 4; - ddy = ddy / 4; - dx = (dx - ddx) / 2; - dy = (dy - ddy) / 2; + private void quadBreakIntoLinesAndAdd(float x0, float y0, + final Curve c, + final float x2, final float y2) { + final float QUAD_DEC_BND = 32; + final int countlg = 4; + int count = 1 << countlg; + int countsq = count * count; + float maxDD = Math.max(c.dbx / countsq, c.dby / countsq); + while (maxDD > QUAD_DEC_BND) { + maxDD /= 4; count <<= 1; } - // can only do this on even "count" values, because we must divide count by 2 - while (count % 2 == 0 && Math.abs(dx) <= INC_BND && Math.abs(dy) <= INC_BND) { - dx = 2 * dx + ddx; - dy = 2 * dy + ddy; - ddx = 4 * ddx; - ddy = 4 * ddy; - count >>= 1; - } - count--; - if (count > 0) { - quads[idx+X0] += dx; - dx += ddx; - quads[idx+Y0] += dy; - dy += ddy; - } else { - quads[idx+X0] = quads[idx+XL]; - quads[idx+Y0] = quads[idx+YMAX]; - } - quads[idx+COUNT] = count; - quads[idx+DDX] = ddx; - quads[idx+DDY] = ddy; - quads[idx+DX] = dx; - quads[idx+DY] = dy; - return count; - } - private int executeCurveAFDIteration(int idx) { - int count = (int)curves[idx+COUNT]; - float ddx = curves[idx+DDX]; - float ddy = curves[idx+DDY]; - float dx = curves[idx+DX]; - float dy = curves[idx+DY]; - float dddx = curves[idx+DDDX]; - float dddy = curves[idx+DDDY]; + + countsq = count * count; + final float ddx = c.dbx / countsq; + final float ddy = c.dby / countsq; + float dx = c.bx / countsq + c.cx / count; + float dy = c.by / countsq + c.cy / count; - while (Math.abs(ddx) > DEC_BND || Math.abs(ddy) > DEC_BND) { - dddx /= 8; - dddy /= 8; - ddx = ddx/4 - dddx; - ddy = ddy/4 - dddy; - dx = (dx - ddx) / 2; - dy = (dy - ddy) / 2; - count <<= 1; - } - // can only do this on even "count" values, because we must divide count by 2 - while (count % 2 == 0 && Math.abs(dx) <= INC_BND && Math.abs(dy) <= INC_BND) { - dx = 2 * dx + ddx; - dy = 2 * dy + ddy; - ddx = 4 * (ddx + dddx); - ddy = 4 * (ddy + dddy); - dddx = 8 * dddx; - dddy = 8 * dddy; - count >>= 1; + while (count-- > 1) { + float x1 = x0 + dx; + dx += ddx; + float y1 = y0 + dy; + dy += ddy; + addLine(x0, y0, x1, y1); + x0 = x1; + y0 = y1; } - count--; - if (count > 0) { - curves[idx+X0] += dx; - dx += ddx; - ddx += dddx; - curves[idx+Y0] += dy; - dy += ddy; - ddy += dddy; - } else { - curves[idx+X0] = curves[idx+XL]; - curves[idx+Y0] = curves[idx+YMAX]; - } - curves[idx+COUNT] = count; - curves[idx+DDDX] = dddx; - curves[idx+DDDY] = dddy; - curves[idx+DDX] = ddx; - curves[idx+DDY] = ddy; - curves[idx+DX] = dx; - curves[idx+DY] = dy; - return count; + addLine(x0, y0, x2, y2); } - - private void initLine(final int idx, float[] pts, int or) { - edges[idx+SLOPE] = (pts[2] - pts[0]) / (pts[3] - pts[1]); - edges[idx+CURX] = pts[0] + (edges[idx+CURY] - pts[1]) * edges[idx+SLOPE]; - } - - private void initQuad(final int idx, float[] points, int or) { + // x0, y0 and x3,y3 are the endpoints of the curve. We could compute these + // using c.xat(0),c.yat(0) and c.xat(1),c.yat(1), but this might introduce + // numerical errors, and our callers already have the exact values. + // Another alternative would be to pass all the control points, and call c.set + // here, but then too many numbers are passed around. + private void curveBreakIntoLinesAndAdd(float x0, float y0, + final Curve c, + final float x3, final float y3) { final int countlg = 3; - final int count = 1 << countlg; + int count = 1 << countlg; // the dx and dy refer to forward differencing variables, not the last // coefficients of the "points" polynomial - final float ddx, ddy, dx, dy; - c.set(points, 6); - - ddx = c.dbx / (1 << (2 * countlg)); - ddy = c.dby / (1 << (2 * countlg)); - dx = c.bx / (1 << (2 * countlg)) + c.cx / (1 << countlg); - dy = c.by / (1 << (2 * countlg)) + c.cy / (1 << countlg); - - quads[idx+DDX] = ddx; - quads[idx+DDY] = ddy; - quads[idx+DX] = dx; - quads[idx+DY] = dy; - quads[idx+COUNT] = count; - quads[idx+XL] = points[4]; - quads[idx+X0] = points[0]; - quads[idx+Y0] = points[1]; - executeQuadAFDIteration(idx); - float x1 = quads[idx+X0], y1 = quads[idx+Y0]; - quads[idx+CURSLOPE] = (x1 - points[0]) / (y1 - points[1]); - quads[idx+CURX] = points[0] + (quads[idx+CURY] - points[1])*quads[idx+CURSLOPE]; - } - - private void initCurve(final int idx, float[] points, int or) { - final int countlg = 3; - final int count = 1 << countlg; - - // the dx and dy refer to forward differencing variables, not the last - // coefficients of the "points" polynomial - final float dddx, dddy, ddx, ddy, dx, dy; - c.set(points, 8); + float dddx, dddy, ddx, ddy, dx, dy; dddx = 2f * c.dax / (1 << (3 * countlg)); dddy = 2f * c.day / (1 << (3 * countlg)); @@ -480,93 +219,100 @@ dx = c.ax / (1 << (3 * countlg)) + c.bx / (1 << (2 * countlg)) + c.cx / (1 << countlg); dy = c.ay / (1 << (3 * countlg)) + c.by / (1 << (2 * countlg)) + c.cy / (1 << countlg); - curves[idx+DDDX] = dddx; - curves[idx+DDDY] = dddy; - curves[idx+DDX] = ddx; - curves[idx+DDY] = ddy; - curves[idx+DX] = dx; - curves[idx+DY] = dy; - curves[idx+COUNT] = count; - curves[idx+XL] = points[6]; - curves[idx+X0] = points[0]; - curves[idx+Y0] = points[1]; - executeCurveAFDIteration(idx); - float x1 = curves[idx+X0], y1 = curves[idx+Y0]; - curves[idx+CURSLOPE] = (x1 - points[0]) / (y1 - points[1]); - curves[idx+CURX] = points[0] + (curves[idx+CURY] - points[1])*curves[idx+CURSLOPE]; - } - - private void addPathSegment(float[] pts, final int type, final int or) { - int idx; - float[] addTo; - switch (type) { - case 4: - idx = numEdges * SIZEOF_EDGE; - addTo = edges = Helpers.widenArray(edges, numEdges*SIZEOF_EDGE, SIZEOF_EDGE); - numEdges++; - break; - case 6: - idx = numQuads * SIZEOF_QUAD; - addTo = quads = Helpers.widenArray(quads, numQuads*SIZEOF_QUAD, SIZEOF_QUAD); - numQuads++; - break; - case 8: - idx = numCurves * SIZEOF_CURVE; - addTo = curves = Helpers.widenArray(curves, numCurves*SIZEOF_CURVE, SIZEOF_CURVE); - numCurves++; - break; - default: - throw new InternalError(); - } - // set the common fields, except CURX, for which we must know the kind - // of curve. NOTE: this must be done before the type specific fields - // are initialized, because those depend on the common ones. - addTo[idx+YMIN] = pts[1]; - addTo[idx+YMAX] = pts[type-1]; - addTo[idx+OR] = or; - addTo[idx+CURY] = (float)Math.ceil(pts[1]); - switch (type) { - case 4: - initLine(idx, pts, or); - break; - case 6: - initQuad(idx, pts, or); - break; - case 8: - initCurve(idx, pts, or); - break; - default: - throw new InternalError(); + // we use x0, y0 to walk the line + float x1 = x0, y1 = y0; + while (count > 0) { + while (Math.abs(ddx) > DEC_BND || Math.abs(ddy) > DEC_BND) { + dddx /= 8; + dddy /= 8; + ddx = ddx/4 - dddx; + ddy = ddy/4 - dddy; + dx = (dx - ddx) / 2; + dy = (dy - ddy) / 2; + count <<= 1; + } + // can only do this on even "count" values, because we must divide count by 2 + while (count % 2 == 0 && Math.abs(dx) <= INC_BND && Math.abs(dy) <= INC_BND) { + dx = 2 * dx + ddx; + dy = 2 * dy + ddy; + ddx = 4 * (ddx + dddx); + ddy = 4 * (ddy + dddy); + dddx = 8 * dddx; + dddy = 8 * dddy; + count >>= 1; + } + count--; + if (count > 0) { + x1 += dx; + dx += ddx; + ddx += dddx; + y1 += dy; + dy += ddy; + ddy += dddy; + } else { + x1 = x3; + y1 = y3; + } + addLine(x0, y0, x1, y1); + x0 = x1; + y0 = y1; } } - // precondition: the curve in pts must be monotonic and increasing in y. - private void somethingTo(float[] pts, final int type, final int or) { - // NOTE: it's very important that we check for or >= 0 below (as - // opposed to or == 1, or or > 0, or anything else). That's - // because if we check for or==1, when the curve being added - // is a horizontal line, or will be 0 so or==1 will be false and - // x0 and y0 will be updated to pts[0] and pts[1] instead of pts[type-2] - // and pts[type-1], which is the correct thing to do. - this.x0 = or >= 0 ? pts[type - 2] : pts[0]; - this.y0 = or >= 0 ? pts[type - 1] : pts[1]; - - float minY = pts[1], maxY = pts[type - 1]; - if (Math.ceil(minY) >= Math.ceil(maxY) || - Math.ceil(minY) >= boundsMaxY || maxY < boundsMinY) - { + // Preconditions: y2 > y1 and the curve must cross some scanline + // i.e.: y1 <= y < y2 for some y such that boundsMinY <= y < boundsMaxY + private void addLine(float x1, float y1, float x2, float y2) { + float or = 1; // orientation of the line. 1 if y increases, 0 otherwise. + if (y2 < y1) { + or = y2; // no need to declare a temp variable. We have or. + y2 = y1; + y1 = or; + or = x2; + x2 = x1; + x1 = or; + or = 0; + } + final int firstCrossing = Math.max((int) Math.ceil(y1), boundsMinY); + final int lastCrossing = Math.min((int)Math.ceil(y2), boundsMaxY); + if (firstCrossing >= lastCrossing) { return; } - if (minY < edgeMinY) { edgeMinY = minY; } - if (maxY > edgeMaxY) { edgeMaxY = maxY; } + if (y1 < edgeMinY) { edgeMinY = y1; } + if (y2 > edgeMaxY) { edgeMaxY = y2; } + + final float slope = (x2 - x1) / (y2 - y1); + + if (slope > 0) { // <==> x1 < x2 + if (x1 < edgeMinX) { edgeMinX = x1; } + if (x2 > edgeMaxX) { edgeMaxX = x2; } + } else { + if (x2 < edgeMinX) { edgeMinX = x2; } + if (x1 > edgeMaxX) { edgeMaxX = x1; } + } - int minXidx = (pts[0] < pts[type-2] ? 0 : type - 2); - float minX = pts[minXidx]; - float maxX = pts[type - 2 - minXidx]; - if (minX < edgeMinX) { edgeMinX = minX; } - if (maxX > edgeMaxX) { edgeMaxX = maxX; } - addPathSegment(pts, type, or); + final int ptr = numEdges * SIZEOF_EDGE; + edges = Helpers.widenArray(edges, ptr, SIZEOF_EDGE); + numEdges++; + edges[ptr+OR] = or; + edges[ptr+CURX] = x1 + (firstCrossing - y1) * slope; + edges[ptr+SLOPE] = slope; + edges[ptr+YMAX] = y2; + final int bucketIdx = firstCrossing - boundsMinY; + addEdgeToBucket(ptr, bucketIdx); + if (lastCrossing < boundsMaxY) { + edgeBucketCounts[lastCrossing - boundsMinY] |= 1; + } + } + + // preconditions: should not be called before the last line has been added + // to the edge list (even though it will return a correct answer at that + // point in time, it's not meant to be used that way). + private int getFirstScanLineCrossing() { + return Math.max(boundsMinY, (int)Math.ceil(edgeMinY)); + } + private int getScanLineCrossingEnd() { + return Math.min(boundsMaxY, (int)Math.ceil(edgeMaxY)); } // END EDGE LIST @@ -619,6 +365,10 @@ this.boundsMinY = pix_boundsY * SUBPIXEL_POSITIONS_Y; this.boundsMaxX = (pix_boundsX + pix_boundsWidth) * SUBPIXEL_POSITIONS_X; this.boundsMaxY = (pix_boundsY + pix_boundsHeight) * SUBPIXEL_POSITIONS_Y; + + edgeBuckets = new int[boundsMaxY - boundsMinY]; + java.util.Arrays.fill(edgeBuckets, NULL); + edgeBucketCounts = new int[edgeBuckets.length]; } private float tosubpixx(float pix_x) { @@ -636,74 +386,34 @@ this.x0 = tosubpixx(pix_x0); } - public void lineJoin() { /* do nothing */ } - - private final float[][] pts = new float[2][8]; - private final float[] ts = new float[4]; - - private static void invertPolyPoints(float[] pts, int off, int type) { - for (int i = off, j = off + type - 2; i < j; i += 2, j -= 2) { - float tmp = pts[i]; - pts[i] = pts[j]; - pts[j] = tmp; - tmp = pts[i+1]; - pts[i+1] = pts[j+1]; - pts[j+1] = tmp; - } - } - - // return orientation before making the curve upright. - private static int makeMonotonicCurveUpright(float[] pts, int off, int type) { - float y0 = pts[off + 1]; - float y1 = pts[off + type - 1]; - if (y0 > y1) { - invertPolyPoints(pts, off, type); - return -1; - } else if (y0 < y1) { - return 1; - } - return 0; - } - public void lineTo(float pix_x1, float pix_y1) { - pts[0][0] = x0; pts[0][1] = y0; - pts[0][2] = tosubpixx(pix_x1); pts[0][3] = tosubpixy(pix_y1); - int or = makeMonotonicCurveUpright(pts[0], 0, 4); - somethingTo(pts[0], 4, or); + float x1 = tosubpixx(pix_x1); + float y1 = tosubpixy(pix_y1); + addLine(x0, y0, x1, y1); + x0 = x1; + y0 = y1; } Curve c = new Curve(); - private void curveOrQuadTo(int type) { - c.set(pts[0], type); - int numTs = c.dxRoots(ts, 0); - numTs += c.dyRoots(ts, numTs); - numTs = Helpers.filterOutNotInAB(ts, 0, numTs, 0, 1); - Helpers.isort(ts, 0, numTs); - - Iterator it = Curve.breakPtsAtTs(pts, type, ts, numTs); - while(it.hasNext()) { - float[] curCurve = it.next(); - int or = makeMonotonicCurveUpright(curCurve, 0, type); - somethingTo(curCurve, type, or); - } - } - @Override public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) { - pts[0][0] = x0; pts[0][1] = y0; - pts[0][2] = tosubpixx(x1); pts[0][3] = tosubpixy(y1); - pts[0][4] = tosubpixx(x2); pts[0][5] = tosubpixy(y2); - pts[0][6] = tosubpixx(x3); pts[0][7] = tosubpixy(y3); - curveOrQuadTo(8); + final float xe = tosubpixx(x3); + final float ye = tosubpixy(y3); + c.set(x0, y0, tosubpixx(x1), tosubpixy(y1), tosubpixx(x2), tosubpixy(y2), xe, ye); + curveBreakIntoLinesAndAdd(x0, y0, c, xe, ye); + x0 = xe; + y0 = ye; } @Override public void quadTo(float x1, float y1, float x2, float y2) { - pts[0][0] = x0; pts[0][1] = y0; - pts[0][2] = tosubpixx(x1); pts[0][3] = tosubpixy(y1); - pts[0][4] = tosubpixx(x2); pts[0][5] = tosubpixy(y2); - curveOrQuadTo(6); + final float xe = tosubpixx(x2); + final float ye = tosubpixy(y2); + c.set(x0, y0, tosubpixx(x1), tosubpixy(y1), xe, ye); + quadBreakIntoLinesAndAdd(x0, y0, c, xe, ye); + x0 = xe; + y0 = ye; } public void closePath() { @@ -728,9 +438,9 @@ // 0x1 if EVEN_ODD, all bits if NON_ZERO int mask = (windingRule == WIND_EVEN_ODD) ? 0x1 : ~0x0; - // add 1 to better deal with the last pixel in a pixel row. - int width = pix_bboxx1 - pix_bboxx0 + 1; - int[] alpha = new int[width+1]; + // add 2 to better deal with the last pixel in a pixel row. + int width = pix_bboxx1 - pix_bboxx0; + int[] alpha = new int[width+2]; int bboxx0 = pix_bboxx0 << SUBPIXEL_LG_POSITIONS_X; int bboxx1 = pix_bboxx1 << SUBPIXEL_LG_POSITIONS_X; @@ -766,7 +476,8 @@ for (int i = 0; i < numCrossings; i++) { int curxo = crossings[i]; int curx = curxo >> 1; - int crorientation = ((curxo & 0x1) == 0x1) ? 1 : -1; + // to turn {0, 1} into {-1, 1}, multiply by 2 and subtract 1. + int crorientation = ((curxo & 0x1) << 1) -1; if ((sum & mask) != 0) { int x0 = Math.max(prev, bboxx0); int x1 = Math.min(curx, bboxx1); @@ -811,26 +522,26 @@ } public void endRendering() { - final int bminx = boundsMinX >> SUBPIXEL_LG_POSITIONS_X; - final int bmaxx = boundsMaxX >> SUBPIXEL_LG_POSITIONS_X; - final int bminy = boundsMinY >> SUBPIXEL_LG_POSITIONS_Y; - final int bmaxy = boundsMaxY >> SUBPIXEL_LG_POSITIONS_Y; - final int eminx = ((int)Math.floor(edgeMinX)) >> SUBPIXEL_LG_POSITIONS_X; - final int emaxx = ((int)Math.ceil(edgeMaxX)) >> SUBPIXEL_LG_POSITIONS_X; - final int eminy = ((int)Math.floor(edgeMinY)) >> SUBPIXEL_LG_POSITIONS_Y; - final int emaxy = ((int)Math.ceil(edgeMaxY)) >> SUBPIXEL_LG_POSITIONS_Y; + int spminX = Math.max((int)Math.ceil(edgeMinX), boundsMinX); + int spmaxX = Math.min((int)Math.ceil(edgeMaxX), boundsMaxX); + int spminY = Math.max((int)Math.ceil(edgeMinY), boundsMinY); + int spmaxY = Math.min((int)Math.ceil(edgeMaxY), boundsMaxY); - final int minX = Math.max(bminx, eminx); - final int maxX = Math.min(bmaxx, emaxx); - final int minY = Math.max(bminy, eminy); - final int maxY = Math.min(bmaxy, emaxy); - if (minX > maxX || minY > maxY) { - this.cache = new PiscesCache(bminx, bminy, bmaxx, bmaxy); + int pminX = spminX >> SUBPIXEL_LG_POSITIONS_X; + int pmaxX = (spmaxX + SUBPIXEL_MASK_X) >> SUBPIXEL_LG_POSITIONS_X; + int pminY = spminY >> SUBPIXEL_LG_POSITIONS_Y; + int pmaxY = (spmaxY + SUBPIXEL_MASK_Y) >> SUBPIXEL_LG_POSITIONS_Y; + + if (pminX > pmaxX || pminY > pmaxY) { + this.cache = new PiscesCache(boundsMinX >> SUBPIXEL_LG_POSITIONS_X, + boundsMinY >> SUBPIXEL_LG_POSITIONS_Y, + boundsMaxX >> SUBPIXEL_LG_POSITIONS_X, + boundsMaxY >> SUBPIXEL_LG_POSITIONS_Y); return; } - this.cache = new PiscesCache(minX, minY, maxX, maxY); - _endRendering(minX, minY, maxX, maxY); + this.cache = new PiscesCache(pminX, pminY, pmaxX, pmaxY); + _endRendering(pminX, pminY, pmaxX, pmaxY); } public PiscesCache getCache() { diff -r c582063ba5bb -r e2932d8114cb jdk/src/share/classes/sun/java2d/pisces/Stroker.java --- a/jdk/src/share/classes/sun/java2d/pisces/Stroker.java Thu Feb 03 19:15:30 2011 -0800 +++ b/jdk/src/share/classes/sun/java2d/pisces/Stroker.java Tue Feb 08 09:22:49 2011 -0500 @@ -33,7 +33,7 @@ // TODO: some of the arithmetic here is too verbose and prone to hard to // debug typos. We should consider making a small Point/Vector class that // has methods like plus(Point), minus(Point), dot(Point), cross(Point)and such -public class Stroker implements PathConsumer2D { +final class Stroker implements PathConsumer2D { private static final int MOVE_TO = 0; private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad @@ -130,7 +130,7 @@ private static void computeOffset(final float lx, final float ly, final float w, final float[] m) { - final float len = (float)Math.hypot(lx, ly); + final float len = (float)Math.sqrt(lx*lx + ly*ly); if (len == 0) { m[0] = m[1] = 0; } else { @@ -758,7 +758,7 @@ // This is where the curve to be processed is put. We give it // enough room to store 2 curves: one for the current subdivision, the // other for the rest of the curve. - private float[][] middle = new float[2][8]; + private float[] middle = new float[2*8]; private float[] lp = new float[8]; private float[] rp = new float[8]; private static final int MAX_N_CURVES = 11; @@ -766,55 +766,55 @@ private void somethingTo(final int type) { // need these so we can update the state at the end of this method - final float xf = middle[0][type-2], yf = middle[0][type-1]; - float dxs = middle[0][2] - middle[0][0]; - float dys = middle[0][3] - middle[0][1]; - float dxf = middle[0][type - 2] - middle[0][type - 4]; - float dyf = middle[0][type - 1] - middle[0][type - 3]; + final float xf = middle[type-2], yf = middle[type-1]; + float dxs = middle[2] - middle[0]; + float dys = middle[3] - middle[1]; + float dxf = middle[type - 2] - middle[type - 4]; + float dyf = middle[type - 1] - middle[type - 3]; switch(type) { case 6: if ((dxs == 0f && dys == 0f) || (dxf == 0f && dyf == 0f)) { - dxs = dxf = middle[0][4] - middle[0][0]; - dys = dyf = middle[0][5] - middle[0][1]; + dxs = dxf = middle[4] - middle[0]; + dys = dyf = middle[5] - middle[1]; } break; case 8: boolean p1eqp2 = (dxs == 0f && dys == 0f); boolean p3eqp4 = (dxf == 0f && dyf == 0f); if (p1eqp2) { - dxs = middle[0][4] - middle[0][0]; - dys = middle[0][5] - middle[0][1]; + dxs = middle[4] - middle[0]; + dys = middle[5] - middle[1]; if (dxs == 0f && dys == 0f) { - dxs = middle[0][6] - middle[0][0]; - dys = middle[0][7] - middle[0][1]; + dxs = middle[6] - middle[0]; + dys = middle[7] - middle[1]; } } if (p3eqp4) { - dxf = middle[0][6] - middle[0][2]; - dyf = middle[0][7] - middle[0][3]; + dxf = middle[6] - middle[2]; + dyf = middle[7] - middle[3]; if (dxf == 0f && dyf == 0f) { - dxf = middle[0][6] - middle[0][0]; - dyf = middle[0][7] - middle[0][1]; + dxf = middle[6] - middle[0]; + dyf = middle[7] - middle[1]; } } } if (dxs == 0f && dys == 0f) { // this happens iff the "curve" is just a point - lineTo(middle[0][0], middle[0][1]); + lineTo(middle[0], middle[1]); 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) { - double len = Math.hypot(dxs, dys); - dxs = (float)(dxs / len); - dys = (float)(dys / len); + float len = (float)Math.sqrt(dxs*dxs + dys*dys); + dxs /= len; + dys /= len; } if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) { - double len = Math.hypot(dxf, dyf); - dxf = (float)(dxf / len); - dyf = (float)(dyf / len); + float len = (float)Math.sqrt(dxf*dxf + dyf*dyf); + dxf /= len; + dyf /= len; } computeOffset(dxs, dys, lineWidth2, offset[0]); @@ -822,20 +822,20 @@ final float my = offset[0][1]; drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, mx, my); - int nSplits = findSubdivPoints(middle[0], subdivTs, type,lineWidth2); + int nSplits = findSubdivPoints(middle, subdivTs, type, lineWidth2); int kind = 0; - Iterator it = Curve.breakPtsAtTs(middle, type, subdivTs, nSplits); + Iterator it = Curve.breakPtsAtTs(middle, type, subdivTs, nSplits); while(it.hasNext()) { - float[] curCurve = it.next(); + int curCurveOff = it.next(); kind = 0; switch (type) { case 8: - kind = computeOffsetCubic(curCurve, 0, lp, rp); + kind = computeOffsetCubic(middle, curCurveOff, lp, rp); break; case 6: - kind = computeOffsetQuad(curCurve, 0, lp, rp); + kind = computeOffsetQuad(middle, curCurveOff, lp, rp); break; } if (kind != 0) { @@ -871,8 +871,7 @@ // 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 Curve c = new Curve(); - private static int findSubdivPoints(float[] pts, float[] ts, - final int type, final float w) + private static int findSubdivPoints(float[] pts, float[] ts, final int type, final float w) { final float x12 = pts[2] - pts[0]; final float y12 = pts[3] - pts[1]; @@ -919,6 +918,7 @@ // 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; @@ -928,10 +928,10 @@ float x2, float y2, float x3, float y3) { - middle[0][0] = cx0; middle[0][1] = cy0; - middle[0][2] = x1; middle[0][3] = y1; - middle[0][4] = x2; middle[0][5] = y2; - middle[0][6] = x3; middle[0][7] = y3; + middle[0] = cx0; middle[1] = cy0; + middle[2] = x1; middle[3] = y1; + middle[4] = x2; middle[5] = y2; + middle[6] = x3; middle[7] = y3; somethingTo(8); } @@ -940,9 +940,9 @@ } @Override public void quadTo(float x1, float y1, float x2, float y2) { - middle[0][0] = cx0; middle[0][1] = cy0; - middle[0][2] = x1; middle[0][3] = y1; - middle[0][4] = x2; middle[0][5] = y2; + middle[0] = cx0; middle[1] = cy0; + middle[2] = x1; middle[3] = y1; + middle[4] = x2; middle[5] = y2; somethingTo(6); } diff -r c582063ba5bb -r e2932d8114cb jdk/src/share/classes/sun/java2d/pisces/TransformingPathConsumer2D.java --- a/jdk/src/share/classes/sun/java2d/pisces/TransformingPathConsumer2D.java Thu Feb 03 19:15:30 2011 -0800 +++ b/jdk/src/share/classes/sun/java2d/pisces/TransformingPathConsumer2D.java Tue Feb 08 09:22:49 2011 -0500 @@ -28,7 +28,7 @@ import sun.awt.geom.PathConsumer2D; import java.awt.geom.AffineTransform; -public class TransformingPathConsumer2D { +final class TransformingPathConsumer2D { public static PathConsumer2D transformConsumer(PathConsumer2D out, AffineTransform at) @@ -50,17 +50,72 @@ return new TranslateFilter(out, Mxt, Myt); } } else { - return new ScaleFilter(out, Mxx, Myy, Mxt, Myt); + if (Mxt == 0f && Myt == 0f) { + return new DeltaScaleFilter(out, Mxx, Myy); + } else { + return new ScaleFilter(out, Mxx, Myy, Mxt, Myt); + } } + } else if (Mxt == 0f && Myt == 0f) { + return new DeltaTransformFilter(out, Mxx, Mxy, Myx, Myy); } else { return new TransformFilter(out, Mxx, Mxy, Mxt, Myx, Myy, Myt); } } - static class TranslateFilter implements PathConsumer2D { - PathConsumer2D out; - float tx; - float ty; + public static PathConsumer2D + deltaTransformConsumer(PathConsumer2D out, + AffineTransform at) + { + if (at == null) { + return out; + } + float Mxx = (float) at.getScaleX(); + float Mxy = (float) at.getShearX(); + float Myx = (float) at.getShearY(); + float Myy = (float) at.getScaleY(); + if (Mxy == 0f && Myx == 0f) { + if (Mxx == 1f && Myy == 1f) { + return out; + } else { + return new DeltaScaleFilter(out, Mxx, Myy); + } + } else { + return new DeltaTransformFilter(out, Mxx, Mxy, Myx, Myy); + } + } + + public static PathConsumer2D + inverseDeltaTransformConsumer(PathConsumer2D out, + AffineTransform at) + { + if (at == null) { + return out; + } + float Mxx = (float) at.getScaleX(); + float Mxy = (float) at.getShearX(); + float Myx = (float) at.getShearY(); + float Myy = (float) at.getScaleY(); + if (Mxy == 0f && Myx == 0f) { + if (Mxx == 1f && Myy == 1f) { + return out; + } else { + return new DeltaScaleFilter(out, 1.0f/Mxx, 1.0f/Myy); + } + } else { + float det = Mxx * Myy - Mxy * Myx; + return new DeltaTransformFilter(out, + Myy / det, + -Mxy / det, + -Myx / det, + Mxx / det); + } + } + + static final class TranslateFilter implements PathConsumer2D { + private final PathConsumer2D out; + private final float tx; + private final float ty; TranslateFilter(PathConsumer2D out, float tx, float ty) @@ -107,12 +162,12 @@ } } - static class ScaleFilter implements PathConsumer2D { - PathConsumer2D out; - float sx; - float sy; - float tx; - float ty; + static final class ScaleFilter implements PathConsumer2D { + private final PathConsumer2D out; + private final float sx; + private final float sy; + private final float tx; + private final float ty; ScaleFilter(PathConsumer2D out, float sx, float sy, float tx, float ty) @@ -161,14 +216,14 @@ } } - static class TransformFilter implements PathConsumer2D { - PathConsumer2D out; - float Mxx; - float Mxy; - float Mxt; - float Myx; - float Myy; - float Myt; + static final class TransformFilter implements PathConsumer2D { + private final PathConsumer2D out; + private final float Mxx; + private final float Mxy; + private final float Mxt; + private final float Myx; + private final float Myy; + private final float Myt; TransformFilter(PathConsumer2D out, float Mxx, float Mxy, float Mxt, @@ -226,4 +281,113 @@ return 0; } } + + static final class DeltaScaleFilter implements PathConsumer2D { + private final float sx, sy; + private final PathConsumer2D out; + + public DeltaScaleFilter(PathConsumer2D out, float Mxx, float Myy) { + sx = Mxx; + sy = Myy; + this.out = out; + } + + public void moveTo(float x0, float y0) { + out.moveTo(x0 * sx, y0 * sy); + } + + public void lineTo(float x1, float y1) { + out.lineTo(x1 * sx, y1 * sy); + } + + public void quadTo(float x1, float y1, + float x2, float y2) + { + out.quadTo(x1 * sx, y1 * sy, + x2 * sx, y2 * sy); + } + + public void curveTo(float x1, float y1, + float x2, float y2, + float x3, float y3) + { + out.curveTo(x1 * sx, y1 * sy, + x2 * sx, y2 * sy, + x3 * sx, y3 * sy); + } + + public void closePath() { + out.closePath(); + } + + public void pathDone() { + out.pathDone(); + } + + public long getNativeConsumer() { + return 0; + } + } + + static final class DeltaTransformFilter implements PathConsumer2D { + private PathConsumer2D out; + private final float Mxx; + private final float Mxy; + private final float Myx; + private final float Myy; + + DeltaTransformFilter(PathConsumer2D out, + float Mxx, float Mxy, + float Myx, float Myy) + { + this.out = out; + this.Mxx = Mxx; + this.Mxy = Mxy; + this.Myx = Myx; + this.Myy = Myy; + } + + public void moveTo(float x0, float y0) { + out.moveTo(x0 * Mxx + y0 * Mxy, + x0 * Myx + y0 * Myy); + } + + public void lineTo(float x1, float y1) { + out.lineTo(x1 * Mxx + y1 * Mxy, + x1 * Myx + y1 * Myy); + } + + public void quadTo(float x1, float y1, + float x2, float y2) + { + out.quadTo(x1 * Mxx + y1 * Mxy, + x1 * Myx + y1 * Myy, + x2 * Mxx + y2 * Mxy, + x2 * Myx + y2 * Myy); + } + + public void curveTo(float x1, float y1, + float x2, float y2, + float x3, float y3) + { + out.curveTo(x1 * Mxx + y1 * Mxy, + x1 * Myx + y1 * Myy, + x2 * Mxx + y2 * Mxy, + x2 * Myx + y2 * Myy, + x3 * Mxx + y3 * Mxy, + x3 * Myx + y3 * Myy); + } + + public void closePath() { + out.closePath(); + } + + public void pathDone() { + out.pathDone(); + } + + public long getNativeConsumer() { + return 0; + } + } }