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:
Denis Lila 2011-02-08 09:22:49 -05:00
parent 8e2b437a8b
commit 4b61d914f1
9 changed files with 719 additions and 785 deletions

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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;
// 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);
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;
}
/* use Cardano's formula */
// 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;
}
double cb_p = p * p * p;
double D = q * q + cb_p;
// 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]];
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,

View File

@ -32,7 +32,7 @@ import java.util.Arrays;
*
* @see PiscesRenderer#render
*/
public final class PiscesCache {
final class PiscesCache {
final int bboxX0, bboxY0, bboxX1, bboxY1;

View File

@ -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));

View File

@ -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

View File

@ -25,12 +25,9 @@
package sun.java2d.pisces;
import java.util.Arrays;
import java.util.Iterator;
import sun.awt.geom.PathConsumer2D;
public class Renderer implements PathConsumer2D {
final class Renderer implements PathConsumer2D {
private class ScanlineIterator {
@ -39,115 +36,81 @@ public class Renderer implements PathConsumer2D {
// crossing bounds. The bounds are not necessarily tight (the scan line
// at minY, for example, might have no crossings). The x bounds will
// be accumulated as crossings are computed.
private int minY, maxY;
private final int maxY;
private int nextY;
// indices into the segment pointer lists. They indicate the "active"
// sublist in the segment lists (the portion of the list that contains
// all the segments that cross the next scan line).
private int elo, ehi;
private final int[] edgePtrs;
private int qlo, qhi;
private final int[] quadPtrs;
private int clo, chi;
private final int[] curvePtrs;
private int edgeCount;
private int[] edgePtrs;
private static final int INIT_CROSSINGS_SIZE = 10;
private ScanlineIterator() {
crossings = new int[INIT_CROSSINGS_SIZE];
edgePtrs = new int[numEdges];
Helpers.fillWithIdxes(edgePtrs, SIZEOF_EDGE);
qsort(edges, edgePtrs, YMIN, 0, numEdges - 1);
quadPtrs = new int[numQuads];
Helpers.fillWithIdxes(quadPtrs, SIZEOF_QUAD);
qsort(quads, quadPtrs, YMIN, 0, numQuads - 1);
curvePtrs = new int[numCurves];
Helpers.fillWithIdxes(curvePtrs, SIZEOF_CURVE);
qsort(curves, curvePtrs, YMIN, 0, numCurves - 1);
edgePtrs = new int[INIT_CROSSINGS_SIZE];
// We don't care if we clip some of the line off with ceil, since
// no scan line crossings will be eliminated (in fact, the ceil is
// the y of the first scan line crossing).
nextY = minY = Math.max(boundsMinY, (int)Math.ceil(edgeMinY));
maxY = Math.min(boundsMaxY, (int)Math.ceil(edgeMaxY));
for (elo = 0; elo < numEdges && edges[edgePtrs[elo]+YMAX] <= minY; elo++)
;
// the active list is *edgePtrs[lo] (inclusive) *edgePtrs[hi] (exclusive)
for (ehi = elo; ehi < numEdges && edges[edgePtrs[ehi]+YMIN] <= minY; ehi++)
edgeSetCurY(edgePtrs[ehi], minY);// TODO: make minY a float to avoid casts
for (qlo = 0; qlo < numQuads && quads[quadPtrs[qlo]+YMAX] <= minY; qlo++)
;
for (qhi = qlo; qhi < numQuads && quads[quadPtrs[qhi]+YMIN] <= minY; qhi++)
quadSetCurY(quadPtrs[qhi], minY);
for (clo = 0; clo < numCurves && curves[curvePtrs[clo]+YMAX] <= minY; clo++)
;
for (chi = clo; chi < numCurves && curves[curvePtrs[chi]+YMIN] <= minY; chi++)
curveSetCurY(curvePtrs[chi], minY);
final int minY = getFirstScanLineCrossing();
nextY = minY;
maxY = getScanLineCrossingEnd()-1;
edgeCount = 0;
}
private int next() {
// we go through the active lists and remove segments that don't cross
// the nextY scanline.
int crossingIdx = 0;
for (int i = elo; i < ehi; i++) {
if (edges[edgePtrs[i]+YMAX] <= nextY) {
edgePtrs[i] = edgePtrs[elo++];
int cury = nextY++;
int bucket = cury - boundsMinY;
int count = this.edgeCount;
int ptrs[] = this.edgePtrs;
int bucketcount = edgeBucketCounts[bucket];
if ((bucketcount & 0x1) != 0) {
int newCount = 0;
for (int i = 0; i < count; i++) {
int ecur = ptrs[i];
if (edges[ecur+YMAX] > cury) {
ptrs[newCount++] = ecur;
}
}
count = newCount;
}
for (int i = qlo; i < qhi; i++) {
if (quads[quadPtrs[i]+YMAX] <= nextY) {
quadPtrs[i] = quadPtrs[qlo++];
ptrs = Helpers.widenArray(ptrs, count, bucketcount >> 1);
for (int ecur = edgeBuckets[bucket]; ecur != NULL; ecur = (int)edges[ecur+NEXT]) {
ptrs[count++] = ecur;
// REMIND: Adjust start Y if necessary
}
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;
}
}
for (int i = clo; i < chi; i++) {
if (curves[curvePtrs[i]+YMAX] <= nextY) {
curvePtrs[i] = curvePtrs[clo++];
int j = i;
while (--j >= 0) {
int jcross = xings[j];
if (jcross <= cross) {
break;
}
xings[j+1] = jcross;
ptrs[j+1] = ptrs[j];
}
xings[j+1] = cross;
ptrs[j+1] = ecur;
}
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;
return count;
}
private boolean hasNext() {
@ -157,51 +120,7 @@ public class Renderer implements PathConsumer2D {
private int curY() {
return nextY - 1;
}
private void addCrossing(int y, int x, float or, int idx) {
x <<= 1;
crossings[idx] = ((or > 0) ? (x | 0x1) : x);
}
}
// quicksort implementation for sorting the edge indices ("pointers")
// by increasing y0. first, last are indices into the "pointer" array
// It sorts the pointer array from first (inclusive) to last (inclusive)
private static void qsort(final float[] data, final int[] ptrs,
final int fieldForCmp, int first, int last)
{
if (last > first) {
int p = partition(data, ptrs, fieldForCmp, first, last);
if (first < p - 1) {
qsort(data, ptrs, fieldForCmp, first, p - 1);
}
if (p < last) {
qsort(data, ptrs, fieldForCmp, p, last);
}
}
}
// i, j are indices into edgePtrs.
private static int partition(final float[] data, final int[] ptrs,
final int fieldForCmp, int i, int j)
{
int pivotValFieldForCmp = ptrs[i]+fieldForCmp;
while (i <= j) {
// edges[edgePtrs[i]+1] is equivalent to (*(edgePtrs[i])).y0 in C
while (data[ptrs[i]+fieldForCmp] < data[pivotValFieldForCmp])
i++;
while (data[ptrs[j]+fieldForCmp] > data[pivotValFieldForCmp])
j--;
if (i <= j) {
int tmp = ptrs[i];
ptrs[i] = ptrs[j];
ptrs[j] = tmp;
i++;
j--;
}
}
return i;
}
//============================================================================
//////////////////////////////////////////////////////////////////////////////
@ -209,269 +128,89 @@ public class Renderer implements PathConsumer2D {
//////////////////////////////////////////////////////////////////////////////
// TODO(maybe): very tempting to use fixed point here. A lot of opportunities
// for shifts and just removing certain operations altogether.
// TODO: it might be worth it to make an EdgeList class. It would probably
// clean things up a bit and not impact performance much.
// common to all types of input path segments.
private static final int YMIN = 0;
private static final int YMAX = 1;
private static final int CURX = 2;
// this and OR are meant to be indeces into "int" fields, but arrays must
private static final int YMAX = 0;
private static final int CURX = 1;
// NEXT and OR are meant to be indices into "int" fields, but arrays must
// be homogenous, so every field is a float. However floats can represent
// exactly up to 26 bit ints, so we're ok.
private static final int CURY = 3;
private static final int OR = 4;
// for straight lines only:
private static final int SLOPE = 5;
// for quads and cubics:
private static final int X0 = 5;
private static final int Y0 = 6;
private static final int XL = 7;
private static final int COUNT = 8;
private static final int CURSLOPE = 9;
private static final int DX = 10;
private static final int DY = 11;
private static final int DDX = 12;
private static final int DDY = 13;
// for cubics only
private static final int DDDX = 14;
private static final int DDDY = 15;
private static final int OR = 2;
private static final int SLOPE = 3;
private static final int NEXT = 4;
private float edgeMinY = Float.POSITIVE_INFINITY;
private float edgeMaxY = Float.NEGATIVE_INFINITY;
private float edgeMinX = Float.POSITIVE_INFINITY;
private float edgeMaxX = Float.NEGATIVE_INFINITY;
private static final int SIZEOF_EDGE = 6;
private static final int SIZEOF_EDGE = 5;
// don't just set NULL to -1, because we want NULL+NEXT to be negative.
private static final int NULL = -SIZEOF_EDGE;
private float[] edges = null;
private int[] edgeBuckets = null;
private int[] edgeBucketCounts = null; // 2*newedges + (1 if pruning needed)
private int numEdges;
// these are static because we need them to be usable from ScanlineIterator
private void edgeSetCurY(final int idx, int y) {
edges[idx+CURX] += (y - edges[idx+CURY]) * edges[idx+SLOPE];
edges[idx+CURY] = y;
}
private void edgeGoToNextY(final int idx) {
edges[idx+CURY] += 1;
edges[idx+CURX] += edges[idx+SLOPE];
}
private static final int SIZEOF_QUAD = 14;
private float[] quads = null;
private int numQuads;
// This function should be called exactly once, to set the first scanline
// of the curve. Before it is called, the curve should think its first
// scanline is CEIL(YMIN).
private void quadSetCurY(final int idx, final int y) {
assert y < quads[idx+YMAX];
assert (quads[idx+CURY] > y);
assert (quads[idx+CURY] == Math.ceil(quads[idx+CURY]));
while (quads[idx+CURY] < ((float)y)) {
quadGoToNextY(idx);
}
}
private void quadGoToNextY(final int idx) {
quads[idx+CURY] += 1;
// this will get overriden if the while executes.
quads[idx+CURX] += quads[idx+CURSLOPE];
int count = (int)quads[idx+COUNT];
// this loop should never execute more than once because our
// curve is monotonic in Y. Still we put it in because you can
// never be too sure when dealing with floating point.
while(quads[idx+CURY] >= quads[idx+Y0] && count > 0) {
float x0 = quads[idx+X0], y0 = quads[idx+Y0];
count = executeQuadAFDIteration(idx);
float x1 = quads[idx+X0], y1 = quads[idx+Y0];
// our quads are monotonic, so this shouldn't happen, but
// it is conceivable that for very flat quads with different
// y values at their endpoints AFD might give us a horizontal
// segment.
if (y1 == y0) {
continue;
}
quads[idx+CURSLOPE] = (x1 - x0) / (y1 - y0);
quads[idx+CURX] = x0 + (quads[idx+CURY] - y0) * quads[idx+CURSLOPE];
}
}
private static final int SIZEOF_CURVE = 16;
private float[] curves = null;
private int numCurves;
private void curveSetCurY(final int idx, final int y) {
assert y < curves[idx+YMAX];
assert (curves[idx+CURY] > y);
assert (curves[idx+CURY] == Math.ceil(curves[idx+CURY]));
while (curves[idx+CURY] < ((float)y)) {
curveGoToNextY(idx);
}
}
private void curveGoToNextY(final int idx) {
curves[idx+CURY] += 1;
// this will get overriden if the while executes.
curves[idx+CURX] += curves[idx+CURSLOPE];
int count = (int)curves[idx+COUNT];
// this loop should never execute more than once because our
// curve is monotonic in Y. Still we put it in because you can
// never be too sure when dealing with floating point.
while(curves[idx+CURY] >= curves[idx+Y0] && count > 0) {
float x0 = curves[idx+X0], y0 = curves[idx+Y0];
count = executeCurveAFDIteration(idx);
float x1 = curves[idx+X0], y1 = curves[idx+Y0];
// our curves are monotonic, so this shouldn't happen, but
// it is conceivable that for very flat curves with different
// y values at their endpoints AFD might give us a horizontal
// segment.
if (y1 == y0) {
continue;
}
curves[idx+CURSLOPE] = (x1 - x0) / (y1 - y0);
curves[idx+CURX] = x0 + (curves[idx+CURY] - y0) * curves[idx+CURSLOPE];
}
}
private static final float DEC_BND = 20f;
private static final float INC_BND = 8f;
// each bucket is a linked list. this method adds eptr to the
// start "bucket"th linked list.
private void addEdgeToBucket(final int eptr, final int bucket) {
edges[eptr+NEXT] = edgeBuckets[bucket];
edgeBuckets[bucket] = eptr;
edgeBucketCounts[bucket] += 2;
}
// Flattens using adaptive forward differencing. This only carries out
// one iteration of the AFD loop. All it does is update AFD variables (i.e.
// X0, Y0, D*[X|Y], COUNT; not variables used for computing scanline crossings).
private int executeQuadAFDIteration(int idx) {
int count = (int)quads[idx+COUNT];
float ddx = quads[idx+DDX];
float ddy = quads[idx+DDY];
float dx = quads[idx+DX];
float dy = quads[idx+DY];
while (Math.abs(ddx) > DEC_BND || Math.abs(ddy) > DEC_BND) {
ddx = ddx / 4;
ddy = ddy / 4;
dx = (dx - ddx) / 2;
dy = (dy - ddy) / 2;
private void quadBreakIntoLinesAndAdd(float x0, float y0,
final Curve c,
final float x2, final float y2) {
final float QUAD_DEC_BND = 32;
final int countlg = 4;
int count = 1 << countlg;
int countsq = count * count;
float maxDD = Math.max(c.dbx / countsq, c.dby / countsq);
while (maxDD > QUAD_DEC_BND) {
maxDD /= 4;
count <<= 1;
}
// can only do this on even "count" values, because we must divide count by 2
while (count % 2 == 0 && Math.abs(dx) <= INC_BND && Math.abs(dy) <= INC_BND) {
dx = 2 * dx + ddx;
dy = 2 * dy + ddy;
ddx = 4 * ddx;
ddy = 4 * ddy;
count >>= 1;
}
count--;
if (count > 0) {
quads[idx+X0] += dx;
countsq = count * count;
final float ddx = c.dbx / countsq;
final float ddy = c.dby / countsq;
float dx = c.bx / countsq + c.cx / count;
float dy = c.by / countsq + c.cy / count;
while (count-- > 1) {
float x1 = x0 + dx;
dx += ddx;
quads[idx+Y0] += dy;
float y1 = y0 + dy;
dy += ddy;
} else {
quads[idx+X0] = quads[idx+XL];
quads[idx+Y0] = quads[idx+YMAX];
addLine(x0, y0, x1, y1);
x0 = x1;
y0 = y1;
}
quads[idx+COUNT] = count;
quads[idx+DDX] = ddx;
quads[idx+DDY] = ddy;
quads[idx+DX] = dx;
quads[idx+DY] = dy;
return count;
}
private int executeCurveAFDIteration(int idx) {
int count = (int)curves[idx+COUNT];
float ddx = curves[idx+DDX];
float ddy = curves[idx+DDY];
float dx = curves[idx+DX];
float dy = curves[idx+DY];
float dddx = curves[idx+DDDX];
float dddy = curves[idx+DDDY];
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;
addLine(x0, y0, x2, y2);
}
private void initLine(final int idx, float[] pts, int or) {
edges[idx+SLOPE] = (pts[2] - pts[0]) / (pts[3] - pts[1]);
edges[idx+CURX] = pts[0] + (edges[idx+CURY] - pts[1]) * edges[idx+SLOPE];
}
private void initQuad(final int idx, float[] points, int or) {
// x0, y0 and x3,y3 are the endpoints of the curve. We could compute these
// using c.xat(0),c.yat(0) and c.xat(1),c.yat(1), but this might introduce
// numerical errors, and our callers already have the exact values.
// Another alternative would be to pass all the control points, and call c.set
// here, but then too many numbers are passed around.
private void curveBreakIntoLinesAndAdd(float x0, float y0,
final Curve c,
final float x3, final float y3) {
final int countlg = 3;
final int count = 1 << countlg;
int count = 1 << countlg;
// the dx and dy refer to forward differencing variables, not the last
// coefficients of the "points" polynomial
final float ddx, ddy, dx, dy;
c.set(points, 6);
ddx = c.dbx / (1 << (2 * countlg));
ddy = c.dby / (1 << (2 * countlg));
dx = c.bx / (1 << (2 * countlg)) + c.cx / (1 << countlg);
dy = c.by / (1 << (2 * countlg)) + c.cy / (1 << countlg);
quads[idx+DDX] = ddx;
quads[idx+DDY] = ddy;
quads[idx+DX] = dx;
quads[idx+DY] = dy;
quads[idx+COUNT] = count;
quads[idx+XL] = points[4];
quads[idx+X0] = points[0];
quads[idx+Y0] = points[1];
executeQuadAFDIteration(idx);
float x1 = quads[idx+X0], y1 = quads[idx+Y0];
quads[idx+CURSLOPE] = (x1 - points[0]) / (y1 - points[1]);
quads[idx+CURX] = points[0] + (quads[idx+CURY] - points[1])*quads[idx+CURSLOPE];
}
private void initCurve(final int idx, float[] points, int or) {
final int countlg = 3;
final int count = 1 << countlg;
// the dx and dy refer to forward differencing variables, not the last
// coefficients of the "points" polynomial
final float dddx, dddy, ddx, ddy, dx, dy;
c.set(points, 8);
float dddx, dddy, ddx, ddy, dx, dy;
dddx = 2f * c.dax / (1 << (3 * countlg));
dddy = 2f * c.day / (1 << (3 * countlg));
@ -480,93 +219,100 @@ public class Renderer implements PathConsumer2D {
dx = c.ax / (1 << (3 * countlg)) + c.bx / (1 << (2 * countlg)) + c.cx / (1 << countlg);
dy = c.ay / (1 << (3 * countlg)) + c.by / (1 << (2 * countlg)) + c.cy / (1 << countlg);
curves[idx+DDDX] = dddx;
curves[idx+DDDY] = dddy;
curves[idx+DDX] = ddx;
curves[idx+DDY] = ddy;
curves[idx+DX] = dx;
curves[idx+DY] = dy;
curves[idx+COUNT] = count;
curves[idx+XL] = points[6];
curves[idx+X0] = points[0];
curves[idx+Y0] = points[1];
executeCurveAFDIteration(idx);
float x1 = curves[idx+X0], y1 = curves[idx+Y0];
curves[idx+CURSLOPE] = (x1 - points[0]) / (y1 - points[1]);
curves[idx+CURX] = points[0] + (curves[idx+CURY] - points[1])*curves[idx+CURSLOPE];
}
private void addPathSegment(float[] pts, final int type, final int or) {
int idx;
float[] addTo;
switch (type) {
case 4:
idx = numEdges * SIZEOF_EDGE;
addTo = edges = Helpers.widenArray(edges, numEdges*SIZEOF_EDGE, SIZEOF_EDGE);
numEdges++;
break;
case 6:
idx = numQuads * SIZEOF_QUAD;
addTo = quads = Helpers.widenArray(quads, numQuads*SIZEOF_QUAD, SIZEOF_QUAD);
numQuads++;
break;
case 8:
idx = numCurves * SIZEOF_CURVE;
addTo = curves = Helpers.widenArray(curves, numCurves*SIZEOF_CURVE, SIZEOF_CURVE);
numCurves++;
break;
default:
throw new InternalError();
}
// set the common fields, except CURX, for which we must know the kind
// of curve. NOTE: this must be done before the type specific fields
// are initialized, because those depend on the common ones.
addTo[idx+YMIN] = pts[1];
addTo[idx+YMAX] = pts[type-1];
addTo[idx+OR] = or;
addTo[idx+CURY] = (float)Math.ceil(pts[1]);
switch (type) {
case 4:
initLine(idx, pts, or);
break;
case 6:
initQuad(idx, pts, or);
break;
case 8:
initCurve(idx, pts, or);
break;
default:
throw new InternalError();
// we use x0, y0 to walk the line
float x1 = x0, y1 = y0;
while (count > 0) {
while (Math.abs(ddx) > DEC_BND || Math.abs(ddy) > DEC_BND) {
dddx /= 8;
dddy /= 8;
ddx = ddx/4 - dddx;
ddy = ddy/4 - dddy;
dx = (dx - ddx) / 2;
dy = (dy - ddy) / 2;
count <<= 1;
}
// can only do this on even "count" values, because we must divide count by 2
while (count % 2 == 0 && Math.abs(dx) <= INC_BND && Math.abs(dy) <= INC_BND) {
dx = 2 * dx + ddx;
dy = 2 * dy + ddy;
ddx = 4 * (ddx + dddx);
ddy = 4 * (ddy + dddy);
dddx = 8 * dddx;
dddy = 8 * dddy;
count >>= 1;
}
count--;
if (count > 0) {
x1 += dx;
dx += ddx;
ddx += dddx;
y1 += dy;
dy += ddy;
ddy += dddy;
} else {
x1 = x3;
y1 = y3;
}
addLine(x0, y0, x1, y1);
x0 = x1;
y0 = y1;
}
}
// precondition: the curve in pts must be monotonic and increasing in y.
private void somethingTo(float[] pts, final int type, final int or) {
// NOTE: it's very important that we check for or >= 0 below (as
// opposed to or == 1, or or > 0, or anything else). That's
// because if we check for or==1, when the curve being added
// is a horizontal line, or will be 0 so or==1 will be false and
// x0 and y0 will be updated to pts[0] and pts[1] instead of pts[type-2]
// and pts[type-1], which is the correct thing to do.
this.x0 = or >= 0 ? pts[type - 2] : pts[0];
this.y0 = or >= 0 ? pts[type - 1] : pts[1];
float minY = pts[1], maxY = pts[type - 1];
if (Math.ceil(minY) >= Math.ceil(maxY) ||
Math.ceil(minY) >= boundsMaxY || maxY < boundsMinY)
{
// Preconditions: y2 > y1 and the curve must cross some scanline
// i.e.: y1 <= y < y2 for some y such that boundsMinY <= y < boundsMaxY
private void addLine(float x1, float y1, float x2, float y2) {
float or = 1; // orientation of the line. 1 if y increases, 0 otherwise.
if (y2 < y1) {
or = y2; // no need to declare a temp variable. We have or.
y2 = y1;
y1 = or;
or = x2;
x2 = x1;
x1 = or;
or = 0;
}
final int firstCrossing = Math.max((int) Math.ceil(y1), boundsMinY);
final int lastCrossing = Math.min((int)Math.ceil(y2), boundsMaxY);
if (firstCrossing >= lastCrossing) {
return;
}
if (minY < edgeMinY) { edgeMinY = minY; }
if (maxY > edgeMaxY) { edgeMaxY = maxY; }
if (y1 < edgeMinY) { edgeMinY = y1; }
if (y2 > edgeMaxY) { edgeMaxY = y2; }
int minXidx = (pts[0] < pts[type-2] ? 0 : type - 2);
float minX = pts[minXidx];
float maxX = pts[type - 2 - minXidx];
if (minX < edgeMinX) { edgeMinX = minX; }
if (maxX > edgeMaxX) { edgeMaxX = maxX; }
addPathSegment(pts, type, or);
final float slope = (x2 - x1) / (y2 - y1);
if (slope > 0) { // <==> x1 < x2
if (x1 < edgeMinX) { edgeMinX = x1; }
if (x2 > edgeMaxX) { edgeMaxX = x2; }
} else {
if (x2 < edgeMinX) { edgeMinX = x2; }
if (x1 > edgeMaxX) { edgeMaxX = x1; }
}
final int ptr = numEdges * SIZEOF_EDGE;
edges = Helpers.widenArray(edges, ptr, SIZEOF_EDGE);
numEdges++;
edges[ptr+OR] = or;
edges[ptr+CURX] = x1 + (firstCrossing - y1) * slope;
edges[ptr+SLOPE] = slope;
edges[ptr+YMAX] = y2;
final int bucketIdx = firstCrossing - boundsMinY;
addEdgeToBucket(ptr, bucketIdx);
if (lastCrossing < boundsMaxY) {
edgeBucketCounts[lastCrossing - boundsMinY] |= 1;
}
}
// preconditions: should not be called before the last line has been added
// to the edge list (even though it will return a correct answer at that
// point in time, it's not meant to be used that way).
private int getFirstScanLineCrossing() {
return Math.max(boundsMinY, (int)Math.ceil(edgeMinY));
}
private int getScanLineCrossingEnd() {
return Math.min(boundsMaxY, (int)Math.ceil(edgeMaxY));
}
// END EDGE LIST
@ -619,6 +365,10 @@ public class Renderer implements PathConsumer2D {
this.boundsMinY = pix_boundsY * SUBPIXEL_POSITIONS_Y;
this.boundsMaxX = (pix_boundsX + pix_boundsWidth) * SUBPIXEL_POSITIONS_X;
this.boundsMaxY = (pix_boundsY + pix_boundsHeight) * SUBPIXEL_POSITIONS_Y;
edgeBuckets = new int[boundsMaxY - boundsMinY];
java.util.Arrays.fill(edgeBuckets, NULL);
edgeBucketCounts = new int[edgeBuckets.length];
}
private float tosubpixx(float pix_x) {
@ -636,74 +386,34 @@ public class Renderer implements PathConsumer2D {
this.x0 = tosubpixx(pix_x0);
}
public void lineJoin() { /* do nothing */ }
private final float[][] pts = new float[2][8];
private final float[] ts = new float[4];
private static void invertPolyPoints(float[] pts, int off, int type) {
for (int i = off, j = off + type - 2; i < j; i += 2, j -= 2) {
float tmp = pts[i];
pts[i] = pts[j];
pts[j] = tmp;
tmp = pts[i+1];
pts[i+1] = pts[j+1];
pts[j+1] = tmp;
}
}
// return orientation before making the curve upright.
private static int makeMonotonicCurveUpright(float[] pts, int off, int type) {
float y0 = pts[off + 1];
float y1 = pts[off + type - 1];
if (y0 > y1) {
invertPolyPoints(pts, off, type);
return -1;
} else if (y0 < y1) {
return 1;
}
return 0;
}
public void lineTo(float pix_x1, float pix_y1) {
pts[0][0] = x0; pts[0][1] = y0;
pts[0][2] = tosubpixx(pix_x1); pts[0][3] = tosubpixy(pix_y1);
int or = makeMonotonicCurveUpright(pts[0], 0, 4);
somethingTo(pts[0], 4, or);
float x1 = tosubpixx(pix_x1);
float y1 = tosubpixy(pix_y1);
addLine(x0, y0, x1, y1);
x0 = x1;
y0 = y1;
}
Curve c = new Curve();
private void curveOrQuadTo(int type) {
c.set(pts[0], type);
int numTs = c.dxRoots(ts, 0);
numTs += c.dyRoots(ts, numTs);
numTs = Helpers.filterOutNotInAB(ts, 0, numTs, 0, 1);
Helpers.isort(ts, 0, numTs);
Iterator<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,
float x2, float y2,
float x3, float y3)
{
pts[0][0] = x0; pts[0][1] = y0;
pts[0][2] = tosubpixx(x1); pts[0][3] = tosubpixy(y1);
pts[0][4] = tosubpixx(x2); pts[0][5] = tosubpixy(y2);
pts[0][6] = tosubpixx(x3); pts[0][7] = tosubpixy(y3);
curveOrQuadTo(8);
final float xe = tosubpixx(x3);
final float ye = tosubpixy(y3);
c.set(x0, y0, tosubpixx(x1), tosubpixy(y1), tosubpixx(x2), tosubpixy(y2), xe, ye);
curveBreakIntoLinesAndAdd(x0, y0, c, xe, ye);
x0 = xe;
y0 = ye;
}
@Override public void quadTo(float x1, float y1, float x2, float y2) {
pts[0][0] = x0; pts[0][1] = y0;
pts[0][2] = tosubpixx(x1); pts[0][3] = tosubpixy(y1);
pts[0][4] = tosubpixx(x2); pts[0][5] = tosubpixy(y2);
curveOrQuadTo(6);
final float xe = tosubpixx(x2);
final float ye = tosubpixy(y2);
c.set(x0, y0, tosubpixx(x1), tosubpixy(y1), xe, ye);
quadBreakIntoLinesAndAdd(x0, y0, c, xe, ye);
x0 = xe;
y0 = ye;
}
public void closePath() {
@ -728,9 +438,9 @@ public class Renderer implements PathConsumer2D {
// 0x1 if EVEN_ODD, all bits if NON_ZERO
int mask = (windingRule == WIND_EVEN_ODD) ? 0x1 : ~0x0;
// add 1 to better deal with the last pixel in a pixel row.
int width = pix_bboxx1 - pix_bboxx0 + 1;
int[] alpha = new int[width+1];
// add 2 to better deal with the last pixel in a pixel row.
int width = pix_bboxx1 - pix_bboxx0;
int[] alpha = new int[width+2];
int bboxx0 = pix_bboxx0 << SUBPIXEL_LG_POSITIONS_X;
int bboxx1 = pix_bboxx1 << SUBPIXEL_LG_POSITIONS_X;
@ -766,7 +476,8 @@ public class Renderer implements PathConsumer2D {
for (int i = 0; i < numCrossings; i++) {
int curxo = crossings[i];
int curx = curxo >> 1;
int crorientation = ((curxo & 0x1) == 0x1) ? 1 : -1;
// to turn {0, 1} into {-1, 1}, multiply by 2 and subtract 1.
int crorientation = ((curxo & 0x1) << 1) -1;
if ((sum & mask) != 0) {
int x0 = Math.max(prev, bboxx0);
int x1 = Math.min(curx, bboxx1);
@ -811,26 +522,26 @@ public class Renderer implements PathConsumer2D {
}
public void endRendering() {
final int bminx = boundsMinX >> SUBPIXEL_LG_POSITIONS_X;
final int bmaxx = boundsMaxX >> SUBPIXEL_LG_POSITIONS_X;
final int bminy = boundsMinY >> SUBPIXEL_LG_POSITIONS_Y;
final int bmaxy = boundsMaxY >> SUBPIXEL_LG_POSITIONS_Y;
final int eminx = ((int)Math.floor(edgeMinX)) >> SUBPIXEL_LG_POSITIONS_X;
final int emaxx = ((int)Math.ceil(edgeMaxX)) >> SUBPIXEL_LG_POSITIONS_X;
final int eminy = ((int)Math.floor(edgeMinY)) >> SUBPIXEL_LG_POSITIONS_Y;
final int emaxy = ((int)Math.ceil(edgeMaxY)) >> SUBPIXEL_LG_POSITIONS_Y;
int spminX = Math.max((int)Math.ceil(edgeMinX), boundsMinX);
int spmaxX = Math.min((int)Math.ceil(edgeMaxX), boundsMaxX);
int spminY = Math.max((int)Math.ceil(edgeMinY), boundsMinY);
int spmaxY = Math.min((int)Math.ceil(edgeMaxY), boundsMaxY);
final int minX = Math.max(bminx, eminx);
final int maxX = Math.min(bmaxx, emaxx);
final int minY = Math.max(bminy, eminy);
final int maxY = Math.min(bmaxy, emaxy);
if (minX > maxX || minY > maxY) {
this.cache = new PiscesCache(bminx, bminy, bmaxx, bmaxy);
int pminX = spminX >> SUBPIXEL_LG_POSITIONS_X;
int pmaxX = (spmaxX + SUBPIXEL_MASK_X) >> SUBPIXEL_LG_POSITIONS_X;
int pminY = spminY >> SUBPIXEL_LG_POSITIONS_Y;
int pmaxY = (spmaxY + SUBPIXEL_MASK_Y) >> SUBPIXEL_LG_POSITIONS_Y;
if (pminX > pmaxX || pminY > pmaxY) {
this.cache = new PiscesCache(boundsMinX >> SUBPIXEL_LG_POSITIONS_X,
boundsMinY >> SUBPIXEL_LG_POSITIONS_Y,
boundsMaxX >> SUBPIXEL_LG_POSITIONS_X,
boundsMaxY >> SUBPIXEL_LG_POSITIONS_Y);
return;
}
this.cache = new PiscesCache(minX, minY, maxX, maxY);
_endRendering(minX, minY, maxX, maxY);
this.cache = new PiscesCache(pminX, pminY, pmaxX, pmaxY);
_endRendering(pminX, pminY, pmaxX, pmaxY);
}
public PiscesCache getCache() {

View File

@ -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);
}

View File

@ -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;
}
}
}