7016856: dashing performance was reduced during latest changes to the OpenJDK rasterizer
Optimized dashing, rasterizing, and the flow of transformed coordinates Reviewed-by: flar
This commit is contained in:
parent
8e2b437a8b
commit
4b61d914f1
@ -27,7 +27,7 @@ package sun.java2d.pisces;
|
|||||||
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
class Curve {
|
final class Curve {
|
||||||
|
|
||||||
float ax, ay, bx, by, cx, cy, dx, dy;
|
float ax, ay, bx, by, cx, cy, dx, dy;
|
||||||
float dax, day, dbx, dby;
|
float dax, day, dbx, dby;
|
||||||
@ -101,14 +101,6 @@ class Curve {
|
|||||||
return t * (t * day + dby) + cy;
|
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) {
|
int dxRoots(float[] roots, int off) {
|
||||||
return Helpers.quadraticRoots(dax, dbx, cx, roots, off);
|
return Helpers.quadraticRoots(dax, dbx, cx, roots, off);
|
||||||
}
|
}
|
||||||
@ -131,17 +123,17 @@ class Curve {
|
|||||||
// finds points where the first and second derivative are
|
// finds points where the first and second derivative are
|
||||||
// perpendicular. This happens when g(t) = f'(t)*f''(t) == 0 (where
|
// perpendicular. This happens when g(t) = f'(t)*f''(t) == 0 (where
|
||||||
// * is a dot product). Unfortunately, we have to solve a cubic.
|
// * 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;
|
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 a = 2*(dax*dax + day*day);
|
||||||
final float b = 3*(dax*dbx + day*dby);
|
final float b = 3*(dax*dbx + day*dby);
|
||||||
final float c = 2*(dax*cx + day*cy) + dbx*dbx + dby*dby;
|
final float c = 2*(dax*cx + day*cy) + dbx*dbx + dby*dby;
|
||||||
final float d = dbx*cx + dby*cy;
|
final float d = dbx*cx + dby*cy;
|
||||||
// TODO: We might want to divide the polynomial by a to make the
|
return Helpers.cubicRootsInAB(a, b, c, d, pts, off, 0f, 1f);
|
||||||
// coefficients smaller. This won't change the roots.
|
|
||||||
return Helpers.cubicRootsInAB(a, b, c, d, pts, off, err, 0f, 1f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tries to find the roots of the function ROC(t)-w in [0, 1). It uses
|
// Tries to find the roots of the function ROC(t)-w in [0, 1). It uses
|
||||||
@ -161,7 +153,7 @@ class Curve {
|
|||||||
// no OOB exception, because by now off<=6, and roots.length >= 10
|
// no OOB exception, because by now off<=6, and roots.length >= 10
|
||||||
assert off <= 6 && roots.length >= 10;
|
assert off <= 6 && roots.length >= 10;
|
||||||
int ret = off;
|
int ret = off;
|
||||||
int numPerpdfddf = perpendiculardfddf(roots, off, err);
|
int numPerpdfddf = perpendiculardfddf(roots, off);
|
||||||
float t0 = 0, ft0 = ROCsq(t0) - w*w;
|
float t0 = 0, ft0 = ROCsq(t0) - w*w;
|
||||||
roots[off + numPerpdfddf] = 1f; // always check interval end points
|
roots[off + numPerpdfddf] = 1f; // always check interval end points
|
||||||
numPerpdfddf++;
|
numPerpdfddf++;
|
||||||
@ -189,8 +181,9 @@ class Curve {
|
|||||||
// A slight modification of the false position algorithm on wikipedia.
|
// A slight modification of the false position algorithm on wikipedia.
|
||||||
// This only works for the ROCsq-x functions. It might be nice to have
|
// 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.
|
// the function as an argument, but that would be awkward in java6.
|
||||||
// It is something to consider for java7, depending on how closures
|
// TODO: It is something to consider for java8 (or whenever lambda
|
||||||
// and function objects turn out. Same goes for the newton's method
|
// expressions make it into the language), depending on how closures
|
||||||
|
// and turn out. Same goes for the newton's method
|
||||||
// algorithm in Helpers.java
|
// algorithm in Helpers.java
|
||||||
private float falsePositionROCsqMinusX(float x0, float x1,
|
private float falsePositionROCsqMinusX(float x0, float x1,
|
||||||
final float x, final float err)
|
final float x, final float err)
|
||||||
@ -203,7 +196,7 @@ class Curve {
|
|||||||
for (int i = 0; i < iterLimit && Math.abs(t - s) > err * Math.abs(t + s); i++) {
|
for (int i = 0; i < iterLimit && Math.abs(t - s) > err * Math.abs(t + s); i++) {
|
||||||
r = (fs * t - ft * s) / (fs - ft);
|
r = (fs * t - ft * s) / (fs - ft);
|
||||||
fr = ROCsq(r) - x;
|
fr = ROCsq(r) - x;
|
||||||
if (fr * ft > 0) {// have the same sign
|
if (sameSign(fr, ft)) {
|
||||||
ft = fr; t = r;
|
ft = fr; t = r;
|
||||||
if (side < 0) {
|
if (side < 0) {
|
||||||
fs /= (1 << (-side));
|
fs /= (1 << (-side));
|
||||||
@ -226,55 +219,65 @@ class Curve {
|
|||||||
return r;
|
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
|
// returns the radius of curvature squared at t of this curve
|
||||||
// see http://en.wikipedia.org/wiki/Radius_of_curvature_(applications)
|
// see http://en.wikipedia.org/wiki/Radius_of_curvature_(applications)
|
||||||
private float ROCsq(final float t) {
|
private float ROCsq(final float t) {
|
||||||
final float dx = dxat(t);
|
// dx=xat(t) and dy=yat(t). These calls have been inlined for efficiency
|
||||||
final float dy = dyat(t);
|
final float dx = t * (t * dax + dbx) + cx;
|
||||||
final float ddx = ddxat(t);
|
final float dy = t * (t * day + dby) + cy;
|
||||||
final float ddy = ddyat(t);
|
final float ddx = 2 * dax * t + dbx;
|
||||||
|
final float ddy = 2 * day * t + dby;
|
||||||
final float dx2dy2 = dx*dx + dy*dy;
|
final float dx2dy2 = dx*dx + dy*dy;
|
||||||
final float ddx2ddy2 = ddx*ddx + ddy*ddy;
|
final float ddx2ddy2 = ddx*ddx + ddy*ddy;
|
||||||
final float ddxdxddydy = ddx*dx + ddy*dy;
|
final float ddxdxddydy = ddx*dx + ddy*dy;
|
||||||
float ret = ((dx2dy2*dx2dy2) / (dx2dy2 * ddx2ddy2 - ddxdxddydy*ddxdxddydy))*dx2dy2;
|
return dx2dy2*((dx2dy2*dx2dy2) / (dx2dy2 * ddx2ddy2 - ddxdxddydy*ddxdxddydy));
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// curve to be broken should be in pts[0]
|
// curve to be broken should be in pts
|
||||||
// this will change the contents of both pts and Ts
|
// 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
|
// 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,
|
// of t values at which to subdivide. An array statisfies this condition,
|
||||||
// but is unnecessarily restrictive. Ts should be an Iterator<Float> instead.
|
// but is unnecessarily restrictive. Ts should be an Iterator<Float> instead.
|
||||||
// Doing this will also make dashing easier, since we could easily make
|
// Doing this will also make dashing easier, since we could easily make
|
||||||
// LengthIterator an Iterator<Float> and feed it to this function to simplify
|
// LengthIterator an Iterator<Float> and feed it to this function to simplify
|
||||||
// the loop in Dasher.somethingTo.
|
// the loop in Dasher.somethingTo.
|
||||||
static Iterator<float[]> breakPtsAtTs(final float[][] pts, final int type,
|
static Iterator<Integer> breakPtsAtTs(final float[] pts, final int type,
|
||||||
final float[] Ts, final int numTs)
|
final float[] Ts, final int numTs)
|
||||||
{
|
{
|
||||||
assert pts.length >= 2 && pts[0].length >= 8 && numTs <= Ts.length;
|
assert pts.length >= 2*type && numTs <= Ts.length;
|
||||||
return new Iterator<float[]>() {
|
return new Iterator<Integer>() {
|
||||||
int nextIdx = 0;
|
// 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;
|
int nextCurveIdx = 0;
|
||||||
|
Integer curCurveOff = i0;
|
||||||
float prevT = 0;
|
float prevT = 0;
|
||||||
|
|
||||||
@Override public boolean hasNext() {
|
@Override public boolean hasNext() {
|
||||||
return nextCurveIdx < numTs + 1;
|
return nextCurveIdx < numTs + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public float[] next() {
|
@Override public Integer next() {
|
||||||
float[] ret;
|
Integer ret;
|
||||||
if (nextCurveIdx < numTs) {
|
if (nextCurveIdx < numTs) {
|
||||||
float curT = Ts[nextCurveIdx];
|
float curT = Ts[nextCurveIdx];
|
||||||
float splitT = (curT - prevT) / (1 - prevT);
|
float splitT = (curT - prevT) / (1 - prevT);
|
||||||
Helpers.subdivideAt(splitT,
|
Helpers.subdivideAt(splitT,
|
||||||
pts[nextIdx], 0,
|
pts, curCurveOff,
|
||||||
pts[nextIdx], 0,
|
pts, 0,
|
||||||
pts[1-nextIdx], 0, type);
|
pts, type, type);
|
||||||
updateTs(Ts, Ts[nextCurveIdx], nextCurveIdx + 1, numTs - nextCurveIdx - 1);
|
prevT = curT;
|
||||||
ret = pts[nextIdx];
|
ret = i0;
|
||||||
nextIdx = 1 - nextIdx;
|
curCurveOff = itype;
|
||||||
} else {
|
} else {
|
||||||
ret = pts[nextIdx];
|
ret = curCurveOff;
|
||||||
}
|
}
|
||||||
nextCurveIdx++;
|
nextCurveIdx++;
|
||||||
return ret;
|
return ret;
|
||||||
@ -283,12 +286,5 @@ class Curve {
|
|||||||
@Override public void remove() {}
|
@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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ import sun.awt.geom.PathConsumer2D;
|
|||||||
* semantics are unclear.
|
* 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 PathConsumer2D out;
|
||||||
private final float[] dash;
|
private final float[] dash;
|
||||||
@ -169,7 +169,7 @@ public class Dasher implements sun.awt.geom.PathConsumer2D {
|
|||||||
float dx = x1 - x0;
|
float dx = x1 - x0;
|
||||||
float dy = y1 - y0;
|
float dy = y1 - y0;
|
||||||
|
|
||||||
float len = (float) Math.hypot(dx, dy);
|
float len = (float) Math.sqrt(dx*dx + dy*dy);
|
||||||
|
|
||||||
if (len == 0) {
|
if (len == 0) {
|
||||||
return;
|
return;
|
||||||
@ -226,7 +226,7 @@ public class Dasher implements sun.awt.geom.PathConsumer2D {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (li == null) {
|
if (li == null) {
|
||||||
li = new LengthIterator(4, 0.0001f);
|
li = new LengthIterator(4, 0.01f);
|
||||||
}
|
}
|
||||||
li.initializeIterationOnCurve(curCurvepts, type);
|
li.initializeIterationOnCurve(curCurvepts, type);
|
||||||
|
|
||||||
@ -237,9 +237,9 @@ public class Dasher implements sun.awt.geom.PathConsumer2D {
|
|||||||
while ((t = li.next(leftInThisDashSegment)) < 1) {
|
while ((t = li.next(leftInThisDashSegment)) < 1) {
|
||||||
if (t != 0) {
|
if (t != 0) {
|
||||||
Helpers.subdivideAt((t - lastSplitT) / (1 - lastSplitT),
|
Helpers.subdivideAt((t - lastSplitT) / (1 - lastSplitT),
|
||||||
curCurvepts, curCurveoff,
|
curCurvepts, curCurveoff,
|
||||||
curCurvepts, 0,
|
curCurvepts, 0,
|
||||||
curCurvepts, type, type);
|
curCurvepts, type, type);
|
||||||
lastSplitT = t;
|
lastSplitT = t;
|
||||||
goTo(curCurvepts, 2, type);
|
goTo(curCurvepts, 2, type);
|
||||||
curCurveoff = type;
|
curCurveoff = type;
|
||||||
@ -307,6 +307,11 @@ public class Dasher implements sun.awt.geom.PathConsumer2D {
|
|||||||
private int recLevel;
|
private int recLevel;
|
||||||
private boolean done;
|
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) {
|
public LengthIterator(int reclimit, float err) {
|
||||||
this.limit = reclimit;
|
this.limit = reclimit;
|
||||||
this.minTincrement = 1f / (1 << limit);
|
this.minTincrement = 1f / (1 << limit);
|
||||||
@ -344,11 +349,52 @@ public class Dasher implements sun.awt.geom.PathConsumer2D {
|
|||||||
this.lastSegLen = 0;
|
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
|
// returns the t value where the remaining curve should be split in
|
||||||
// order for the left subdivided curve to have length len. If len
|
// order for the left subdivided curve to have length len. If len
|
||||||
// is >= than the length of the uniterated curve, it returns 1.
|
// is >= than the length of the uniterated curve, it returns 1.
|
||||||
public float next(float len) {
|
public float next(final float len) {
|
||||||
float targetLength = lenAtLastSplit + len;
|
final float targetLength = lenAtLastSplit + len;
|
||||||
while(lenAtNextT < targetLength) {
|
while(lenAtNextT < targetLength) {
|
||||||
if (done) {
|
if (done) {
|
||||||
lastSegLen = lenAtNextT - lenAtLastSplit;
|
lastSegLen = lenAtNextT - lenAtLastSplit;
|
||||||
@ -357,8 +403,46 @@ public class Dasher implements sun.awt.geom.PathConsumer2D {
|
|||||||
goToNextLeaf();
|
goToNextLeaf();
|
||||||
}
|
}
|
||||||
lenAtLastSplit = targetLength;
|
lenAtLastSplit = targetLength;
|
||||||
float t = binSearchForLen(lenAtLastSplit - lenAtLastT,
|
final float leaflen = lenAtNextT - lenAtLastT;
|
||||||
recCurveStack[recLevel], curveType, lenAtNextT - lenAtLastT, ERR);
|
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
|
// t is relative to the current leaf, so we must make it a valid parameter
|
||||||
// of the original curve.
|
// of the original curve.
|
||||||
t = t * (nextT - lastT) + lastT;
|
t = t * (nextT - lastT) + lastT;
|
||||||
@ -379,36 +463,6 @@ public class Dasher implements sun.awt.geom.PathConsumer2D {
|
|||||||
return lastSegLen;
|
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
|
// 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.
|
// preconditions: must be on a leaf, and that leaf must not be the root.
|
||||||
private void goToNextLeaf() {
|
private void goToNextLeaf() {
|
||||||
@ -437,6 +491,9 @@ public class Dasher implements sun.awt.geom.PathConsumer2D {
|
|||||||
lenAtLastT = lenAtNextT;
|
lenAtLastT = lenAtNextT;
|
||||||
nextT += (1 << (limit - recLevel)) * minTincrement;
|
nextT += (1 << (limit - recLevel)) * minTincrement;
|
||||||
lenAtNextT += len;
|
lenAtNextT += len;
|
||||||
|
// invalidate caches
|
||||||
|
flatLeafCoefCache[2] = -1;
|
||||||
|
cachedHaveLowAcceleration = -1;
|
||||||
} else {
|
} else {
|
||||||
Helpers.subdivide(recCurveStack[recLevel], 0,
|
Helpers.subdivide(recCurveStack[recLevel], 0,
|
||||||
recCurveStack[recLevel+1], 0,
|
recCurveStack[recLevel+1], 0,
|
||||||
@ -450,11 +507,24 @@ public class Dasher implements sun.awt.geom.PathConsumer2D {
|
|||||||
// this is a bit of a hack. It returns -1 if we're not on a leaf, and
|
// 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.
|
// the length of the leaf if we are on a leaf.
|
||||||
private float onLeaf() {
|
private float onLeaf() {
|
||||||
float polylen = Helpers.polyLineLength(recCurveStack[recLevel], 0, curveType);
|
float[] curve = recCurveStack[recLevel];
|
||||||
float linelen = Helpers.linelen(recCurveStack[recLevel][0], recCurveStack[recLevel][1],
|
float polyLen = 0;
|
||||||
recCurveStack[recLevel][curveType - 2], recCurveStack[recLevel][curveType - 1]);
|
|
||||||
return (polylen - linelen < ERR || recLevel == limit) ?
|
float x0 = curve[0], y0 = curve[1];
|
||||||
(polylen + linelen)/2 : -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +26,12 @@
|
|||||||
package sun.java2d.pisces;
|
package sun.java2d.pisces;
|
||||||
|
|
||||||
import java.util.Arrays;
|
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 {
|
final class Helpers {
|
||||||
private Helpers() {
|
private Helpers() {
|
||||||
@ -75,100 +81,74 @@ final class Helpers {
|
|||||||
return ret - off;
|
return ret - off;
|
||||||
}
|
}
|
||||||
|
|
||||||
// find the roots of g(t) = a*t^3 + b*t^2 + c*t + d in [A,B)
|
// find the roots of g(t) = d*t^3 + a*t^2 + b*t + c in [A,B)
|
||||||
// We will not use Cardano's method, since it is complicated and
|
static int cubicRootsInAB(float d, float a, float b, float c,
|
||||||
// involves too many square and cubic roots. We will use Newton's method.
|
float[] pts, final int off,
|
||||||
// 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,
|
|
||||||
final float A, final float B)
|
final float A, final float B)
|
||||||
{
|
{
|
||||||
if (a == 0) {
|
if (d == 0) {
|
||||||
return quadraticRoots(b, c, d, pts, off);
|
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).
|
||||||
|
|
||||||
int ret = off;
|
/* normal form: x^3 + ax^2 + bx + c = 0 */
|
||||||
|
a /= d;
|
||||||
|
b /= d;
|
||||||
|
c /= d;
|
||||||
|
|
||||||
// we don't actually care much about the extrema themselves. We
|
// substitute x = y - A/3 to eliminate quadratic term:
|
||||||
// only use them to ensure that g(t) is monotonic in each
|
// x^3 +Px + Q = 0
|
||||||
// interval [pts[i],pts[i+1] (for i in off...off+numCritPts+1).
|
//
|
||||||
// This will allow us to determine intervals containing exactly
|
// Since we actually need P/3 and Q/2 for all of the
|
||||||
// one root.
|
// calculations that follow, we will calculate
|
||||||
// The end points of the interval are always local extrema.
|
// p = P/3
|
||||||
pts[off] = A;
|
// q = Q/2
|
||||||
pts[off + numCritPts + 1] = B;
|
// instead and use those values for simplicity of the code.
|
||||||
numCritPts += 2;
|
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);
|
||||||
|
|
||||||
float x0 = pts[off], fx0 = evalCubic(a, b, c, d, x0);
|
/* use Cardano's formula */
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// precondition: the polynomial to be evaluated must not be 0 at x0.
|
double cb_p = p * p * p;
|
||||||
static float CubicNewton(final float a, final float b,
|
double D = q * q + cb_p;
|
||||||
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++;
|
|
||||||
}
|
|
||||||
return x1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// fills the input array with numbers 0, INC, 2*INC, ...
|
int num;
|
||||||
static void fillWithIdxes(final float[] data, final int[] idxes) {
|
if (D < 0) {
|
||||||
if (idxes.length > 0) {
|
// see: http://en.wikipedia.org/wiki/Cubic_function#Trigonometric_.28and_hyperbolic.29_method
|
||||||
idxes[0] = 0;
|
final double phi = 1.0/3 * acos(-q / sqrt(-cb_p));
|
||||||
for (int i = 1; i < idxes.length; i++) {
|
final double t = 2 * sqrt(-p);
|
||||||
idxes[i] = idxes[i-1] + (int)data[idxes[i-1]];
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static void fillWithIdxes(final int[] idxes, final int inc) {
|
final float sub = 1.0f/3 * a;
|
||||||
if (idxes.length > 0) {
|
|
||||||
idxes[0] = 0;
|
for (int i = 0; i < num; ++i) {
|
||||||
for (int i = 1; i < idxes.length; i++) {
|
pts[ off+i ] -= sub;
|
||||||
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
|
// These use a hardcoded factor of 2 for increasing sizes. Perhaps this
|
||||||
@ -182,6 +162,7 @@ final class Helpers {
|
|||||||
}
|
}
|
||||||
return Arrays.copyOf(in, 2 * (cursize + numToAdd));
|
return Arrays.copyOf(in, 2 * (cursize + numToAdd));
|
||||||
}
|
}
|
||||||
|
|
||||||
static int[] widenArray(int[] in, final int cursize, final int numToAdd) {
|
static int[] widenArray(int[] in, final int cursize, final int numToAdd) {
|
||||||
if (in.length >= cursize + numToAdd) {
|
if (in.length >= cursize + numToAdd) {
|
||||||
return in;
|
return in;
|
||||||
@ -208,7 +189,7 @@ final class Helpers {
|
|||||||
{
|
{
|
||||||
int ret = off;
|
int ret = off;
|
||||||
for (int i = off; i < off + len; i++) {
|
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];
|
nums[ret++] = nums[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -225,7 +206,9 @@ final class Helpers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static float linelen(float x1, float y1, float x2, float y2) {
|
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,
|
static void subdivide(float[] src, int srcoff, float[] left, int leftoff,
|
||||||
|
@ -32,7 +32,7 @@ import java.util.Arrays;
|
|||||||
*
|
*
|
||||||
* @see PiscesRenderer#render
|
* @see PiscesRenderer#render
|
||||||
*/
|
*/
|
||||||
public final class PiscesCache {
|
final class PiscesCache {
|
||||||
|
|
||||||
final int bboxX0, bboxY0, bboxX1, bboxY1;
|
final int bboxX0, bboxY0, bboxX1, bboxY1;
|
||||||
|
|
||||||
|
@ -27,7 +27,6 @@ package sun.java2d.pisces;
|
|||||||
|
|
||||||
import java.awt.Shape;
|
import java.awt.Shape;
|
||||||
import java.awt.BasicStroke;
|
import java.awt.BasicStroke;
|
||||||
import java.awt.geom.NoninvertibleTransformException;
|
|
||||||
import java.awt.geom.Path2D;
|
import java.awt.geom.Path2D;
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.PathIterator;
|
import java.awt.geom.PathIterator;
|
||||||
@ -250,7 +249,7 @@ public class PiscesRenderingEngine extends RenderingEngine {
|
|||||||
float dashphase,
|
float dashphase,
|
||||||
PathConsumer2D pc2d)
|
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
|
// with the pre-transformation coordinates. This will repeat a lot of
|
||||||
// computations done in the path iterator, but the alternative is to
|
// computations done in the path iterator, but the alternative is to
|
||||||
// work with transformed paths and compute untransformed coordinates
|
// work with transformed paths and compute untransformed coordinates
|
||||||
@ -265,7 +264,7 @@ public class PiscesRenderingEngine extends RenderingEngine {
|
|||||||
// transformation after the path processing has been done.
|
// transformation after the path processing has been done.
|
||||||
// We can't do this if normalization is on, because it isn't a good
|
// We can't do this if normalization is on, because it isn't a good
|
||||||
// idea to normalize before the transformation is applied.
|
// idea to normalize before the transformation is applied.
|
||||||
AffineTransform inat = null;
|
AffineTransform strokerat = null;
|
||||||
AffineTransform outat = null;
|
AffineTransform outat = null;
|
||||||
|
|
||||||
PathIterator pi = null;
|
PathIterator pi = null;
|
||||||
@ -284,9 +283,9 @@ public class PiscesRenderingEngine extends RenderingEngine {
|
|||||||
// again so, nothing can be drawn.
|
// again so, nothing can be drawn.
|
||||||
|
|
||||||
// Every path needs an initial moveTo and a pathDone. If these
|
// 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,
|
// 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.
|
// but the pathDone is definitely needed.
|
||||||
pc2d.moveTo(0, 0);
|
pc2d.moveTo(0, 0);
|
||||||
pc2d.pathDone();
|
pc2d.pathDone();
|
||||||
@ -313,25 +312,32 @@ public class PiscesRenderingEngine extends RenderingEngine {
|
|||||||
if (normalize != NormMode.OFF) {
|
if (normalize != NormMode.OFF) {
|
||||||
pi = new NormalizingPathIterator(pi, normalize);
|
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 {
|
} 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) {
|
if (normalize != NormMode.OFF) {
|
||||||
try {
|
strokerat = at;
|
||||||
inat = outat.createInverse();
|
|
||||||
} catch (NoninvertibleTransformException e) {
|
|
||||||
// we made sure this can't happen
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
pi = src.getPathIterator(at);
|
pi = src.getPathIterator(at);
|
||||||
pi = new NormalizingPathIterator(pi, normalize);
|
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 {
|
} else {
|
||||||
|
outat = at;
|
||||||
pi = src.getPathIterator(null);
|
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 {
|
} else {
|
||||||
@ -343,13 +349,17 @@ public class PiscesRenderingEngine extends RenderingEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.transformConsumer(pc2d, outat);
|
||||||
|
pc2d = TransformingPathConsumer2D.deltaTransformConsumer(pc2d, strokerat);
|
||||||
pc2d = new Stroker(pc2d, width, caps, join, miterlimit);
|
pc2d = new Stroker(pc2d, width, caps, join, miterlimit);
|
||||||
if (dashes != null) {
|
if (dashes != null) {
|
||||||
pc2d = new Dasher(pc2d, dashes, dashphase);
|
pc2d = new Dasher(pc2d, dashes, dashphase);
|
||||||
}
|
}
|
||||||
pc2d = TransformingPathConsumer2D.transformConsumer(pc2d, inat);
|
pc2d = TransformingPathConsumer2D.inverseDeltaTransformConsumer(pc2d, strokerat);
|
||||||
|
|
||||||
pathTo(pi, pc2d);
|
pathTo(pi, pc2d);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -588,9 +598,9 @@ public class PiscesRenderingEngine extends RenderingEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Renderer r = new Renderer(3, 3,
|
Renderer r = new Renderer(3, 3,
|
||||||
clip.getLoX(), clip.getLoY(),
|
clip.getLoX(), clip.getLoY(),
|
||||||
clip.getWidth(), clip.getHeight(),
|
clip.getWidth(), clip.getHeight(),
|
||||||
PathIterator.WIND_EVEN_ODD);
|
PathIterator.WIND_EVEN_ODD);
|
||||||
|
|
||||||
r.moveTo((float) x, (float) y);
|
r.moveTo((float) x, (float) y);
|
||||||
r.lineTo((float) (x+dx1), (float) (y+dy1));
|
r.lineTo((float) (x+dx1), (float) (y+dy1));
|
||||||
|
@ -30,7 +30,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
|
|
||||||
import sun.java2d.pipe.AATileGenerator;
|
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;
|
public static final int TILE_SIZE = PiscesCache.TILE_SIZE;
|
||||||
|
|
||||||
// perhaps we should be using weak references here, but right now
|
// perhaps we should be using weak references here, but right now
|
||||||
|
@ -25,12 +25,9 @@
|
|||||||
|
|
||||||
package sun.java2d.pisces;
|
package sun.java2d.pisces;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Iterator;
|
|
||||||
|
|
||||||
import sun.awt.geom.PathConsumer2D;
|
import sun.awt.geom.PathConsumer2D;
|
||||||
|
|
||||||
public class Renderer implements PathConsumer2D {
|
final class Renderer implements PathConsumer2D {
|
||||||
|
|
||||||
private class ScanlineIterator {
|
private class ScanlineIterator {
|
||||||
|
|
||||||
@ -39,115 +36,81 @@ public class Renderer implements PathConsumer2D {
|
|||||||
// crossing bounds. The bounds are not necessarily tight (the scan line
|
// crossing bounds. The bounds are not necessarily tight (the scan line
|
||||||
// at minY, for example, might have no crossings). The x bounds will
|
// at minY, for example, might have no crossings). The x bounds will
|
||||||
// be accumulated as crossings are computed.
|
// be accumulated as crossings are computed.
|
||||||
private int minY, maxY;
|
private final int maxY;
|
||||||
private int nextY;
|
private int nextY;
|
||||||
|
|
||||||
// indices into the segment pointer lists. They indicate the "active"
|
// indices into the segment pointer lists. They indicate the "active"
|
||||||
// sublist in the segment lists (the portion of the list that contains
|
// sublist in the segment lists (the portion of the list that contains
|
||||||
// all the segments that cross the next scan line).
|
// all the segments that cross the next scan line).
|
||||||
private int elo, ehi;
|
private int edgeCount;
|
||||||
private final int[] edgePtrs;
|
private int[] edgePtrs;
|
||||||
private int qlo, qhi;
|
|
||||||
private final int[] quadPtrs;
|
|
||||||
private int clo, chi;
|
|
||||||
private final int[] curvePtrs;
|
|
||||||
|
|
||||||
private static final int INIT_CROSSINGS_SIZE = 10;
|
private static final int INIT_CROSSINGS_SIZE = 10;
|
||||||
|
|
||||||
private ScanlineIterator() {
|
private ScanlineIterator() {
|
||||||
crossings = new int[INIT_CROSSINGS_SIZE];
|
crossings = new int[INIT_CROSSINGS_SIZE];
|
||||||
|
edgePtrs = 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);
|
|
||||||
|
|
||||||
// We don't care if we clip some of the line off with ceil, since
|
// 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
|
// no scan line crossings will be eliminated (in fact, the ceil is
|
||||||
// the y of the first scan line crossing).
|
// the y of the first scan line crossing).
|
||||||
nextY = minY = Math.max(boundsMinY, (int)Math.ceil(edgeMinY));
|
final int minY = getFirstScanLineCrossing();
|
||||||
maxY = Math.min(boundsMaxY, (int)Math.ceil(edgeMaxY));
|
nextY = minY;
|
||||||
|
maxY = getScanLineCrossingEnd()-1;
|
||||||
for (elo = 0; elo < numEdges && edges[edgePtrs[elo]+YMAX] <= minY; elo++)
|
edgeCount = 0;
|
||||||
;
|
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int next() {
|
private int next() {
|
||||||
// we go through the active lists and remove segments that don't cross
|
int cury = nextY++;
|
||||||
// the nextY scanline.
|
int bucket = cury - boundsMinY;
|
||||||
int crossingIdx = 0;
|
int count = this.edgeCount;
|
||||||
for (int i = elo; i < ehi; i++) {
|
int ptrs[] = this.edgePtrs;
|
||||||
if (edges[edgePtrs[i]+YMAX] <= nextY) {
|
int bucketcount = edgeBucketCounts[bucket];
|
||||||
edgePtrs[i] = edgePtrs[elo++];
|
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++) {
|
ptrs = Helpers.widenArray(ptrs, count, bucketcount >> 1);
|
||||||
if (quads[quadPtrs[i]+YMAX] <= nextY) {
|
for (int ecur = edgeBuckets[bucket]; ecur != NULL; ecur = (int)edges[ecur+NEXT]) {
|
||||||
quadPtrs[i] = quadPtrs[qlo++];
|
ptrs[count++] = ecur;
|
||||||
|
// REMIND: Adjust start Y if necessary
|
||||||
|
}
|
||||||
|
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 = 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;
|
||||||
for (int i = clo; i < chi; i++) {
|
while (--j >= 0) {
|
||||||
if (curves[curvePtrs[i]+YMAX] <= nextY) {
|
int jcross = xings[j];
|
||||||
curvePtrs[i] = curvePtrs[clo++];
|
if (jcross <= cross) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
xings[j+1] = jcross;
|
||||||
|
ptrs[j+1] = ptrs[j];
|
||||||
}
|
}
|
||||||
|
xings[j+1] = cross;
|
||||||
|
ptrs[j+1] = ecur;
|
||||||
}
|
}
|
||||||
|
return count;
|
||||||
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++;
|
|
||||||
}
|
|
||||||
for (int i = clo; i < chi; i++) {
|
|
||||||
int ptr = curvePtrs[i];
|
|
||||||
addCrossing(nextY, (int)curves[ptr+CURX], curves[ptr+OR], crossingIdx);
|
|
||||||
curveGoToNextY(ptr);
|
|
||||||
crossingIdx++;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasNext() {
|
private boolean hasNext() {
|
||||||
@ -157,51 +120,7 @@ public class Renderer implements PathConsumer2D {
|
|||||||
private int curY() {
|
private int curY() {
|
||||||
return nextY - 1;
|
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 @@ public class Renderer implements PathConsumer2D {
|
|||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
// TODO(maybe): very tempting to use fixed point here. A lot of opportunities
|
// TODO(maybe): very tempting to use fixed point here. A lot of opportunities
|
||||||
// for shifts and just removing certain operations altogether.
|
// 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.
|
// common to all types of input path segments.
|
||||||
private static final int YMIN = 0;
|
private static final int YMAX = 0;
|
||||||
private static final int YMAX = 1;
|
private static final int CURX = 1;
|
||||||
private static final int CURX = 2;
|
// NEXT and OR are meant to be indices into "int" fields, but arrays must
|
||||||
// this and OR are meant to be indeces into "int" fields, but arrays must
|
|
||||||
// be homogenous, so every field is a float. However floats can represent
|
// be homogenous, so every field is a float. However floats can represent
|
||||||
// exactly up to 26 bit ints, so we're ok.
|
// exactly up to 26 bit ints, so we're ok.
|
||||||
private static final int CURY = 3;
|
private static final int OR = 2;
|
||||||
private static final int OR = 4;
|
private static final int SLOPE = 3;
|
||||||
|
private static final int NEXT = 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 float edgeMinY = Float.POSITIVE_INFINITY;
|
private float edgeMinY = Float.POSITIVE_INFINITY;
|
||||||
private float edgeMaxY = Float.NEGATIVE_INFINITY;
|
private float edgeMaxY = Float.NEGATIVE_INFINITY;
|
||||||
private float edgeMinX = Float.POSITIVE_INFINITY;
|
private float edgeMinX = Float.POSITIVE_INFINITY;
|
||||||
private float edgeMaxX = Float.NEGATIVE_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 float[] edges = null;
|
||||||
|
private int[] edgeBuckets = null;
|
||||||
|
private int[] edgeBucketCounts = null; // 2*newedges + (1 if pruning needed)
|
||||||
private int numEdges;
|
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 DEC_BND = 20f;
|
||||||
private static final float INC_BND = 8f;
|
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
|
// Flattens using adaptive forward differencing. This only carries out
|
||||||
// one iteration of the AFD loop. All it does is update AFD variables (i.e.
|
// 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).
|
// X0, Y0, D*[X|Y], COUNT; not variables used for computing scanline crossings).
|
||||||
private int executeQuadAFDIteration(int idx) {
|
private void quadBreakIntoLinesAndAdd(float x0, float y0,
|
||||||
int count = (int)quads[idx+COUNT];
|
final Curve c,
|
||||||
float ddx = quads[idx+DDX];
|
final float x2, final float y2) {
|
||||||
float ddy = quads[idx+DDY];
|
final float QUAD_DEC_BND = 32;
|
||||||
float dx = quads[idx+DX];
|
final int countlg = 4;
|
||||||
float dy = quads[idx+DY];
|
int count = 1 << countlg;
|
||||||
|
int countsq = count * count;
|
||||||
while (Math.abs(ddx) > DEC_BND || Math.abs(ddy) > DEC_BND) {
|
float maxDD = Math.max(c.dbx / countsq, c.dby / countsq);
|
||||||
ddx = ddx / 4;
|
while (maxDD > QUAD_DEC_BND) {
|
||||||
ddy = ddy / 4;
|
maxDD /= 4;
|
||||||
dx = (dx - ddx) / 2;
|
|
||||||
dy = (dy - ddy) / 2;
|
|
||||||
count <<= 1;
|
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) {
|
countsq = count * count;
|
||||||
dx = 2 * dx + ddx;
|
final float ddx = c.dbx / countsq;
|
||||||
dy = 2 * dy + ddy;
|
final float ddy = c.dby / countsq;
|
||||||
ddx = 4 * ddx;
|
float dx = c.bx / countsq + c.cx / count;
|
||||||
ddy = 4 * ddy;
|
float dy = c.by / countsq + c.cy / count;
|
||||||
count >>= 1;
|
|
||||||
}
|
while (count-- > 1) {
|
||||||
count--;
|
float x1 = x0 + dx;
|
||||||
if (count > 0) {
|
|
||||||
quads[idx+X0] += dx;
|
|
||||||
dx += ddx;
|
dx += ddx;
|
||||||
quads[idx+Y0] += dy;
|
float y1 = y0 + dy;
|
||||||
dy += ddy;
|
dy += ddy;
|
||||||
} else {
|
addLine(x0, y0, x1, y1);
|
||||||
quads[idx+X0] = quads[idx+XL];
|
x0 = x1;
|
||||||
quads[idx+Y0] = quads[idx+YMAX];
|
y0 = y1;
|
||||||
}
|
}
|
||||||
quads[idx+COUNT] = count;
|
addLine(x0, y0, x2, y2);
|
||||||
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];
|
|
||||||
|
|
||||||
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) {
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// x0, y0 and x3,y3 are the endpoints of the curve. We could compute these
|
||||||
private void initLine(final int idx, float[] pts, int or) {
|
// using c.xat(0),c.yat(0) and c.xat(1),c.yat(1), but this might introduce
|
||||||
edges[idx+SLOPE] = (pts[2] - pts[0]) / (pts[3] - pts[1]);
|
// numerical errors, and our callers already have the exact values.
|
||||||
edges[idx+CURX] = pts[0] + (edges[idx+CURY] - pts[1]) * edges[idx+SLOPE];
|
// 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,
|
||||||
private void initQuad(final int idx, float[] points, int or) {
|
final Curve c,
|
||||||
|
final float x3, final float y3) {
|
||||||
final int countlg = 3;
|
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
|
// the dx and dy refer to forward differencing variables, not the last
|
||||||
// coefficients of the "points" polynomial
|
// coefficients of the "points" polynomial
|
||||||
final float ddx, ddy, dx, dy;
|
float dddx, dddy, 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);
|
|
||||||
dddx = 2f * c.dax / (1 << (3 * countlg));
|
dddx = 2f * c.dax / (1 << (3 * countlg));
|
||||||
dddy = 2f * c.day / (1 << (3 * countlg));
|
dddy = 2f * c.day / (1 << (3 * countlg));
|
||||||
|
|
||||||
@ -480,93 +219,100 @@ public class Renderer implements PathConsumer2D {
|
|||||||
dx = c.ax / (1 << (3 * countlg)) + c.bx / (1 << (2 * countlg)) + c.cx / (1 << countlg);
|
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);
|
dy = c.ay / (1 << (3 * countlg)) + c.by / (1 << (2 * countlg)) + c.cy / (1 << countlg);
|
||||||
|
|
||||||
curves[idx+DDDX] = dddx;
|
// we use x0, y0 to walk the line
|
||||||
curves[idx+DDDY] = dddy;
|
float x1 = x0, y1 = y0;
|
||||||
curves[idx+DDX] = ddx;
|
while (count > 0) {
|
||||||
curves[idx+DDY] = ddy;
|
while (Math.abs(ddx) > DEC_BND || Math.abs(ddy) > DEC_BND) {
|
||||||
curves[idx+DX] = dx;
|
dddx /= 8;
|
||||||
curves[idx+DY] = dy;
|
dddy /= 8;
|
||||||
curves[idx+COUNT] = count;
|
ddx = ddx/4 - dddx;
|
||||||
curves[idx+XL] = points[6];
|
ddy = ddy/4 - dddy;
|
||||||
curves[idx+X0] = points[0];
|
dx = (dx - ddx) / 2;
|
||||||
curves[idx+Y0] = points[1];
|
dy = (dy - ddy) / 2;
|
||||||
executeCurveAFDIteration(idx);
|
count <<= 1;
|
||||||
float x1 = curves[idx+X0], y1 = curves[idx+Y0];
|
}
|
||||||
curves[idx+CURSLOPE] = (x1 - points[0]) / (y1 - points[1]);
|
// can only do this on even "count" values, because we must divide count by 2
|
||||||
curves[idx+CURX] = points[0] + (curves[idx+CURY] - points[1])*curves[idx+CURSLOPE];
|
while (count % 2 == 0 && Math.abs(dx) <= INC_BND && Math.abs(dy) <= INC_BND) {
|
||||||
}
|
dx = 2 * dx + ddx;
|
||||||
|
dy = 2 * dy + ddy;
|
||||||
private void addPathSegment(float[] pts, final int type, final int or) {
|
ddx = 4 * (ddx + dddx);
|
||||||
int idx;
|
ddy = 4 * (ddy + dddy);
|
||||||
float[] addTo;
|
dddx = 8 * dddx;
|
||||||
switch (type) {
|
dddy = 8 * dddy;
|
||||||
case 4:
|
count >>= 1;
|
||||||
idx = numEdges * SIZEOF_EDGE;
|
}
|
||||||
addTo = edges = Helpers.widenArray(edges, numEdges*SIZEOF_EDGE, SIZEOF_EDGE);
|
count--;
|
||||||
numEdges++;
|
if (count > 0) {
|
||||||
break;
|
x1 += dx;
|
||||||
case 6:
|
dx += ddx;
|
||||||
idx = numQuads * SIZEOF_QUAD;
|
ddx += dddx;
|
||||||
addTo = quads = Helpers.widenArray(quads, numQuads*SIZEOF_QUAD, SIZEOF_QUAD);
|
y1 += dy;
|
||||||
numQuads++;
|
dy += ddy;
|
||||||
break;
|
ddy += dddy;
|
||||||
case 8:
|
} else {
|
||||||
idx = numCurves * SIZEOF_CURVE;
|
x1 = x3;
|
||||||
addTo = curves = Helpers.widenArray(curves, numCurves*SIZEOF_CURVE, SIZEOF_CURVE);
|
y1 = y3;
|
||||||
numCurves++;
|
}
|
||||||
break;
|
addLine(x0, y0, x1, y1);
|
||||||
default:
|
x0 = x1;
|
||||||
throw new InternalError();
|
y0 = y1;
|
||||||
}
|
|
||||||
// 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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// precondition: the curve in pts must be monotonic and increasing in y.
|
// Preconditions: y2 > y1 and the curve must cross some scanline
|
||||||
private void somethingTo(float[] pts, final int type, final int or) {
|
// i.e.: y1 <= y < y2 for some y such that boundsMinY <= y < boundsMaxY
|
||||||
// NOTE: it's very important that we check for or >= 0 below (as
|
private void addLine(float x1, float y1, float x2, float y2) {
|
||||||
// opposed to or == 1, or or > 0, or anything else). That's
|
float or = 1; // orientation of the line. 1 if y increases, 0 otherwise.
|
||||||
// because if we check for or==1, when the curve being added
|
if (y2 < y1) {
|
||||||
// is a horizontal line, or will be 0 so or==1 will be false and
|
or = y2; // no need to declare a temp variable. We have or.
|
||||||
// x0 and y0 will be updated to pts[0] and pts[1] instead of pts[type-2]
|
y2 = y1;
|
||||||
// and pts[type-1], which is the correct thing to do.
|
y1 = or;
|
||||||
this.x0 = or >= 0 ? pts[type - 2] : pts[0];
|
or = x2;
|
||||||
this.y0 = or >= 0 ? pts[type - 1] : pts[1];
|
x2 = x1;
|
||||||
|
x1 = or;
|
||||||
float minY = pts[1], maxY = pts[type - 1];
|
or = 0;
|
||||||
if (Math.ceil(minY) >= Math.ceil(maxY) ||
|
}
|
||||||
Math.ceil(minY) >= boundsMaxY || maxY < boundsMinY)
|
final int firstCrossing = Math.max((int) Math.ceil(y1), boundsMinY);
|
||||||
{
|
final int lastCrossing = Math.min((int)Math.ceil(y2), boundsMaxY);
|
||||||
|
if (firstCrossing >= lastCrossing) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (minY < edgeMinY) { edgeMinY = minY; }
|
if (y1 < edgeMinY) { edgeMinY = y1; }
|
||||||
if (maxY > edgeMaxY) { edgeMaxY = maxY; }
|
if (y2 > edgeMaxY) { edgeMaxY = y2; }
|
||||||
|
|
||||||
int minXidx = (pts[0] < pts[type-2] ? 0 : type - 2);
|
final float slope = (x2 - x1) / (y2 - y1);
|
||||||
float minX = pts[minXidx];
|
|
||||||
float maxX = pts[type - 2 - minXidx];
|
if (slope > 0) { // <==> x1 < x2
|
||||||
if (minX < edgeMinX) { edgeMinX = minX; }
|
if (x1 < edgeMinX) { edgeMinX = x1; }
|
||||||
if (maxX > edgeMaxX) { edgeMaxX = maxX; }
|
if (x2 > edgeMaxX) { edgeMaxX = x2; }
|
||||||
addPathSegment(pts, type, or);
|
} else {
|
||||||
|
if (x2 < edgeMinX) { edgeMinX = x2; }
|
||||||
|
if (x1 > edgeMaxX) { edgeMaxX = x1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
// END EDGE LIST
|
||||||
@ -619,6 +365,10 @@ public class Renderer implements PathConsumer2D {
|
|||||||
this.boundsMinY = pix_boundsY * SUBPIXEL_POSITIONS_Y;
|
this.boundsMinY = pix_boundsY * SUBPIXEL_POSITIONS_Y;
|
||||||
this.boundsMaxX = (pix_boundsX + pix_boundsWidth) * SUBPIXEL_POSITIONS_X;
|
this.boundsMaxX = (pix_boundsX + pix_boundsWidth) * SUBPIXEL_POSITIONS_X;
|
||||||
this.boundsMaxY = (pix_boundsY + pix_boundsHeight) * SUBPIXEL_POSITIONS_Y;
|
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) {
|
private float tosubpixx(float pix_x) {
|
||||||
@ -636,74 +386,34 @@ public class Renderer implements PathConsumer2D {
|
|||||||
this.x0 = tosubpixx(pix_x0);
|
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) {
|
public void lineTo(float pix_x1, float pix_y1) {
|
||||||
pts[0][0] = x0; pts[0][1] = y0;
|
float x1 = tosubpixx(pix_x1);
|
||||||
pts[0][2] = tosubpixx(pix_x1); pts[0][3] = tosubpixy(pix_y1);
|
float y1 = tosubpixy(pix_y1);
|
||||||
int or = makeMonotonicCurveUpright(pts[0], 0, 4);
|
addLine(x0, y0, x1, y1);
|
||||||
somethingTo(pts[0], 4, or);
|
x0 = x1;
|
||||||
|
y0 = y1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Curve c = new Curve();
|
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<float[]> 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,
|
@Override public void curveTo(float x1, float y1,
|
||||||
float x2, float y2,
|
float x2, float y2,
|
||||||
float x3, float y3)
|
float x3, float y3)
|
||||||
{
|
{
|
||||||
pts[0][0] = x0; pts[0][1] = y0;
|
final float xe = tosubpixx(x3);
|
||||||
pts[0][2] = tosubpixx(x1); pts[0][3] = tosubpixy(y1);
|
final float ye = tosubpixy(y3);
|
||||||
pts[0][4] = tosubpixx(x2); pts[0][5] = tosubpixy(y2);
|
c.set(x0, y0, tosubpixx(x1), tosubpixy(y1), tosubpixx(x2), tosubpixy(y2), xe, ye);
|
||||||
pts[0][6] = tosubpixx(x3); pts[0][7] = tosubpixy(y3);
|
curveBreakIntoLinesAndAdd(x0, y0, c, xe, ye);
|
||||||
curveOrQuadTo(8);
|
x0 = xe;
|
||||||
|
y0 = ye;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void quadTo(float x1, float y1, float x2, float y2) {
|
@Override public void quadTo(float x1, float y1, float x2, float y2) {
|
||||||
pts[0][0] = x0; pts[0][1] = y0;
|
final float xe = tosubpixx(x2);
|
||||||
pts[0][2] = tosubpixx(x1); pts[0][3] = tosubpixy(y1);
|
final float ye = tosubpixy(y2);
|
||||||
pts[0][4] = tosubpixx(x2); pts[0][5] = tosubpixy(y2);
|
c.set(x0, y0, tosubpixx(x1), tosubpixy(y1), xe, ye);
|
||||||
curveOrQuadTo(6);
|
quadBreakIntoLinesAndAdd(x0, y0, c, xe, ye);
|
||||||
|
x0 = xe;
|
||||||
|
y0 = ye;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void closePath() {
|
public void closePath() {
|
||||||
@ -728,9 +438,9 @@ public class Renderer implements PathConsumer2D {
|
|||||||
// 0x1 if EVEN_ODD, all bits if NON_ZERO
|
// 0x1 if EVEN_ODD, all bits if NON_ZERO
|
||||||
int mask = (windingRule == WIND_EVEN_ODD) ? 0x1 : ~0x0;
|
int mask = (windingRule == WIND_EVEN_ODD) ? 0x1 : ~0x0;
|
||||||
|
|
||||||
// add 1 to better deal with the last pixel in a pixel row.
|
// add 2 to better deal with the last pixel in a pixel row.
|
||||||
int width = pix_bboxx1 - pix_bboxx0 + 1;
|
int width = pix_bboxx1 - pix_bboxx0;
|
||||||
int[] alpha = new int[width+1];
|
int[] alpha = new int[width+2];
|
||||||
|
|
||||||
int bboxx0 = pix_bboxx0 << SUBPIXEL_LG_POSITIONS_X;
|
int bboxx0 = pix_bboxx0 << SUBPIXEL_LG_POSITIONS_X;
|
||||||
int bboxx1 = pix_bboxx1 << SUBPIXEL_LG_POSITIONS_X;
|
int bboxx1 = pix_bboxx1 << SUBPIXEL_LG_POSITIONS_X;
|
||||||
@ -766,7 +476,8 @@ public class Renderer implements PathConsumer2D {
|
|||||||
for (int i = 0; i < numCrossings; i++) {
|
for (int i = 0; i < numCrossings; i++) {
|
||||||
int curxo = crossings[i];
|
int curxo = crossings[i];
|
||||||
int curx = curxo >> 1;
|
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) {
|
if ((sum & mask) != 0) {
|
||||||
int x0 = Math.max(prev, bboxx0);
|
int x0 = Math.max(prev, bboxx0);
|
||||||
int x1 = Math.min(curx, bboxx1);
|
int x1 = Math.min(curx, bboxx1);
|
||||||
@ -811,26 +522,26 @@ public class Renderer implements PathConsumer2D {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void endRendering() {
|
public void endRendering() {
|
||||||
final int bminx = boundsMinX >> SUBPIXEL_LG_POSITIONS_X;
|
int spminX = Math.max((int)Math.ceil(edgeMinX), boundsMinX);
|
||||||
final int bmaxx = boundsMaxX >> SUBPIXEL_LG_POSITIONS_X;
|
int spmaxX = Math.min((int)Math.ceil(edgeMaxX), boundsMaxX);
|
||||||
final int bminy = boundsMinY >> SUBPIXEL_LG_POSITIONS_Y;
|
int spminY = Math.max((int)Math.ceil(edgeMinY), boundsMinY);
|
||||||
final int bmaxy = boundsMaxY >> SUBPIXEL_LG_POSITIONS_Y;
|
int spmaxY = Math.min((int)Math.ceil(edgeMaxY), boundsMaxY);
|
||||||
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;
|
|
||||||
|
|
||||||
final int minX = Math.max(bminx, eminx);
|
int pminX = spminX >> SUBPIXEL_LG_POSITIONS_X;
|
||||||
final int maxX = Math.min(bmaxx, emaxx);
|
int pmaxX = (spmaxX + SUBPIXEL_MASK_X) >> SUBPIXEL_LG_POSITIONS_X;
|
||||||
final int minY = Math.max(bminy, eminy);
|
int pminY = spminY >> SUBPIXEL_LG_POSITIONS_Y;
|
||||||
final int maxY = Math.min(bmaxy, emaxy);
|
int pmaxY = (spmaxY + SUBPIXEL_MASK_Y) >> SUBPIXEL_LG_POSITIONS_Y;
|
||||||
if (minX > maxX || minY > maxY) {
|
|
||||||
this.cache = new PiscesCache(bminx, bminy, bmaxx, bmaxy);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.cache = new PiscesCache(minX, minY, maxX, maxY);
|
this.cache = new PiscesCache(pminX, pminY, pmaxX, pmaxY);
|
||||||
_endRendering(minX, minY, maxX, maxY);
|
_endRendering(pminX, pminY, pmaxX, pmaxY);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PiscesCache getCache() {
|
public PiscesCache getCache() {
|
||||||
|
@ -33,7 +33,7 @@ import sun.awt.geom.PathConsumer2D;
|
|||||||
// TODO: some of the arithmetic here is too verbose and prone to hard to
|
// 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
|
// 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
|
// 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 MOVE_TO = 0;
|
||||||
private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad
|
private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad
|
||||||
@ -130,7 +130,7 @@ public class Stroker implements PathConsumer2D {
|
|||||||
private static void computeOffset(final float lx, final float ly,
|
private static void computeOffset(final float lx, final float ly,
|
||||||
final float w, final float[] m)
|
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) {
|
if (len == 0) {
|
||||||
m[0] = m[1] = 0;
|
m[0] = m[1] = 0;
|
||||||
} else {
|
} else {
|
||||||
@ -758,7 +758,7 @@ public class Stroker implements PathConsumer2D {
|
|||||||
// This is where the curve to be processed is put. We give it
|
// 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
|
// enough room to store 2 curves: one for the current subdivision, the
|
||||||
// other for the rest of the curve.
|
// 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[] lp = new float[8];
|
||||||
private float[] rp = new float[8];
|
private float[] rp = new float[8];
|
||||||
private static final int MAX_N_CURVES = 11;
|
private static final int MAX_N_CURVES = 11;
|
||||||
@ -766,55 +766,55 @@ public class Stroker implements PathConsumer2D {
|
|||||||
|
|
||||||
private void somethingTo(final int type) {
|
private void somethingTo(final int type) {
|
||||||
// need these so we can update the state at the end of this method
|
// 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];
|
final float xf = middle[type-2], yf = middle[type-1];
|
||||||
float dxs = middle[0][2] - middle[0][0];
|
float dxs = middle[2] - middle[0];
|
||||||
float dys = middle[0][3] - middle[0][1];
|
float dys = middle[3] - middle[1];
|
||||||
float dxf = middle[0][type - 2] - middle[0][type - 4];
|
float dxf = middle[type - 2] - middle[type - 4];
|
||||||
float dyf = middle[0][type - 1] - middle[0][type - 3];
|
float dyf = middle[type - 1] - middle[type - 3];
|
||||||
switch(type) {
|
switch(type) {
|
||||||
case 6:
|
case 6:
|
||||||
if ((dxs == 0f && dys == 0f) ||
|
if ((dxs == 0f && dys == 0f) ||
|
||||||
(dxf == 0f && dyf == 0f)) {
|
(dxf == 0f && dyf == 0f)) {
|
||||||
dxs = dxf = middle[0][4] - middle[0][0];
|
dxs = dxf = middle[4] - middle[0];
|
||||||
dys = dyf = middle[0][5] - middle[0][1];
|
dys = dyf = middle[5] - middle[1];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 8:
|
case 8:
|
||||||
boolean p1eqp2 = (dxs == 0f && dys == 0f);
|
boolean p1eqp2 = (dxs == 0f && dys == 0f);
|
||||||
boolean p3eqp4 = (dxf == 0f && dyf == 0f);
|
boolean p3eqp4 = (dxf == 0f && dyf == 0f);
|
||||||
if (p1eqp2) {
|
if (p1eqp2) {
|
||||||
dxs = middle[0][4] - middle[0][0];
|
dxs = middle[4] - middle[0];
|
||||||
dys = middle[0][5] - middle[0][1];
|
dys = middle[5] - middle[1];
|
||||||
if (dxs == 0f && dys == 0f) {
|
if (dxs == 0f && dys == 0f) {
|
||||||
dxs = middle[0][6] - middle[0][0];
|
dxs = middle[6] - middle[0];
|
||||||
dys = middle[0][7] - middle[0][1];
|
dys = middle[7] - middle[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (p3eqp4) {
|
if (p3eqp4) {
|
||||||
dxf = middle[0][6] - middle[0][2];
|
dxf = middle[6] - middle[2];
|
||||||
dyf = middle[0][7] - middle[0][3];
|
dyf = middle[7] - middle[3];
|
||||||
if (dxf == 0f && dyf == 0f) {
|
if (dxf == 0f && dyf == 0f) {
|
||||||
dxf = middle[0][6] - middle[0][0];
|
dxf = middle[6] - middle[0];
|
||||||
dyf = middle[0][7] - middle[0][1];
|
dyf = middle[7] - middle[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (dxs == 0f && dys == 0f) {
|
if (dxs == 0f && dys == 0f) {
|
||||||
// this happens iff the "curve" is just a point
|
// this happens iff the "curve" is just a point
|
||||||
lineTo(middle[0][0], middle[0][1]);
|
lineTo(middle[0], middle[1]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// if these vectors are too small, normalize them, to avoid future
|
// if these vectors are too small, normalize them, to avoid future
|
||||||
// precision problems.
|
// precision problems.
|
||||||
if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) {
|
if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) {
|
||||||
double len = Math.hypot(dxs, dys);
|
float len = (float)Math.sqrt(dxs*dxs + dys*dys);
|
||||||
dxs = (float)(dxs / len);
|
dxs /= len;
|
||||||
dys = (float)(dys / len);
|
dys /= len;
|
||||||
}
|
}
|
||||||
if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) {
|
if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) {
|
||||||
double len = Math.hypot(dxf, dyf);
|
float len = (float)Math.sqrt(dxf*dxf + dyf*dyf);
|
||||||
dxf = (float)(dxf / len);
|
dxf /= len;
|
||||||
dyf = (float)(dyf / len);
|
dyf /= len;
|
||||||
}
|
}
|
||||||
|
|
||||||
computeOffset(dxs, dys, lineWidth2, offset[0]);
|
computeOffset(dxs, dys, lineWidth2, offset[0]);
|
||||||
@ -822,20 +822,20 @@ public class Stroker implements PathConsumer2D {
|
|||||||
final float my = offset[0][1];
|
final float my = offset[0][1];
|
||||||
drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, mx, my);
|
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;
|
int kind = 0;
|
||||||
Iterator<float[]> it = Curve.breakPtsAtTs(middle, type, subdivTs, nSplits);
|
Iterator<Integer> it = Curve.breakPtsAtTs(middle, type, subdivTs, nSplits);
|
||||||
while(it.hasNext()) {
|
while(it.hasNext()) {
|
||||||
float[] curCurve = it.next();
|
int curCurveOff = it.next();
|
||||||
|
|
||||||
kind = 0;
|
kind = 0;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 8:
|
case 8:
|
||||||
kind = computeOffsetCubic(curCurve, 0, lp, rp);
|
kind = computeOffsetCubic(middle, curCurveOff, lp, rp);
|
||||||
break;
|
break;
|
||||||
case 6:
|
case 6:
|
||||||
kind = computeOffsetQuad(curCurve, 0, lp, rp);
|
kind = computeOffsetQuad(middle, curCurveOff, lp, rp);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (kind != 0) {
|
if (kind != 0) {
|
||||||
@ -871,8 +871,7 @@ public class Stroker implements PathConsumer2D {
|
|||||||
// to get good offset curves a distance of w away from the middle curve.
|
// 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.
|
// Stores the points in ts, and returns how many of them there were.
|
||||||
private static Curve c = new Curve();
|
private static Curve c = new Curve();
|
||||||
private static int findSubdivPoints(float[] pts, float[] ts,
|
private static int findSubdivPoints(float[] pts, float[] ts, final int type, final float w)
|
||||||
final int type, final float w)
|
|
||||||
{
|
{
|
||||||
final float x12 = pts[2] - pts[0];
|
final float x12 = pts[2] - pts[0];
|
||||||
final float y12 = pts[3] - pts[1];
|
final float y12 = pts[3] - pts[1];
|
||||||
@ -919,6 +918,7 @@ public class Stroker implements PathConsumer2D {
|
|||||||
// now we must subdivide at points where one of the offset curves will have
|
// 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.
|
// a cusp. This happens at ts where the radius of curvature is equal to w.
|
||||||
ret += c.rootsOfROCMinusW(ts, ret, w, 0.0001f);
|
ret += c.rootsOfROCMinusW(ts, ret, w, 0.0001f);
|
||||||
|
|
||||||
ret = Helpers.filterOutNotInAB(ts, 0, ret, 0.0001f, 0.9999f);
|
ret = Helpers.filterOutNotInAB(ts, 0, ret, 0.0001f, 0.9999f);
|
||||||
Helpers.isort(ts, 0, ret);
|
Helpers.isort(ts, 0, ret);
|
||||||
return ret;
|
return ret;
|
||||||
@ -928,10 +928,10 @@ public class Stroker implements PathConsumer2D {
|
|||||||
float x2, float y2,
|
float x2, float y2,
|
||||||
float x3, float y3)
|
float x3, float y3)
|
||||||
{
|
{
|
||||||
middle[0][0] = cx0; middle[0][1] = cy0;
|
middle[0] = cx0; middle[1] = cy0;
|
||||||
middle[0][2] = x1; middle[0][3] = y1;
|
middle[2] = x1; middle[3] = y1;
|
||||||
middle[0][4] = x2; middle[0][5] = y2;
|
middle[4] = x2; middle[5] = y2;
|
||||||
middle[0][6] = x3; middle[0][7] = y3;
|
middle[6] = x3; middle[7] = y3;
|
||||||
somethingTo(8);
|
somethingTo(8);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -940,9 +940,9 @@ public class Stroker implements PathConsumer2D {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override public void quadTo(float x1, float y1, float x2, float y2) {
|
@Override public void quadTo(float x1, float y1, float x2, float y2) {
|
||||||
middle[0][0] = cx0; middle[0][1] = cy0;
|
middle[0] = cx0; middle[1] = cy0;
|
||||||
middle[0][2] = x1; middle[0][3] = y1;
|
middle[2] = x1; middle[3] = y1;
|
||||||
middle[0][4] = x2; middle[0][5] = y2;
|
middle[4] = x2; middle[5] = y2;
|
||||||
somethingTo(6);
|
somethingTo(6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ package sun.java2d.pisces;
|
|||||||
import sun.awt.geom.PathConsumer2D;
|
import sun.awt.geom.PathConsumer2D;
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
|
|
||||||
public class TransformingPathConsumer2D {
|
final class TransformingPathConsumer2D {
|
||||||
public static PathConsumer2D
|
public static PathConsumer2D
|
||||||
transformConsumer(PathConsumer2D out,
|
transformConsumer(PathConsumer2D out,
|
||||||
AffineTransform at)
|
AffineTransform at)
|
||||||
@ -50,17 +50,72 @@ public class TransformingPathConsumer2D {
|
|||||||
return new TranslateFilter(out, Mxt, Myt);
|
return new TranslateFilter(out, Mxt, Myt);
|
||||||
}
|
}
|
||||||
} else {
|
} 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 {
|
} else {
|
||||||
return new TransformFilter(out, Mxx, Mxy, Mxt, Myx, Myy, Myt);
|
return new TransformFilter(out, Mxx, Mxy, Mxt, Myx, Myy, Myt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class TranslateFilter implements PathConsumer2D {
|
public static PathConsumer2D
|
||||||
PathConsumer2D out;
|
deltaTransformConsumer(PathConsumer2D out,
|
||||||
float tx;
|
AffineTransform at)
|
||||||
float ty;
|
{
|
||||||
|
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,
|
TranslateFilter(PathConsumer2D out,
|
||||||
float tx, float ty)
|
float tx, float ty)
|
||||||
@ -107,12 +162,12 @@ public class TransformingPathConsumer2D {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class ScaleFilter implements PathConsumer2D {
|
static final class ScaleFilter implements PathConsumer2D {
|
||||||
PathConsumer2D out;
|
private final PathConsumer2D out;
|
||||||
float sx;
|
private final float sx;
|
||||||
float sy;
|
private final float sy;
|
||||||
float tx;
|
private final float tx;
|
||||||
float ty;
|
private final float ty;
|
||||||
|
|
||||||
ScaleFilter(PathConsumer2D out,
|
ScaleFilter(PathConsumer2D out,
|
||||||
float sx, float sy, float tx, float ty)
|
float sx, float sy, float tx, float ty)
|
||||||
@ -161,14 +216,14 @@ public class TransformingPathConsumer2D {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class TransformFilter implements PathConsumer2D {
|
static final class TransformFilter implements PathConsumer2D {
|
||||||
PathConsumer2D out;
|
private final PathConsumer2D out;
|
||||||
float Mxx;
|
private final float Mxx;
|
||||||
float Mxy;
|
private final float Mxy;
|
||||||
float Mxt;
|
private final float Mxt;
|
||||||
float Myx;
|
private final float Myx;
|
||||||
float Myy;
|
private final float Myy;
|
||||||
float Myt;
|
private final float Myt;
|
||||||
|
|
||||||
TransformFilter(PathConsumer2D out,
|
TransformFilter(PathConsumer2D out,
|
||||||
float Mxx, float Mxy, float Mxt,
|
float Mxx, float Mxy, float Mxt,
|
||||||
@ -226,4 +281,113 @@ public class TransformingPathConsumer2D {
|
|||||||
return 0;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user