提交 4883457f 编写于 作者: D dlila

7016856: dashing performance was reduced during latest changes to the OpenJDK rasterizer

Summary: Optimized dashing, rasterizing, and the flow of transformed coordinates
Reviewed-by: flar
上级 eceb31dc
......@@ -27,7 +27,7 @@ package sun.java2d.pisces;
import java.util.Iterator;
class Curve {
final class Curve {
float ax, ay, bx, by, cx, cy, dx, dy;
float dax, day, dbx, dby;
......@@ -101,14 +101,6 @@ class Curve {
return t * (t * day + dby) + cy;
}
private float ddxat(float t) {
return 2 * dax * t + dbx;
}
private float ddyat(float t) {
return 2 * day * t + dby;
}
int dxRoots(float[] roots, int off) {
return Helpers.quadraticRoots(dax, dbx, cx, roots, off);
}
......@@ -131,17 +123,17 @@ class Curve {
// finds points where the first and second derivative are
// perpendicular. This happens when g(t) = f'(t)*f''(t) == 0 (where
// * is a dot product). Unfortunately, we have to solve a cubic.
private int perpendiculardfddf(float[] pts, int off, final float err) {
private int perpendiculardfddf(float[] pts, int off) {
assert pts.length >= off + 4;
// these are the coefficients of g(t):
// these are the coefficients of some multiple of g(t) (not g(t),
// because the roots of a polynomial are not changed after multiplication
// by a constant, and this way we save a few multiplications).
final float a = 2*(dax*dax + day*day);
final float b = 3*(dax*dbx + day*dby);
final float c = 2*(dax*cx + day*cy) + dbx*dbx + dby*dby;
final float d = dbx*cx + dby*cy;
// TODO: We might want to divide the polynomial by a to make the
// coefficients smaller. This won't change the roots.
return Helpers.cubicRootsInAB(a, b, c, d, pts, off, err, 0f, 1f);
return Helpers.cubicRootsInAB(a, b, c, d, pts, off, 0f, 1f);
}
// Tries to find the roots of the function ROC(t)-w in [0, 1). It uses
......@@ -161,7 +153,7 @@ class Curve {
// no OOB exception, because by now off<=6, and roots.length >= 10
assert off <= 6 && roots.length >= 10;
int ret = off;
int numPerpdfddf = perpendiculardfddf(roots, off, err);
int numPerpdfddf = perpendiculardfddf(roots, off);
float t0 = 0, ft0 = ROCsq(t0) - w*w;
roots[off + numPerpdfddf] = 1f; // always check interval end points
numPerpdfddf++;
......@@ -189,8 +181,9 @@ class Curve {
// A slight modification of the false position algorithm on wikipedia.
// This only works for the ROCsq-x functions. It might be nice to have
// the function as an argument, but that would be awkward in java6.
// It is something to consider for java7, depending on how closures
// and function objects turn out. Same goes for the newton's method
// TODO: It is something to consider for java8 (or whenever lambda
// expressions make it into the language), depending on how closures
// and turn out. Same goes for the newton's method
// algorithm in Helpers.java
private float falsePositionROCsqMinusX(float x0, float x1,
final float x, final float err)
......@@ -203,7 +196,7 @@ class Curve {
for (int i = 0; i < iterLimit && Math.abs(t - s) > err * Math.abs(t + s); i++) {
r = (fs * t - ft * s) / (fs - ft);
fr = ROCsq(r) - x;
if (fr * ft > 0) {// have the same sign
if (sameSign(fr, ft)) {
ft = fr; t = r;
if (side < 0) {
fs /= (1 << (-side));
......@@ -226,55 +219,65 @@ class Curve {
return r;
}
private static boolean sameSign(double x, double y) {
// another way is to test if x*y > 0. This is bad for small x, y.
return (x < 0 && y < 0) || (x > 0 && y > 0);
}
// returns the radius of curvature squared at t of this curve
// see http://en.wikipedia.org/wiki/Radius_of_curvature_(applications)
private float ROCsq(final float t) {
final float dx = dxat(t);
final float dy = dyat(t);
final float ddx = ddxat(t);
final float ddy = ddyat(t);
// dx=xat(t) and dy=yat(t). These calls have been inlined for efficiency
final float dx = t * (t * dax + dbx) + cx;
final float dy = t * (t * day + dby) + cy;
final float ddx = 2 * dax * t + dbx;
final float ddy = 2 * day * t + dby;
final float dx2dy2 = dx*dx + dy*dy;
final float ddx2ddy2 = ddx*ddx + ddy*ddy;
final float ddxdxddydy = ddx*dx + ddy*dy;
float ret = ((dx2dy2*dx2dy2) / (dx2dy2 * ddx2ddy2 - ddxdxddydy*ddxdxddydy))*dx2dy2;
return ret;
return dx2dy2*((dx2dy2*dx2dy2) / (dx2dy2 * ddx2ddy2 - ddxdxddydy*ddxdxddydy));
}
// curve to be broken should be in pts[0]
// this will change the contents of both pts and Ts
// curve to be broken should be in pts
// this will change the contents of pts but not Ts
// TODO: There's no reason for Ts to be an array. All we need is a sequence
// of t values at which to subdivide. An array statisfies this condition,
// but is unnecessarily restrictive. Ts should be an Iterator<Float> instead.
// Doing this will also make dashing easier, since we could easily make
// LengthIterator an Iterator<Float> and feed it to this function to simplify
// 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)
{
assert pts.length >= 2 && pts[0].length >= 8 && numTs <= Ts.length;
return new Iterator<float[]>() {
int nextIdx = 0;
assert pts.length >= 2*type && numTs <= Ts.length;
return new Iterator<Integer>() {
// these prevent object creation and destruction during autoboxing.
// Because of this, the compiler should be able to completely
// eliminate the boxing costs.
final Integer i0 = 0;
final Integer itype = type;
int nextCurveIdx = 0;
Integer curCurveOff = i0;
float prevT = 0;
@Override public boolean hasNext() {
return nextCurveIdx < numTs + 1;
}
@Override public float[] next() {
float[] ret;
@Override public Integer next() {
Integer ret;
if (nextCurveIdx < numTs) {
float curT = Ts[nextCurveIdx];
float splitT = (curT - prevT) / (1 - prevT);
Helpers.subdivideAt(splitT,
pts[nextIdx], 0,
pts[nextIdx], 0,
pts[1-nextIdx], 0, type);
updateTs(Ts, Ts[nextCurveIdx], nextCurveIdx + 1, numTs - nextCurveIdx - 1);
ret = pts[nextIdx];
nextIdx = 1 - nextIdx;
pts, curCurveOff,
pts, 0,
pts, type, type);
prevT = curT;
ret = i0;
curCurveOff = itype;
} else {
ret = pts[nextIdx];
ret = curCurveOff;
}
nextCurveIdx++;
return ret;
......@@ -283,12 +286,5 @@ class Curve {
@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.
*
*/
public class Dasher implements sun.awt.geom.PathConsumer2D {
final class Dasher implements sun.awt.geom.PathConsumer2D {
private final PathConsumer2D out;
private final float[] dash;
......@@ -169,7 +169,7 @@ public class Dasher implements sun.awt.geom.PathConsumer2D {
float dx = x1 - x0;
float dy = y1 - y0;
float len = (float) Math.hypot(dx, dy);
float len = (float) Math.sqrt(dx*dx + dy*dy);
if (len == 0) {
return;
......@@ -226,7 +226,7 @@ public class Dasher implements sun.awt.geom.PathConsumer2D {
return;
}
if (li == null) {
li = new LengthIterator(4, 0.0001f);
li = new LengthIterator(4, 0.01f);
}
li.initializeIterationOnCurve(curCurvepts, type);
......@@ -237,9 +237,9 @@ public class Dasher implements sun.awt.geom.PathConsumer2D {
while ((t = li.next(leftInThisDashSegment)) < 1) {
if (t != 0) {
Helpers.subdivideAt((t - lastSplitT) / (1 - lastSplitT),
curCurvepts, curCurveoff,
curCurvepts, 0,
curCurvepts, type, type);
curCurvepts, curCurveoff,
curCurvepts, 0,
curCurvepts, type, type);
lastSplitT = t;
goTo(curCurvepts, 2, type);
curCurveoff = type;
......@@ -307,6 +307,11 @@ public class Dasher implements sun.awt.geom.PathConsumer2D {
private int recLevel;
private boolean done;
// the lengths of the lines of the control polygon. Only its first
// curveType/2 - 1 elements are valid. This is an optimization. See
// next(float) for more detail.
private float[] curLeafCtrlPolyLengths = new float[3];
public LengthIterator(int reclimit, float err) {
this.limit = reclimit;
this.minTincrement = 1f / (1 << limit);
......@@ -344,11 +349,52 @@ public class Dasher implements sun.awt.geom.PathConsumer2D {
this.lastSegLen = 0;
}
// 0 == false, 1 == true, -1 == invalid cached value.
private int cachedHaveLowAcceleration = -1;
private boolean haveLowAcceleration(float err) {
if (cachedHaveLowAcceleration == -1) {
final float len1 = curLeafCtrlPolyLengths[0];
final float len2 = curLeafCtrlPolyLengths[1];
// the test below is equivalent to !within(len1/len2, 1, err).
// It is using a multiplication instead of a division, so it
// should be a bit faster.
if (!Helpers.within(len1, len2, err*len2)) {
cachedHaveLowAcceleration = 0;
return false;
}
if (curveType == 8) {
final float len3 = curLeafCtrlPolyLengths[2];
// if len1 is close to 2 and 2 is close to 3, that probably
// means 1 is close to 3 so the second part of this test might
// not be needed, but it doesn't hurt to include it.
if (!(Helpers.within(len2, len3, err*len3) &&
Helpers.within(len1, len3, err*len3))) {
cachedHaveLowAcceleration = 0;
return false;
}
}
cachedHaveLowAcceleration = 1;
return true;
}
return (cachedHaveLowAcceleration == 1);
}
// we want to avoid allocations/gc so we keep this array so we
// can put roots in it,
private float[] nextRoots = new float[4];
// caches the coefficients of the current leaf in its flattened
// form (see inside next() for what that means). The cache is
// invalid when it's third element is negative, since in any
// valid flattened curve, this would be >= 0.
private float[] flatLeafCoefCache = new float[] {0, 0, -1, 0};
// returns the t value where the remaining curve should be split in
// order for the left subdivided curve to have length len. If len
// is >= than the length of the uniterated curve, it returns 1.
public float next(float len) {
float targetLength = lenAtLastSplit + len;
public float next(final float len) {
final float targetLength = lenAtLastSplit + len;
while(lenAtNextT < targetLength) {
if (done) {
lastSegLen = lenAtNextT - lenAtLastSplit;
......@@ -357,8 +403,46 @@ public class Dasher implements sun.awt.geom.PathConsumer2D {
goToNextLeaf();
}
lenAtLastSplit = targetLength;
float t = binSearchForLen(lenAtLastSplit - lenAtLastT,
recCurveStack[recLevel], curveType, lenAtNextT - lenAtLastT, ERR);
final float leaflen = lenAtNextT - lenAtLastT;
float t = (targetLength - lenAtLastT) / leaflen;
// cubicRootsInAB is a fairly expensive call, so we just don't do it
// if the acceleration in this section of the curve is small enough.
if (!haveLowAcceleration(0.05f)) {
// We flatten the current leaf along the x axis, so that we're
// left with a, b, c which define a 1D Bezier curve. We then
// solve this to get the parameter of the original leaf that
// gives us the desired length.
if (flatLeafCoefCache[2] < 0) {
float x = 0+curLeafCtrlPolyLengths[0],
y = x+curLeafCtrlPolyLengths[1];
if (curveType == 8) {
float z = y + curLeafCtrlPolyLengths[2];
flatLeafCoefCache[0] = 3*(x - y) + z;
flatLeafCoefCache[1] = 3*(y - 2*x);
flatLeafCoefCache[2] = 3*x;
flatLeafCoefCache[3] = -z;
} else if (curveType == 6) {
flatLeafCoefCache[0] = 0f;
flatLeafCoefCache[1] = y - 2*x;
flatLeafCoefCache[2] = 2*x;
flatLeafCoefCache[3] = -y;
}
}
float a = flatLeafCoefCache[0];
float b = flatLeafCoefCache[1];
float c = flatLeafCoefCache[2];
float d = t*flatLeafCoefCache[3];
// we use cubicRootsInAB here, because we want only roots in 0, 1,
// and our quadratic root finder doesn't filter, so it's just a
// matter of convenience.
int n = Helpers.cubicRootsInAB(a, b, c, d, nextRoots, 0, 0, 1);
if (n == 1 && !Float.isNaN(nextRoots[0])) {
t = nextRoots[0];
}
}
// t is relative to the current leaf, so we must make it a valid parameter
// of the original curve.
t = t * (nextT - lastT) + lastT;
......@@ -379,36 +463,6 @@ public class Dasher implements sun.awt.geom.PathConsumer2D {
return lastSegLen;
}
// Returns t such that if leaf is subdivided at t the left
// curve will have length len. leafLen must be the length of leaf.
private static Curve bsc = new Curve();
private static float binSearchForLen(float len, float[] leaf, int type,
float leafLen, float err)
{
assert len <= leafLen;
bsc.set(leaf, type);
float errBound = err*len;
float left = 0, right = 1;
while (left < right) {
float m = (left + right) / 2;
if (m == left || m == right) {
return m;
}
float x = bsc.xat(m);
float y = bsc.yat(m);
float leftLen = Helpers.linelen(leaf[0], leaf[1], x, y);
if (Math.abs(leftLen - len) < errBound) {
return m;
}
if (leftLen < len) {
left = m;
} else {
right = m;
}
}
return left;
}
// go to the next leaf (in an inorder traversal) in the recursion tree
// preconditions: must be on a leaf, and that leaf must not be the root.
private void goToNextLeaf() {
......@@ -437,6 +491,9 @@ public class Dasher implements sun.awt.geom.PathConsumer2D {
lenAtLastT = lenAtNextT;
nextT += (1 << (limit - recLevel)) * minTincrement;
lenAtNextT += len;
// invalidate caches
flatLeafCoefCache[2] = -1;
cachedHaveLowAcceleration = -1;
} else {
Helpers.subdivide(recCurveStack[recLevel], 0,
recCurveStack[recLevel+1], 0,
......@@ -450,11 +507,24 @@ 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
// the length of the leaf if we are on a leaf.
private float onLeaf() {
float polylen = Helpers.polyLineLength(recCurveStack[recLevel], 0, curveType);
float linelen = Helpers.linelen(recCurveStack[recLevel][0], recCurveStack[recLevel][1],
recCurveStack[recLevel][curveType - 2], recCurveStack[recLevel][curveType - 1]);
return (polylen - linelen < ERR || recLevel == limit) ?
(polylen + linelen)/2 : -1;
float[] curve = recCurveStack[recLevel];
float polyLen = 0;
float x0 = curve[0], y0 = curve[1];
for (int i = 2; i < curveType; i += 2) {
final float x1 = curve[i], y1 = curve[i+1];
final float len = Helpers.linelen(x0, y0, x1, y1);
polyLen += len;
curLeafCtrlPolyLengths[i/2 - 1] = len;
x0 = x1;
y0 = y1;
}
final float lineLen = Helpers.linelen(curve[0], curve[1], curve[curveType-2], curve[curveType-1]);
if (polyLen - lineLen < ERR || recLevel == limit) {
return (polyLen + lineLen)/2;
}
return -1;
}
}
......
......@@ -26,6 +26,12 @@
package sun.java2d.pisces;
import java.util.Arrays;
import static java.lang.Math.PI;
import static java.lang.Math.cos;
import static java.lang.Math.sqrt;
import static java.lang.Math.cbrt;
import static java.lang.Math.acos;
final class Helpers {
private Helpers() {
......@@ -75,100 +81,74 @@ final class Helpers {
return ret - off;
}
// find the roots of g(t) = a*t^3 + b*t^2 + c*t + d in [A,B)
// We will not use Cardano's method, since it is complicated and
// involves too many square and cubic roots. We will use Newton's method.
// TODO: this should probably return ALL roots. Then the user can do
// his own filtering of roots outside [A,B).
static int cubicRootsInAB(final float a, final float b,
final float c, final float d,
float[] pts, final int off, final float E,
// find the roots of g(t) = d*t^3 + a*t^2 + b*t + c in [A,B)
static int cubicRootsInAB(float d, float a, float b, float c,
float[] pts, final int off,
final float A, final float B)
{
if (a == 0) {
return quadraticRoots(b, c, d, pts, off);
}
// 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;
if (d == 0) {
int num = quadraticRoots(a, b, c, pts, off);
return filterOutNotInAB(pts, off, num, A, B) - off;
}
// From Graphics Gems:
// http://tog.acm.org/resources/GraphicsGems/gems/Roots3And4.c
// (also from awt.geom.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
// only use them to ensure that g(t) is monotonic in each
// interval [pts[i],pts[i+1] (for i in off...off+numCritPts+1).
// This will allow us to determine intervals containing exactly
// one root.
// The end points of the interval are always local extrema.
pts[off] = A;
pts[off + numCritPts + 1] = B;
numCritPts += 2;
float x0 = pts[off], fx0 = evalCubic(a, b, c, d, x0);
for (int i = off; i < off + numCritPts - 1; i++) {
float x1 = pts[i+1], fx1 = evalCubic(a, b, c, d, x1);
if (fx0 == 0f) {
pts[ret++] = x0;
} else if (fx1 * fx0 < 0f) { // have opposite signs
pts[ret++] = CubicNewton(a, b, c, d,
x0 + fx0 * (x1 - x0) / (fx0 - fx1), E);
}
x0 = x1;
fx0 = fx1;
}
return ret - off;
}
// substitute x = y - A/3 to eliminate quadratic term:
// x^3 +Px + Q = 0
//
// Since we actually need P/3 and Q/2 for all of the
// calculations that follow, we will calculate
// p = P/3
// q = Q/2
// instead and use those values for simplicity of the code.
double sq_A = a * a;
double p = 1.0/3 * (-1.0/3 * sq_A + b);
double q = 1.0/2 * (2.0/27 * a * sq_A - 1.0/3 * a * b + c);
// precondition: the polynomial to be evaluated must not be 0 at x0.
static float CubicNewton(final float a, final float b,
final float c, final float d,
float x0, final float err)
{
// considering how this function is used, 10 should be more than enough
final int itlimit = 10;
float fx0 = evalCubic(a, b, c, d, x0);
float x1;
int count = 0;
while(true) {
x1 = x0 - (fx0 / evalCubic(0, 3 * a, 2 * b, c, x0));
if (Math.abs(x1 - x0) < err * Math.abs(x1 + x0) || count == itlimit) {
break;
}
x0 = x1;
fx0 = evalCubic(a, b, c, d, x0);
count++;
}
return x1;
}
/* use Cardano's formula */
// fills the input array with numbers 0, INC, 2*INC, ...
static void fillWithIdxes(final float[] data, final int[] idxes) {
if (idxes.length > 0) {
idxes[0] = 0;
for (int i = 1; i < idxes.length; i++) {
idxes[i] = idxes[i-1] + (int)data[idxes[i-1]];
double cb_p = p * p * p;
double D = q * q + cb_p;
int num;
if (D < 0) {
// see: http://en.wikipedia.org/wiki/Cubic_function#Trigonometric_.28and_hyperbolic.29_method
final double phi = 1.0/3 * acos(-q / sqrt(-cb_p));
final double t = 2 * sqrt(-p);
pts[ off+0 ] = (float)( t * cos(phi));
pts[ off+1 ] = (float)(-t * cos(phi + PI / 3));
pts[ off+2 ] = (float)(-t * cos(phi - PI / 3));
num = 3;
} else {
final double sqrt_D = sqrt(D);
final double u = cbrt(sqrt_D - q);
final double v = - cbrt(sqrt_D + q);
pts[ off ] = (float)(u + v);
num = 1;
if (within(D, 0, 1e-8)) {
pts[off+1] = -(pts[off] / 2);
num = 2;
}
}
}
static void fillWithIdxes(final int[] idxes, final int inc) {
if (idxes.length > 0) {
idxes[0] = 0;
for (int i = 1; i < idxes.length; i++) {
idxes[i] = idxes[i-1] + inc;
}
final float sub = 1.0f/3 * a;
for (int i = 0; i < num; ++i) {
pts[ off+i ] -= sub;
}
return filterOutNotInAB(pts, off, num, A, B) - off;
}
// 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));
}
static int[] widenArray(int[] in, final int cursize, final int numToAdd) {
if (in.length >= cursize + numToAdd) {
return in;
......@@ -208,7 +189,7 @@ final class Helpers {
{
int ret = off;
for (int i = off; i < off + len; i++) {
if (nums[i] > a && nums[i] < b) {
if (nums[i] >= a && nums[i] < b) {
nums[ret++] = nums[i];
}
}
......@@ -225,7 +206,9 @@ final class Helpers {
}
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,
......
......@@ -32,7 +32,7 @@ import java.util.Arrays;
*
* @see PiscesRenderer#render
*/
public final class PiscesCache {
final class PiscesCache {
final int bboxX0, bboxY0, bboxX1, bboxY1;
......
......@@ -27,7 +27,6 @@ package sun.java2d.pisces;
import java.awt.Shape;
import java.awt.BasicStroke;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Path2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.PathIterator;
......@@ -250,7 +249,7 @@ public class PiscesRenderingEngine extends RenderingEngine {
float dashphase,
PathConsumer2D pc2d)
{
// We use inat and outat so that in Stroker and Dasher we can work only
// We use strokerat and outat so that in Stroker and Dasher we can work only
// with the pre-transformation coordinates. This will repeat a lot of
// computations done in the path iterator, but the alternative is to
// work with transformed paths and compute untransformed coordinates
......@@ -265,7 +264,7 @@ public class PiscesRenderingEngine extends RenderingEngine {
// transformation after the path processing has been done.
// We can't do this if normalization is on, because it isn't a good
// idea to normalize before the transformation is applied.
AffineTransform inat = null;
AffineTransform strokerat = null;
AffineTransform outat = null;
PathIterator pi = null;
......@@ -284,9 +283,9 @@ public class PiscesRenderingEngine extends RenderingEngine {
// again so, nothing can be drawn.
// Every path needs an initial moveTo and a pathDone. If these
// aren't there this causes a SIGSEV in libawt.so (at the time
// are not there this causes a SIGSEGV in libawt.so (at the time
// of writing of this comment (September 16, 2010)). Actually,
// I'm not sure if the moveTo is necessary to avoid the SIGSEV
// I am not sure if the moveTo is necessary to avoid the SIGSEGV
// but the pathDone is definitely needed.
pc2d.moveTo(0, 0);
pc2d.pathDone();
......@@ -313,25 +312,32 @@ public class PiscesRenderingEngine extends RenderingEngine {
if (normalize != NormMode.OFF) {
pi = new NormalizingPathIterator(pi, normalize);
}
// leave inat and outat null.
// by now strokerat == null && outat == null. Input paths to
// stroker (and maybe dasher) will have the full transform at
// applied to them and nothing will happen to the output paths.
} else {
// We only need the inverse if normalization is on. Otherwise
// we just don't transform the input paths, do all the stroking
// and then transform out output (instead of making PathIterator
// apply the transformation, us applying the inverse, and then
// us applying the transform again to our output).
outat = at;
if (normalize != NormMode.OFF) {
try {
inat = outat.createInverse();
} catch (NoninvertibleTransformException e) {
// we made sure this can't happen
e.printStackTrace();
}
strokerat = at;
pi = src.getPathIterator(at);
pi = new NormalizingPathIterator(pi, normalize);
// by now strokerat == at && outat == null. Input paths to
// stroker (and maybe dasher) will have the full transform at
// applied to them, then they will be normalized, and then
// the inverse of *only the non translation part of at* will
// be applied to the normalized paths. This won't cause problems
// in stroker, because, suppose at = T*A, where T is just the
// translation part of at, and A is the rest. T*A has already
// been applied to Stroker/Dasher's input. Then Ainv will be
// applied. Ainv*T*A is not equal to T, but it is a translation,
// which means that none of stroker's assumptions about its
// input will be violated. After all this, A will be applied
// to stroker's output.
} else {
outat = at;
pi = src.getPathIterator(null);
// outat == at && strokerat == null. This is because if no
// normalization is done, we can just apply all our
// transformations to stroker's output.
}
}
} else {
......@@ -343,13 +349,17 @@ 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.deltaTransformConsumer(pc2d, strokerat);
pc2d = new Stroker(pc2d, width, caps, join, miterlimit);
if (dashes != null) {
pc2d = new Dasher(pc2d, dashes, dashphase);
}
pc2d = TransformingPathConsumer2D.transformConsumer(pc2d, inat);
pc2d = TransformingPathConsumer2D.inverseDeltaTransformConsumer(pc2d, strokerat);
pathTo(pi, pc2d);
}
......@@ -588,9 +598,9 @@ public class PiscesRenderingEngine extends RenderingEngine {
}
Renderer r = new Renderer(3, 3,
clip.getLoX(), clip.getLoY(),
clip.getWidth(), clip.getHeight(),
PathIterator.WIND_EVEN_ODD);
clip.getLoX(), clip.getLoY(),
clip.getWidth(), clip.getHeight(),
PathIterator.WIND_EVEN_ODD);
r.moveTo((float) x, (float) y);
r.lineTo((float) (x+dx1), (float) (y+dy1));
......
......@@ -30,7 +30,7 @@ import java.util.concurrent.ConcurrentHashMap;
import sun.java2d.pipe.AATileGenerator;
public final class PiscesTileGenerator implements AATileGenerator {
final class PiscesTileGenerator implements AATileGenerator {
public static final int TILE_SIZE = PiscesCache.TILE_SIZE;
// perhaps we should be using weak references here, but right now
......
......@@ -33,7 +33,7 @@ import sun.awt.geom.PathConsumer2D;
// TODO: some of the arithmetic here is too verbose and prone to hard to
// debug typos. We should consider making a small Point/Vector class that
// has methods like plus(Point), minus(Point), dot(Point), cross(Point)and such
public class Stroker implements PathConsumer2D {
final class Stroker implements PathConsumer2D {
private static final int MOVE_TO = 0;
private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad
......@@ -130,7 +130,7 @@ public class Stroker implements PathConsumer2D {
private static void computeOffset(final float lx, final float ly,
final float w, final float[] m)
{
final float len = (float)Math.hypot(lx, ly);
final float len = (float)Math.sqrt(lx*lx + ly*ly);
if (len == 0) {
m[0] = m[1] = 0;
} else {
......@@ -758,7 +758,7 @@ public class Stroker implements PathConsumer2D {
// This is where the curve to be processed is put. We give it
// enough room to store 2 curves: one for the current subdivision, the
// other for the rest of the curve.
private float[][] middle = new float[2][8];
private float[] middle = new float[2*8];
private float[] lp = new float[8];
private float[] rp = new float[8];
private static final int MAX_N_CURVES = 11;
......@@ -766,55 +766,55 @@ public class Stroker implements PathConsumer2D {
private void somethingTo(final int type) {
// need these so we can update the state at the end of this method
final float xf = middle[0][type-2], yf = middle[0][type-1];
float dxs = middle[0][2] - middle[0][0];
float dys = middle[0][3] - middle[0][1];
float dxf = middle[0][type - 2] - middle[0][type - 4];
float dyf = middle[0][type - 1] - middle[0][type - 3];
final float xf = middle[type-2], yf = middle[type-1];
float dxs = middle[2] - middle[0];
float dys = middle[3] - middle[1];
float dxf = middle[type - 2] - middle[type - 4];
float dyf = middle[type - 1] - middle[type - 3];
switch(type) {
case 6:
if ((dxs == 0f && dys == 0f) ||
(dxf == 0f && dyf == 0f)) {
dxs = dxf = middle[0][4] - middle[0][0];
dys = dyf = middle[0][5] - middle[0][1];
dxs = dxf = middle[4] - middle[0];
dys = dyf = middle[5] - middle[1];
}
break;
case 8:
boolean p1eqp2 = (dxs == 0f && dys == 0f);
boolean p3eqp4 = (dxf == 0f && dyf == 0f);
if (p1eqp2) {
dxs = middle[0][4] - middle[0][0];
dys = middle[0][5] - middle[0][1];
dxs = middle[4] - middle[0];
dys = middle[5] - middle[1];
if (dxs == 0f && dys == 0f) {
dxs = middle[0][6] - middle[0][0];
dys = middle[0][7] - middle[0][1];
dxs = middle[6] - middle[0];
dys = middle[7] - middle[1];
}
}
if (p3eqp4) {
dxf = middle[0][6] - middle[0][2];
dyf = middle[0][7] - middle[0][3];
dxf = middle[6] - middle[2];
dyf = middle[7] - middle[3];
if (dxf == 0f && dyf == 0f) {
dxf = middle[0][6] - middle[0][0];
dyf = middle[0][7] - middle[0][1];
dxf = middle[6] - middle[0];
dyf = middle[7] - middle[1];
}
}
}
if (dxs == 0f && dys == 0f) {
// this happens iff the "curve" is just a point
lineTo(middle[0][0], middle[0][1]);
lineTo(middle[0], middle[1]);
return;
}
// if these vectors are too small, normalize them, to avoid future
// precision problems.
if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) {
double len = Math.hypot(dxs, dys);
dxs = (float)(dxs / len);
dys = (float)(dys / len);
float len = (float)Math.sqrt(dxs*dxs + dys*dys);
dxs /= len;
dys /= len;
}
if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) {
double len = Math.hypot(dxf, dyf);
dxf = (float)(dxf / len);
dyf = (float)(dyf / len);
float len = (float)Math.sqrt(dxf*dxf + dyf*dyf);
dxf /= len;
dyf /= len;
}
computeOffset(dxs, dys, lineWidth2, offset[0]);
......@@ -822,20 +822,20 @@ public class Stroker implements PathConsumer2D {
final float my = offset[0][1];
drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, mx, my);
int nSplits = findSubdivPoints(middle[0], subdivTs, type,lineWidth2);
int nSplits = findSubdivPoints(middle, subdivTs, type, lineWidth2);
int kind = 0;
Iterator<float[]> it = Curve.breakPtsAtTs(middle, type, subdivTs, nSplits);
Iterator<Integer> it = Curve.breakPtsAtTs(middle, type, subdivTs, nSplits);
while(it.hasNext()) {
float[] curCurve = it.next();
int curCurveOff = it.next();
kind = 0;
switch (type) {
case 8:
kind = computeOffsetCubic(curCurve, 0, lp, rp);
kind = computeOffsetCubic(middle, curCurveOff, lp, rp);
break;
case 6:
kind = computeOffsetQuad(curCurve, 0, lp, rp);
kind = computeOffsetQuad(middle, curCurveOff, lp, rp);
break;
}
if (kind != 0) {
......@@ -871,8 +871,7 @@ public class Stroker implements PathConsumer2D {
// to get good offset curves a distance of w away from the middle curve.
// Stores the points in ts, and returns how many of them there were.
private static Curve c = new Curve();
private static int findSubdivPoints(float[] pts, float[] ts,
final int type, final float w)
private static int findSubdivPoints(float[] pts, float[] ts, final int type, final float w)
{
final float x12 = pts[2] - pts[0];
final float y12 = pts[3] - pts[1];
......@@ -919,6 +918,7 @@ public class Stroker implements PathConsumer2D {
// now we must subdivide at points where one of the offset curves will have
// a cusp. This happens at ts where the radius of curvature is equal to w.
ret += c.rootsOfROCMinusW(ts, ret, w, 0.0001f);
ret = Helpers.filterOutNotInAB(ts, 0, ret, 0.0001f, 0.9999f);
Helpers.isort(ts, 0, ret);
return ret;
......@@ -928,10 +928,10 @@ public class Stroker implements PathConsumer2D {
float x2, float y2,
float x3, float y3)
{
middle[0][0] = cx0; middle[0][1] = cy0;
middle[0][2] = x1; middle[0][3] = y1;
middle[0][4] = x2; middle[0][5] = y2;
middle[0][6] = x3; middle[0][7] = y3;
middle[0] = cx0; middle[1] = cy0;
middle[2] = x1; middle[3] = y1;
middle[4] = x2; middle[5] = y2;
middle[6] = x3; middle[7] = y3;
somethingTo(8);
}
......@@ -940,9 +940,9 @@ public class Stroker implements PathConsumer2D {
}
@Override public void quadTo(float x1, float y1, float x2, float y2) {
middle[0][0] = cx0; middle[0][1] = cy0;
middle[0][2] = x1; middle[0][3] = y1;
middle[0][4] = x2; middle[0][5] = y2;
middle[0] = cx0; middle[1] = cy0;
middle[2] = x1; middle[3] = y1;
middle[4] = x2; middle[5] = y2;
somethingTo(6);
}
......
......@@ -28,7 +28,7 @@ package sun.java2d.pisces;
import sun.awt.geom.PathConsumer2D;
import java.awt.geom.AffineTransform;
public class TransformingPathConsumer2D {
final class TransformingPathConsumer2D {
public static PathConsumer2D
transformConsumer(PathConsumer2D out,
AffineTransform at)
......@@ -50,17 +50,72 @@ public class TransformingPathConsumer2D {
return new TranslateFilter(out, Mxt, Myt);
}
} else {
return new ScaleFilter(out, Mxx, Myy, Mxt, Myt);
if (Mxt == 0f && Myt == 0f) {
return new DeltaScaleFilter(out, Mxx, Myy);
} else {
return new ScaleFilter(out, Mxx, Myy, Mxt, Myt);
}
}
} else if (Mxt == 0f && Myt == 0f) {
return new DeltaTransformFilter(out, Mxx, Mxy, Myx, Myy);
} else {
return new TransformFilter(out, Mxx, Mxy, Mxt, Myx, Myy, Myt);
}
}
static class TranslateFilter implements PathConsumer2D {
PathConsumer2D out;
float tx;
float ty;
public static PathConsumer2D
deltaTransformConsumer(PathConsumer2D out,
AffineTransform at)
{
if (at == null) {
return out;
}
float Mxx = (float) at.getScaleX();
float Mxy = (float) at.getShearX();
float Myx = (float) at.getShearY();
float Myy = (float) at.getScaleY();
if (Mxy == 0f && Myx == 0f) {
if (Mxx == 1f && Myy == 1f) {
return out;
} else {
return new DeltaScaleFilter(out, Mxx, Myy);
}
} else {
return new DeltaTransformFilter(out, Mxx, Mxy, Myx, Myy);
}
}
public static PathConsumer2D
inverseDeltaTransformConsumer(PathConsumer2D out,
AffineTransform at)
{
if (at == null) {
return out;
}
float Mxx = (float) at.getScaleX();
float Mxy = (float) at.getShearX();
float Myx = (float) at.getShearY();
float Myy = (float) at.getScaleY();
if (Mxy == 0f && Myx == 0f) {
if (Mxx == 1f && Myy == 1f) {
return out;
} else {
return new DeltaScaleFilter(out, 1.0f/Mxx, 1.0f/Myy);
}
} else {
float det = Mxx * Myy - Mxy * Myx;
return new DeltaTransformFilter(out,
Myy / det,
-Mxy / det,
-Myx / det,
Mxx / det);
}
}
static final class TranslateFilter implements PathConsumer2D {
private final PathConsumer2D out;
private final float tx;
private final float ty;
TranslateFilter(PathConsumer2D out,
float tx, float ty)
......@@ -107,12 +162,12 @@ public class TransformingPathConsumer2D {
}
}
static class ScaleFilter implements PathConsumer2D {
PathConsumer2D out;
float sx;
float sy;
float tx;
float ty;
static final class ScaleFilter implements PathConsumer2D {
private final PathConsumer2D out;
private final float sx;
private final float sy;
private final float tx;
private final float ty;
ScaleFilter(PathConsumer2D out,
float sx, float sy, float tx, float ty)
......@@ -161,14 +216,14 @@ public class TransformingPathConsumer2D {
}
}
static class TransformFilter implements PathConsumer2D {
PathConsumer2D out;
float Mxx;
float Mxy;
float Mxt;
float Myx;
float Myy;
float Myt;
static final class TransformFilter implements PathConsumer2D {
private final PathConsumer2D out;
private final float Mxx;
private final float Mxy;
private final float Mxt;
private final float Myx;
private final float Myy;
private final float Myt;
TransformFilter(PathConsumer2D out,
float Mxx, float Mxy, float Mxt,
......@@ -226,4 +281,113 @@ public class TransformingPathConsumer2D {
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;
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册