提交 273af63e 编写于 作者: L lana

Merge

/*
* Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.java2d.pisces;
import java.util.Iterator;
class Curve {
float ax, ay, bx, by, cx, cy, dx, dy;
float dax, day, dbx, dby;
Curve() {
}
void set(float[] points, int type) {
switch(type) {
case 8:
set(points[0], points[1],
points[2], points[3],
points[4], points[5],
points[6], points[7]);
break;
case 6:
set(points[0], points[1],
points[2], points[3],
points[4], points[5]);
break;
default:
throw new InternalError("Curves can only be cubic or quadratic");
}
}
void set(float x1, float y1,
float x2, float y2,
float x3, float y3,
float x4, float y4)
{
ax = 3 * (x2 - x3) + x4 - x1;
ay = 3 * (y2 - y3) + y4 - y1;
bx = 3 * (x1 - 2 * x2 + x3);
by = 3 * (y1 - 2 * y2 + y3);
cx = 3 * (x2 - x1);
cy = 3 * (y2 - y1);
dx = x1;
dy = y1;
dax = 3 * ax; day = 3 * ay;
dbx = 2 * bx; dby = 2 * by;
}
void set(float x1, float y1,
float x2, float y2,
float x3, float y3)
{
ax = ay = 0f;
bx = x1 - 2 * x2 + x3;
by = y1 - 2 * y2 + y3;
cx = 2 * (x2 - x1);
cy = 2 * (y2 - y1);
dx = x1;
dy = y1;
dax = 0; day = 0;
dbx = 2 * bx; dby = 2 * by;
}
float xat(float t) {
return t * (t * (t * ax + bx) + cx) + dx;
}
float yat(float t) {
return t * (t * (t * ay + by) + cy) + dy;
}
float dxat(float t) {
return t * (t * dax + dbx) + cx;
}
float dyat(float t) {
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);
}
int dyRoots(float[] roots, int off) {
return Helpers.quadraticRoots(day, dby, cy, roots, off);
}
int infPoints(float[] pts, int off) {
// inflection point at t if -f'(t)x*f''(t)y + f'(t)y*f''(t)x == 0
// Fortunately, this turns out to be quadratic, so there are at
// most 2 inflection points.
final float a = dax * dby - dbx * day;
final float b = 2 * (cy * dax - day * cx);
final float c = cy * dbx - cx * dby;
return Helpers.quadraticRoots(a, b, c, pts, off);
}
// 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) {
assert pts.length >= off + 4;
// these are the coefficients of g(t):
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);
}
// Tries to find the roots of the function ROC(t)-w in [0, 1). It uses
// a variant of the false position algorithm to find the roots. False
// position requires that 2 initial values x0,x1 be given, and that the
// function must have opposite signs at those values. To find such
// values, we need the local extrema of the ROC function, for which we
// need the roots of its derivative; however, it's harder to find the
// roots of the derivative in this case than it is to find the roots
// of the original function. So, we find all points where this curve's
// first and second derivative are perpendicular, and we pretend these
// are our local extrema. There are at most 3 of these, so we will check
// at most 4 sub-intervals of (0,1). ROC has asymptotes at inflection
// points, so roc-w can have at least 6 roots. This shouldn't be a
// problem for what we're trying to do (draw a nice looking curve).
int rootsOfROCMinusW(float[] roots, int off, final float w, final float err) {
// 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);
float t0 = 0, ft0 = ROCsq(t0) - w*w;
roots[off + numPerpdfddf] = 1f; // always check interval end points
numPerpdfddf++;
for (int i = off; i < off + numPerpdfddf; i++) {
float t1 = roots[i], ft1 = ROCsq(t1) - w*w;
if (ft0 == 0f) {
roots[ret++] = t0;
} else if (ft1 * ft0 < 0f) { // have opposite signs
// (ROC(t)^2 == w^2) == (ROC(t) == w) is true because
// ROC(t) >= 0 for all t.
roots[ret++] = falsePositionROCsqMinusX(t0, t1, w*w, err);
}
t0 = t1;
ft0 = ft1;
}
return ret - off;
}
private static float eliminateInf(float x) {
return (x == Float.POSITIVE_INFINITY ? Float.MAX_VALUE :
(x == Float.NEGATIVE_INFINITY ? Float.MIN_VALUE : x));
}
// 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
// algorithm in Helpers.java
private float falsePositionROCsqMinusX(float x0, float x1,
final float x, final float err)
{
final int iterLimit = 100;
int side = 0;
float t = x1, ft = eliminateInf(ROCsq(t) - x);
float s = x0, fs = eliminateInf(ROCsq(s) - x);
float r = s, fr;
for (int i = 0; i < iterLimit && Math.abs(t - s) > err * Math.abs(t + s); i++) {
r = (fs * t - ft * s) / (fs - ft);
fr = ROCsq(r) - x;
if (fr * ft > 0) {// have the same sign
ft = fr; t = r;
if (side < 0) {
fs /= (1 << (-side));
side--;
} else {
side = -1;
}
} else if (fr * fs > 0) {
fs = fr; s = r;
if (side > 0) {
ft /= (1 << side);
side++;
} else {
side = 1;
}
} else {
break;
}
}
return r;
}
// 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);
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;
}
// curve to be broken should be in pts[0]
// this will change the contents of both pts and 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,
final float[] Ts, final int numTs)
{
assert pts.length >= 2 && pts[0].length >= 8 && numTs <= Ts.length;
return new Iterator<float[]>() {
int nextIdx = 0;
int nextCurveIdx = 0;
float prevT = 0;
@Override public boolean hasNext() {
return nextCurveIdx < numTs + 1;
}
@Override public float[] next() {
float[] 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;
} else {
ret = pts[nextIdx];
}
nextCurveIdx++;
return ret;
}
@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);
}
}
}
......@@ -25,6 +25,8 @@
package sun.java2d.pisces;
import sun.awt.geom.PathConsumer2D;
/**
* The <code>Dasher</code> class takes a series of linear commands
* (<code>moveTo</code>, <code>lineTo</code>, <code>close</code> and
......@@ -36,18 +38,16 @@ package sun.java2d.pisces;
* semantics are unclear.
*
*/
public class Dasher implements LineSink {
private final LineSink output;
public class Dasher implements sun.awt.geom.PathConsumer2D {
private final PathConsumer2D out;
private final float[] dash;
private final float startPhase;
private final boolean startDashOn;
private final int startIdx;
private final float m00, m10, m01, m11;
private final float det;
private boolean firstDashOn;
private boolean starting;
private boolean needsMoveTo;
private int idx;
private boolean dashOn;
......@@ -55,28 +55,23 @@ public class Dasher implements LineSink {
private float sx, sy;
private float x0, y0;
private float sx1, sy1;
// temporary storage for the current curve
private float[] curCurvepts;
/**
* Constructs a <code>Dasher</code>.
*
* @param output an output <code>LineSink</code>.
* @param dash an array of <code>int</code>s containing the dash pattern
* @param phase an <code>int</code> containing the dash phase
* @param transform a <code>Transform4</code> object indicating
* the transform that has been previously applied to all incoming
* coordinates. This is required in order to compute dash lengths
* properly.
* @param out an output <code>PathConsumer2D</code>.
* @param dash an array of <code>float</code>s containing the dash pattern
* @param phase a <code>float</code> containing the dash phase
*/
public Dasher(LineSink output,
float[] dash, float phase,
float a00, float a01, float a10, float a11) {
public Dasher(PathConsumer2D out, float[] dash, float phase) {
if (phase < 0) {
throw new IllegalArgumentException("phase < 0 !");
}
this.output = output;
this.out = out;
// Normalize so 0 <= phase < dash[0]
int idx = 0;
......@@ -92,16 +87,19 @@ public class Dasher implements LineSink {
this.startPhase = this.phase = phase;
this.startDashOn = dashOn;
this.startIdx = idx;
this.starting = true;
m00 = a00;
m01 = a01;
m10 = a10;
m11 = a11;
det = m00 * m11 - m01 * m10;
// we need curCurvepts to be able to contain 2 curves because when
// dashing curves, we need to subdivide it
curCurvepts = new float[8 * 2];
}
public void moveTo(float x0, float y0) {
output.moveTo(x0, y0);
if (firstSegidx > 0) {
out.moveTo(sx, sy);
emitFirstSegments();
}
needsMoveTo = true;
this.idx = startIdx;
this.dashOn = this.startDashOn;
this.phase = this.startPhase;
......@@ -110,104 +108,398 @@ public class Dasher implements LineSink {
this.starting = true;
}
public void lineJoin() {
output.lineJoin();
private void emitSeg(float[] buf, int off, int type) {
switch (type) {
case 8:
out.curveTo(buf[off+0], buf[off+1],
buf[off+2], buf[off+3],
buf[off+4], buf[off+5]);
break;
case 6:
out.quadTo(buf[off+0], buf[off+1],
buf[off+2], buf[off+3]);
break;
case 4:
out.lineTo(buf[off], buf[off+1]);
}
}
private void emitFirstSegments() {
for (int i = 0; i < firstSegidx; ) {
emitSeg(firstSegmentsBuffer, i+1, (int)firstSegmentsBuffer[i]);
i += (((int)firstSegmentsBuffer[i]) - 1);
}
firstSegidx = 0;
}
private void goTo(float x1, float y1) {
// We don't emit the first dash right away. If we did, caps would be
// drawn on it, but we need joins to be drawn if there's a closePath()
// So, we store the path elements that make up the first dash in the
// buffer below.
private float[] firstSegmentsBuffer = new float[7];
private int firstSegidx = 0;
// precondition: pts must be in relative coordinates (relative to x0,y0)
// fullCurve is true iff the curve in pts has not been split.
private void goTo(float[] pts, int off, final int type) {
float x = pts[off + type - 4];
float y = pts[off + type - 3];
if (dashOn) {
if (starting) {
this.sx1 = x1;
this.sy1 = y1;
firstDashOn = true;
starting = false;
firstSegmentsBuffer = Helpers.widenArray(firstSegmentsBuffer,
firstSegidx, type - 2);
firstSegmentsBuffer[firstSegidx++] = type;
System.arraycopy(pts, off, firstSegmentsBuffer, firstSegidx, type - 2);
firstSegidx += type - 2;
} else {
if (needsMoveTo) {
out.moveTo(x0, y0);
needsMoveTo = false;
}
emitSeg(pts, off, type);
}
output.lineTo(x1, y1);
} else {
if (starting) {
firstDashOn = false;
starting = false;
}
output.moveTo(x1, y1);
starting = false;
needsMoveTo = true;
}
this.x0 = x1;
this.y0 = y1;
this.x0 = x;
this.y0 = y;
}
public void lineTo(float x1, float y1) {
// The widened line is squished to a 0 width one, so no drawing is done
if (det == 0) {
goTo(x1, y1);
return;
}
float dx = x1 - x0;
float dy = y1 - y0;
float len = (float) Math.hypot(dx, dy);
// Compute segment length in the untransformed
// coordinate system
float la = (dy*m00 - dx*m10)/det;
float lb = (dy*m01 - dx*m11)/det;
float origLen = (float) Math.hypot(la, lb);
if (origLen == 0) {
// Let the output LineSink deal with cases where dx, dy are 0.
goTo(x1, y1);
if (len == 0) {
return;
}
// The scaling factors needed to get the dx and dy of the
// transformed dash segments.
float cx = dx / origLen;
float cy = dy / origLen;
float cx = dx / len;
float cy = dy / len;
while (true) {
float leftInThisDashSegment = dash[idx] - phase;
if (origLen < leftInThisDashSegment) {
goTo(x1, y1);
if (len <= leftInThisDashSegment) {
curCurvepts[0] = x1;
curCurvepts[1] = y1;
goTo(curCurvepts, 0, 4);
// Advance phase within current dash segment
phase += origLen;
return;
} else if (origLen == leftInThisDashSegment) {
goTo(x1, y1);
phase = 0f;
idx = (idx + 1) % dash.length;
dashOn = !dashOn;
phase += len;
if (len == leftInThisDashSegment) {
phase = 0f;
idx = (idx + 1) % dash.length;
dashOn = !dashOn;
}
return;
}
float dashx, dashy;
float dashdx = dash[idx] * cx;
float dashdy = dash[idx] * cy;
if (phase == 0) {
dashx = x0 + dashdx;
dashy = y0 + dashdy;
curCurvepts[0] = x0 + dashdx;
curCurvepts[1] = y0 + dashdy;
} else {
float p = (leftInThisDashSegment) / dash[idx];
dashx = x0 + p * dashdx;
dashy = y0 + p * dashdy;
float p = leftInThisDashSegment / dash[idx];
curCurvepts[0] = x0 + p * dashdx;
curCurvepts[1] = y0 + p * dashdy;
}
goTo(dashx, dashy);
goTo(curCurvepts, 0, 4);
len -= leftInThisDashSegment;
// Advance to next dash segment
idx = (idx + 1) % dash.length;
dashOn = !dashOn;
phase = 0;
}
}
private LengthIterator li = null;
// preconditions: curCurvepts must be an array of length at least 2 * type,
// that contains the curve we want to dash in the first type elements
private void somethingTo(int type) {
if (pointCurve(curCurvepts, type)) {
return;
}
if (li == null) {
li = new LengthIterator(4, 0.0001f);
}
li.initializeIterationOnCurve(curCurvepts, type);
origLen -= (dash[idx] - phase);
int curCurveoff = 0; // initially the current curve is at curCurvepts[0...type]
float lastSplitT = 0;
float t = 0;
float leftInThisDashSegment = dash[idx] - phase;
while ((t = li.next(leftInThisDashSegment)) < 1) {
if (t != 0) {
Helpers.subdivideAt((t - lastSplitT) / (1 - lastSplitT),
curCurvepts, curCurveoff,
curCurvepts, 0,
curCurvepts, type, type);
lastSplitT = t;
goTo(curCurvepts, 2, type);
curCurveoff = type;
}
// Advance to next dash segment
idx = (idx + 1) % dash.length;
dashOn = !dashOn;
phase = 0;
leftInThisDashSegment = dash[idx];
}
goTo(curCurvepts, curCurveoff+2, type);
phase += li.lastSegLen();
if (phase >= dash[idx]) {
phase = 0f;
idx = (idx + 1) % dash.length;
dashOn = !dashOn;
}
}
private static boolean pointCurve(float[] curve, int type) {
for (int i = 2; i < type; i++) {
if (curve[i] != curve[i-2]) {
return false;
}
}
return true;
}
// Objects of this class are used to iterate through curves. They return
// t values where the left side of the curve has a specified length.
// It does this by subdividing the input curve until a certain error
// condition has been met. A recursive subdivision procedure would
// return as many as 1<<limit curves, but this is an iterator and we
// don't need all the curves all at once, so what we carry out a
// lazy inorder traversal of the recursion tree (meaning we only move
// through the tree when we need the next subdivided curve). This saves
// us a lot of memory because at any one time we only need to store
// limit+1 curves - one for each level of the tree + 1.
// NOTE: the way we do things here is not enough to traverse a general
// tree; however, the trees we are interested in have the property that
// every non leaf node has exactly 2 children
private static class LengthIterator {
private enum Side {LEFT, RIGHT};
// Holds the curves at various levels of the recursion. The root
// (i.e. the original curve) is at recCurveStack[0] (but then it
// gets subdivided, the left half is put at 1, so most of the time
// only the right half of the original curve is at 0)
private float[][] recCurveStack;
// sides[i] indicates whether the node at level i+1 in the path from
// the root to the current leaf is a left or right child of its parent.
private Side[] sides;
private int curveType;
private final int limit;
private final float ERR;
private final float minTincrement;
// lastT and nextT delimit the current leaf.
private float nextT;
private float lenAtNextT;
private float lastT;
private float lenAtLastT;
private float lenAtLastSplit;
private float lastSegLen;
// the current level in the recursion tree. 0 is the root. limit
// is the deepest possible leaf.
private int recLevel;
private boolean done;
public LengthIterator(int reclimit, float err) {
this.limit = reclimit;
this.minTincrement = 1f / (1 << limit);
this.ERR = err;
this.recCurveStack = new float[reclimit+1][8];
this.sides = new Side[reclimit];
// if any methods are called without first initializing this object on
// a curve, we want it to fail ASAP.
this.nextT = Float.MAX_VALUE;
this.lenAtNextT = Float.MAX_VALUE;
this.lenAtLastSplit = Float.MIN_VALUE;
this.recLevel = Integer.MIN_VALUE;
this.lastSegLen = Float.MAX_VALUE;
this.done = true;
}
public void initializeIterationOnCurve(float[] pts, int type) {
System.arraycopy(pts, 0, recCurveStack[0], 0, type);
this.curveType = type;
this.recLevel = 0;
this.lastT = 0;
this.lenAtLastT = 0;
this.nextT = 0;
this.lenAtNextT = 0;
goLeft(); // initializes nextT and lenAtNextT properly
this.lenAtLastSplit = 0;
if (recLevel > 0) {
this.sides[0] = Side.LEFT;
this.done = false;
} else {
// the root of the tree is a leaf so we're done.
this.sides[0] = Side.RIGHT;
this.done = true;
}
this.lastSegLen = 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;
while(lenAtNextT < targetLength) {
if (done) {
lastSegLen = lenAtNextT - lenAtLastSplit;
return 1;
}
goToNextLeaf();
}
lenAtLastSplit = targetLength;
float t = binSearchForLen(lenAtLastSplit - lenAtLastT,
recCurveStack[recLevel], curveType, lenAtNextT - lenAtLastT, ERR);
// t is relative to the current leaf, so we must make it a valid parameter
// of the original curve.
t = t * (nextT - lastT) + lastT;
if (t >= 1) {
t = 1;
done = true;
}
// even if done = true, if we're here, that means targetLength
// is equal to, or very, very close to the total length of the
// curve, so lastSegLen won't be too high. In cases where len
// overshoots the curve, this method will exit in the while
// loop, and lastSegLen will still be set to the right value.
lastSegLen = len;
return t;
}
public float lastSegLen() {
return lastSegLen;
}
// Returns t such that if leaf is subdivided at t the left
// curve will have length len. leafLen must be the length of leaf.
private static Curve bsc = new Curve();
private static float binSearchForLen(float len, float[] leaf, int type,
float leafLen, float err)
{
assert len <= leafLen;
bsc.set(leaf, type);
float errBound = err*len;
float left = 0, right = 1;
while (left < right) {
float m = (left + right) / 2;
if (m == left || m == right) {
return m;
}
float x = bsc.xat(m);
float y = bsc.yat(m);
float leftLen = Helpers.linelen(leaf[0], leaf[1], x, y);
if (Math.abs(leftLen - len) < errBound) {
return m;
}
if (leftLen < len) {
left = m;
} else {
right = m;
}
}
return left;
}
// go to the next leaf (in an inorder traversal) in the recursion tree
// preconditions: must be on a leaf, and that leaf must not be the root.
private void goToNextLeaf() {
// We must go to the first ancestor node that has an unvisited
// right child.
recLevel--;
while(sides[recLevel] == Side.RIGHT) {
if (recLevel == 0) {
done = true;
return;
}
recLevel--;
}
sides[recLevel] = Side.RIGHT;
System.arraycopy(recCurveStack[recLevel], 0, recCurveStack[recLevel+1], 0, curveType);
recLevel++;
goLeft();
}
// go to the leftmost node from the current node. Return its length.
private void goLeft() {
float len = onLeaf();
if (len >= 0) {
lastT = nextT;
lenAtLastT = lenAtNextT;
nextT += (1 << (limit - recLevel)) * minTincrement;
lenAtNextT += len;
} else {
Helpers.subdivide(recCurveStack[recLevel], 0,
recCurveStack[recLevel+1], 0,
recCurveStack[recLevel], 0, curveType);
sides[recLevel] = Side.LEFT;
recLevel++;
goLeft();
}
}
// 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;
}
}
@Override
public void curveTo(float x1, float y1,
float x2, float y2,
float x3, float y3)
{
curCurvepts[0] = x0; curCurvepts[1] = y0;
curCurvepts[2] = x1; curCurvepts[3] = y1;
curCurvepts[4] = x2; curCurvepts[5] = y2;
curCurvepts[6] = x3; curCurvepts[7] = y3;
somethingTo(8);
}
public void close() {
@Override
public void quadTo(float x1, float y1, float x2, float y2) {
curCurvepts[0] = x0; curCurvepts[1] = y0;
curCurvepts[2] = x1; curCurvepts[3] = y1;
curCurvepts[4] = x2; curCurvepts[5] = y2;
somethingTo(6);
}
public void closePath() {
lineTo(sx, sy);
if (firstDashOn) {
output.lineTo(sx1, sy1);
if (firstSegidx > 0) {
if (!dashOn || needsMoveTo) {
out.moveTo(sx, sy);
}
emitFirstSegments();
}
moveTo(sx, sy);
}
public void end() {
output.end();
public void pathDone() {
if (firstSegidx > 0) {
out.moveTo(sx, sy);
emitFirstSegments();
}
out.pathDone();
}
@Override
public long getNativeConsumer() {
throw new InternalError("Dasher does not use a native consumer");
}
}
/*
* Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.java2d.pisces;
import java.util.Arrays;
final class Helpers {
private Helpers() {
throw new Error("This is a non instantiable class");
}
static boolean within(final float x, final float y, final float err) {
final float d = y - x;
return (d <= err && d >= -err);
}
static boolean within(final double x, final double y, final double err) {
final double d = y - x;
return (d <= err && d >= -err);
}
static int quadraticRoots(final float a, final float b,
final float c, float[] zeroes, final int off)
{
int ret = off;
float t;
if (a != 0f) {
final float dis = b*b - 4*a*c;
if (dis > 0) {
final float sqrtDis = (float)Math.sqrt(dis);
// depending on the sign of b we use a slightly different
// algorithm than the traditional one to find one of the roots
// so we can avoid adding numbers of different signs (which
// might result in loss of precision).
if (b >= 0) {
zeroes[ret++] = (2 * c) / (-b - sqrtDis);
zeroes[ret++] = (-b - sqrtDis) / (2 * a);
} else {
zeroes[ret++] = (-b + sqrtDis) / (2 * a);
zeroes[ret++] = (2 * c) / (-b + sqrtDis);
}
} else if (dis == 0f) {
t = (-b) / (2 * a);
zeroes[ret++] = t;
}
} else {
if (b != 0f) {
t = (-c) / b;
zeroes[ret++] = t;
}
}
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,
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;
}
int ret = off;
// we don't actually care much about the extrema themselves. We
// only use them to ensure that g(t) is monotonic in each
// interval [pts[i],pts[i+1] (for i in off...off+numCritPts+1).
// This will allow us to determine intervals containing exactly
// one root.
// The end points of the interval are always local extrema.
pts[off] = A;
pts[off + numCritPts + 1] = B;
numCritPts += 2;
float x0 = pts[off], fx0 = evalCubic(a, b, c, d, x0);
for (int i = off; i < off + numCritPts - 1; i++) {
float x1 = pts[i+1], fx1 = evalCubic(a, b, c, d, x1);
if (fx0 == 0f) {
pts[ret++] = x0;
} else if (fx1 * fx0 < 0f) { // have opposite signs
pts[ret++] = CubicNewton(a, b, c, d,
x0 + fx0 * (x1 - x0) / (fx0 - fx1), E);
}
x0 = x1;
fx0 = fx1;
}
return ret - off;
}
// 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;
}
// fills the input array with numbers 0, INC, 2*INC, ...
static void fillWithIdxes(final float[] data, final int[] idxes) {
if (idxes.length > 0) {
idxes[0] = 0;
for (int i = 1; i < idxes.length; i++) {
idxes[i] = idxes[i-1] + (int)data[idxes[i-1]];
}
}
}
static void fillWithIdxes(final int[] idxes, final int inc) {
if (idxes.length > 0) {
idxes[0] = 0;
for (int i = 1; i < idxes.length; i++) {
idxes[i] = idxes[i-1] + inc;
}
}
}
// These use a hardcoded factor of 2 for increasing sizes. Perhaps this
// should be provided as an argument.
static float[] widenArray(float[] in, final int cursize, final int numToAdd) {
if (in == null) {
return new float[5 * numToAdd];
}
if (in.length >= cursize + numToAdd) {
return in;
}
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;
}
return Arrays.copyOf(in, 2 * (cursize + numToAdd));
}
static float evalCubic(final float a, final float b,
final float c, final float d,
final float t)
{
return t * (t * (t * a + b) + c) + d;
}
static float evalQuad(final float a, final float b,
final float c, final float t)
{
return t * (t * a + b) + c;
}
// returns the index 1 past the last valid element remaining after filtering
static int filterOutNotInAB(float[] nums, final int off, final int len,
final float a, final float b)
{
int ret = off;
for (int i = off; i < off + len; i++) {
if (nums[i] > a && nums[i] < b) {
nums[ret++] = nums[i];
}
}
return ret;
}
static float polyLineLength(float[] poly, final int off, final int nCoords) {
assert nCoords % 2 == 0 && poly.length >= off + nCoords : "";
float acc = 0;
for (int i = off + 2; i < off + nCoords; i += 2) {
acc += linelen(poly[i], poly[i+1], poly[i-2], poly[i-1]);
}
return acc;
}
static float linelen(float x1, float y1, float x2, float y2) {
return (float)Math.hypot(x2 - x1, y2 - y1);
}
static void subdivide(float[] src, int srcoff, float[] left, int leftoff,
float[] right, int rightoff, int type)
{
switch(type) {
case 6:
Helpers.subdivideQuad(src, srcoff, left, leftoff, right, rightoff);
break;
case 8:
Helpers.subdivideCubic(src, srcoff, left, leftoff, right, rightoff);
break;
default:
throw new InternalError("Unsupported curve type");
}
}
static void isort(float[] a, int off, int len) {
for (int i = off + 1; i < off + len; i++) {
float ai = a[i];
int j = i - 1;
for (; j >= off && a[j] > ai; j--) {
a[j+1] = a[j];
}
a[j+1] = ai;
}
}
// Most of these are copied from classes in java.awt.geom because we need
// float versions of these functions, and Line2D, CubicCurve2D,
// QuadCurve2D don't provide them.
/**
* Subdivides the cubic curve specified by the coordinates
* stored in the <code>src</code> array at indices <code>srcoff</code>
* through (<code>srcoff</code>&nbsp;+&nbsp;7) and stores the
* resulting two subdivided curves into the two result arrays at the
* corresponding indices.
* Either or both of the <code>left</code> and <code>right</code>
* arrays may be <code>null</code> or a reference to the same array
* as the <code>src</code> array.
* Note that the last point in the first subdivided curve is the
* same as the first point in the second subdivided curve. Thus,
* it is possible to pass the same array for <code>left</code>
* and <code>right</code> and to use offsets, such as <code>rightoff</code>
* equals (<code>leftoff</code> + 6), in order
* to avoid allocating extra storage for this common point.
* @param src the array holding the coordinates for the source curve
* @param srcoff the offset into the array of the beginning of the
* the 6 source coordinates
* @param left the array for storing the coordinates for the first
* half of the subdivided curve
* @param leftoff the offset into the array of the beginning of the
* the 6 left coordinates
* @param right the array for storing the coordinates for the second
* half of the subdivided curve
* @param rightoff the offset into the array of the beginning of the
* the 6 right coordinates
* @since 1.7
*/
static void subdivideCubic(float src[], int srcoff,
float left[], int leftoff,
float right[], int rightoff)
{
float x1 = src[srcoff + 0];
float y1 = src[srcoff + 1];
float ctrlx1 = src[srcoff + 2];
float ctrly1 = src[srcoff + 3];
float ctrlx2 = src[srcoff + 4];
float ctrly2 = src[srcoff + 5];
float x2 = src[srcoff + 6];
float y2 = src[srcoff + 7];
if (left != null) {
left[leftoff + 0] = x1;
left[leftoff + 1] = y1;
}
if (right != null) {
right[rightoff + 6] = x2;
right[rightoff + 7] = y2;
}
x1 = (x1 + ctrlx1) / 2.0f;
y1 = (y1 + ctrly1) / 2.0f;
x2 = (x2 + ctrlx2) / 2.0f;
y2 = (y2 + ctrly2) / 2.0f;
float centerx = (ctrlx1 + ctrlx2) / 2.0f;
float centery = (ctrly1 + ctrly2) / 2.0f;
ctrlx1 = (x1 + centerx) / 2.0f;
ctrly1 = (y1 + centery) / 2.0f;
ctrlx2 = (x2 + centerx) / 2.0f;
ctrly2 = (y2 + centery) / 2.0f;
centerx = (ctrlx1 + ctrlx2) / 2.0f;
centery = (ctrly1 + ctrly2) / 2.0f;
if (left != null) {
left[leftoff + 2] = x1;
left[leftoff + 3] = y1;
left[leftoff + 4] = ctrlx1;
left[leftoff + 5] = ctrly1;
left[leftoff + 6] = centerx;
left[leftoff + 7] = centery;
}
if (right != null) {
right[rightoff + 0] = centerx;
right[rightoff + 1] = centery;
right[rightoff + 2] = ctrlx2;
right[rightoff + 3] = ctrly2;
right[rightoff + 4] = x2;
right[rightoff + 5] = y2;
}
}
static void subdivideCubicAt(float t, float src[], int srcoff,
float left[], int leftoff,
float right[], int rightoff)
{
float x1 = src[srcoff + 0];
float y1 = src[srcoff + 1];
float ctrlx1 = src[srcoff + 2];
float ctrly1 = src[srcoff + 3];
float ctrlx2 = src[srcoff + 4];
float ctrly2 = src[srcoff + 5];
float x2 = src[srcoff + 6];
float y2 = src[srcoff + 7];
if (left != null) {
left[leftoff + 0] = x1;
left[leftoff + 1] = y1;
}
if (right != null) {
right[rightoff + 6] = x2;
right[rightoff + 7] = y2;
}
x1 = x1 + t * (ctrlx1 - x1);
y1 = y1 + t * (ctrly1 - y1);
x2 = ctrlx2 + t * (x2 - ctrlx2);
y2 = ctrly2 + t * (y2 - ctrly2);
float centerx = ctrlx1 + t * (ctrlx2 - ctrlx1);
float centery = ctrly1 + t * (ctrly2 - ctrly1);
ctrlx1 = x1 + t * (centerx - x1);
ctrly1 = y1 + t * (centery - y1);
ctrlx2 = centerx + t * (x2 - centerx);
ctrly2 = centery + t * (y2 - centery);
centerx = ctrlx1 + t * (ctrlx2 - ctrlx1);
centery = ctrly1 + t * (ctrly2 - ctrly1);
if (left != null) {
left[leftoff + 2] = x1;
left[leftoff + 3] = y1;
left[leftoff + 4] = ctrlx1;
left[leftoff + 5] = ctrly1;
left[leftoff + 6] = centerx;
left[leftoff + 7] = centery;
}
if (right != null) {
right[rightoff + 0] = centerx;
right[rightoff + 1] = centery;
right[rightoff + 2] = ctrlx2;
right[rightoff + 3] = ctrly2;
right[rightoff + 4] = x2;
right[rightoff + 5] = y2;
}
}
static void subdivideQuad(float src[], int srcoff,
float left[], int leftoff,
float right[], int rightoff)
{
float x1 = src[srcoff + 0];
float y1 = src[srcoff + 1];
float ctrlx = src[srcoff + 2];
float ctrly = src[srcoff + 3];
float x2 = src[srcoff + 4];
float y2 = src[srcoff + 5];
if (left != null) {
left[leftoff + 0] = x1;
left[leftoff + 1] = y1;
}
if (right != null) {
right[rightoff + 4] = x2;
right[rightoff + 5] = y2;
}
x1 = (x1 + ctrlx) / 2.0f;
y1 = (y1 + ctrly) / 2.0f;
x2 = (x2 + ctrlx) / 2.0f;
y2 = (y2 + ctrly) / 2.0f;
ctrlx = (x1 + x2) / 2.0f;
ctrly = (y1 + y2) / 2.0f;
if (left != null) {
left[leftoff + 2] = x1;
left[leftoff + 3] = y1;
left[leftoff + 4] = ctrlx;
left[leftoff + 5] = ctrly;
}
if (right != null) {
right[rightoff + 0] = ctrlx;
right[rightoff + 1] = ctrly;
right[rightoff + 2] = x2;
right[rightoff + 3] = y2;
}
}
static void subdivideQuadAt(float t, float src[], int srcoff,
float left[], int leftoff,
float right[], int rightoff)
{
float x1 = src[srcoff + 0];
float y1 = src[srcoff + 1];
float ctrlx = src[srcoff + 2];
float ctrly = src[srcoff + 3];
float x2 = src[srcoff + 4];
float y2 = src[srcoff + 5];
if (left != null) {
left[leftoff + 0] = x1;
left[leftoff + 1] = y1;
}
if (right != null) {
right[rightoff + 4] = x2;
right[rightoff + 5] = y2;
}
x1 = x1 + t * (ctrlx - x1);
y1 = y1 + t * (ctrly - y1);
x2 = ctrlx + t * (x2 - ctrlx);
y2 = ctrly + t * (y2 - ctrly);
ctrlx = x1 + t * (x2 - x1);
ctrly = y1 + t * (y2 - y1);
if (left != null) {
left[leftoff + 2] = x1;
left[leftoff + 3] = y1;
left[leftoff + 4] = ctrlx;
left[leftoff + 5] = ctrly;
}
if (right != null) {
right[rightoff + 0] = ctrlx;
right[rightoff + 1] = ctrly;
right[rightoff + 2] = x2;
right[rightoff + 3] = y2;
}
}
static void subdivideAt(float t, float src[], int srcoff,
float left[], int leftoff,
float right[], int rightoff, int size)
{
switch(size) {
case 8:
subdivideCubicAt(t, src, srcoff, left, leftoff, right, rightoff);
break;
case 6:
subdivideQuadAt(t, src, srcoff, left, leftoff, right, rightoff);
break;
}
}
}
/*
* Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.java2d.pisces;
/**
* The <code>LineSink</code> interface accepts a series of line
* drawing commands: <code>moveTo</code>, <code>lineTo</code>,
* <code>close</code> (equivalent to a <code>lineTo</code> command
* with an argument equal to the argument of the last
* <code>moveTo</code> command), and <code>end</code>.
*
* <p> A <code>Flattener</code> may be used to connect a general path
* source to a <code>LineSink</code>.
*
* <p> The <code>Renderer</code> class implements the
* <code>LineSink</code> interface.
*
*/
public interface LineSink {
/**
* Moves the current drawing position to the point <code>(x0,
* y0)</code>.
*
* @param x0 the X coordinate
* @param y0 the Y coordinate
*/
public void moveTo(float x0, float y0);
/**
* Provides a hint that the current segment should be joined to
* the following segment using an explicit miter or round join if
* required.
*
* <p> An application-generated path will generally have no need
* to contain calls to this method; they are typically introduced
* by a <code>Flattener</code> to mark segment divisions that
* appear in its input, and consumed by a <code>Stroker</code>
* that is responsible for emitting the miter or round join
* segments.
*
* <p> Other <code>LineSink</code> classes should simply pass this
* hint to their output sink as needed.
*/
public void lineJoin();
/**
* Draws a line from the current drawing position to the point
* <code>(x1, y1)</code> and sets the current drawing position to
* <code>(x1, y1)</code>.
*
* @param x1 the X coordinate
* @param y1 the Y coordinate
*/
public void lineTo(float x1, float y1);
/**
* Closes the current path by drawing a line from the current
* drawing position to the point specified by the moset recent
* <code>moveTo</code> command.
*/
public void close();
/**
* Ends the current path. It may be necessary to end a path in
* order to allow end caps to be drawn.
*/
public void end();
}
......@@ -25,6 +25,8 @@
package sun.java2d.pisces;
import java.util.Arrays;
/**
* An object used to cache pre-rendered complex paths.
*
......@@ -32,115 +34,153 @@ package sun.java2d.pisces;
*/
public final class PiscesCache {
int bboxX0, bboxY0, bboxX1, bboxY1;
final int bboxX0, bboxY0, bboxX1, bboxY1;
// rowAARLE[i] holds the encoding of the pixel row with y = bboxY0+i.
// The format of each of the inner arrays is: rowAARLE[i][0,1] = (x0, n)
// where x0 is the first x in row i with nonzero alpha, and n is the
// number of RLE entries in this row. rowAARLE[i][j,j+1] for j>1 is
// (val,runlen)
final int[][] rowAARLE;
// RLE encodings are added in increasing y rows and then in increasing
// x inside those rows. Therefore, at any one time there is a well
// defined position (x,y) where a run length is about to be added (or
// the row terminated). x0,y0 is this (x,y)-(bboxX0,bboxY0). They
// are used to get indices into the current tile.
private int x0 = Integer.MIN_VALUE, y0 = Integer.MIN_VALUE;
// touchedTile[i][j] is the sum of all the alphas in the tile with
// y=i*TILE_SIZE+bboxY0 and x=j*TILE_SIZE+bboxX0.
private final int[][] touchedTile;
static final int TILE_SIZE_LG = 5;
static final int TILE_SIZE = 1 << TILE_SIZE_LG; // 32
private static final int INIT_ROW_SIZE = 8; // enough for 3 run lengths
PiscesCache(int minx, int miny, int maxx, int maxy) {
assert maxy >= miny && maxx >= minx;
bboxX0 = minx;
bboxY0 = miny;
bboxX1 = maxx + 1;
bboxY1 = maxy + 1;
// we could just leave the inner arrays as null and allocate them
// lazily (which would be beneficial for shapes with gaps), but we
// assume there won't be too many of those so we allocate everything
// up front (which is better for other cases)
rowAARLE = new int[bboxY1 - bboxY0 + 1][INIT_ROW_SIZE];
x0 = 0;
y0 = -1; // -1 makes the first assert in startRow succeed
// the ceiling of (maxy - miny + 1) / TILE_SIZE;
int nyTiles = (maxy - miny + TILE_SIZE) >> TILE_SIZE_LG;
int nxTiles = (maxx - minx + TILE_SIZE) >> TILE_SIZE_LG;
touchedTile = new int[nyTiles][nxTiles];
}
byte[] rowAARLE;
int alphaRLELength;
void addRLERun(int val, int runLen) {
if (runLen > 0) {
addTupleToRow(y0, val, runLen);
if (val != 0) {
// the x and y of the current row, minus bboxX0, bboxY0
int tx = x0 >> TILE_SIZE_LG;
int ty = y0 >> TILE_SIZE_LG;
int tx1 = (x0 + runLen - 1) >> TILE_SIZE_LG;
// while we forbid rows from starting before bboxx0, our users
// can still store rows that go beyond bboxx1 (although this
// shouldn't happen), so it's a good idea to check that i
// is not going out of bounds in touchedTile[ty]
if (tx1 >= touchedTile[ty].length) {
tx1 = touchedTile[ty].length - 1;
}
if (tx <= tx1) {
int nextTileXCoord = (tx + 1) << TILE_SIZE_LG;
if (nextTileXCoord > x0+runLen) {
touchedTile[ty][tx] += val * runLen;
} else {
touchedTile[ty][tx] += val * (nextTileXCoord - x0);
}
tx++;
}
// don't go all the way to tx1 - we need to handle the last
// tile as a special case (just like we did with the first
for (; tx < tx1; tx++) {
// try {
touchedTile[ty][tx] += (val << TILE_SIZE_LG);
// } catch (RuntimeException e) {
// System.out.println("x0, y0: " + x0 + ", " + y0);
// System.out.printf("tx, ty, tx1: %d, %d, %d %n", tx, ty, tx1);
// System.out.printf("bboxX/Y0/1: %d, %d, %d, %d %n",
// bboxX0, bboxY0, bboxX1, bboxY1);
// throw e;
// }
}
// they will be equal unless x0>>TILE_SIZE_LG == tx1
if (tx == tx1) {
int lastXCoord = Math.min(x0 + runLen, (tx + 1) << TILE_SIZE_LG);
int txXCoord = tx << TILE_SIZE_LG;
touchedTile[ty][tx] += val * (lastXCoord - txXCoord);
}
}
x0 += runLen;
}
}
int[] rowOffsetsRLE;
int[] minTouched;
int alphaRows;
void startRow(int y, int x) {
// rows are supposed to be added by increasing y.
assert y - bboxY0 > y0;
assert y <= bboxY1; // perhaps this should be < instead of <=
private PiscesCache() {}
y0 = y - bboxY0;
// this should be a new, uninitialized row.
assert rowAARLE[y0][1] == 0;
public static PiscesCache createInstance() {
return new PiscesCache();
}
x0 = x - bboxX0;
assert x0 >= 0 : "Input must not be to the left of bbox bounds";
private static final float ROWAA_RLE_FACTOR = 1.5f;
private static final float TOUCHED_FACTOR = 1.5f;
private static final int MIN_TOUCHED_LEN = 64;
private void reallocRowAARLE(int newLength) {
if (rowAARLE == null) {
rowAARLE = new byte[newLength];
} else if (rowAARLE.length < newLength) {
int len = Math.max(newLength,
(int)(rowAARLE.length*ROWAA_RLE_FACTOR));
byte[] newRowAARLE = new byte[len];
System.arraycopy(rowAARLE, 0, newRowAARLE, 0, rowAARLE.length);
rowAARLE = newRowAARLE;
}
// the way addTupleToRow is implemented it would work for this but it's
// not a good idea to use it because it is meant for adding
// RLE tuples, not the first tuple (which is special).
rowAARLE[y0][0] = x;
rowAARLE[y0][1] = 2;
}
private void reallocRowInfo(int newHeight) {
if (minTouched == null) {
int len = Math.max(newHeight, MIN_TOUCHED_LEN);
minTouched = new int[len];
rowOffsetsRLE = new int[len];
} else if (minTouched.length < newHeight) {
int len = Math.max(newHeight,
(int)(minTouched.length*TOUCHED_FACTOR));
int[] newMinTouched = new int[len];
int[] newRowOffsetsRLE = new int[len];
System.arraycopy(minTouched, 0, newMinTouched, 0,
alphaRows);
System.arraycopy(rowOffsetsRLE, 0, newRowOffsetsRLE, 0,
alphaRows);
minTouched = newMinTouched;
rowOffsetsRLE = newRowOffsetsRLE;
}
int alphaSumInTile(int x, int y) {
x -= bboxX0;
y -= bboxY0;
return touchedTile[y>>TILE_SIZE_LG][x>>TILE_SIZE_LG];
}
void addRLERun(byte val, int runLen) {
reallocRowAARLE(alphaRLELength + 2);
rowAARLE[alphaRLELength++] = val;
rowAARLE[alphaRLELength++] = (byte)runLen;
int minTouched(int rowidx) {
return rowAARLE[rowidx][0];
}
void startRow(int y, int x0, int x1) {
if (alphaRows == 0) {
bboxY0 = y;
bboxY1 = y+1;
bboxX0 = x0;
bboxX1 = x1+1;
} else {
if (bboxX0 > x0) bboxX0 = x0;
if (bboxX1 < x1 + 1) bboxX1 = x1 + 1;
while (bboxY1++ < y) {
reallocRowInfo(alphaRows+1);
minTouched[alphaRows] = 0;
// Assuming last 2 entries in rowAARLE are 0,0
rowOffsetsRLE[alphaRows] = alphaRLELength-2;
alphaRows++;
}
}
reallocRowInfo(alphaRows+1);
minTouched[alphaRows] = x0;
rowOffsetsRLE[alphaRows] = alphaRLELength;
alphaRows++;
int rowLength(int rowidx) {
return rowAARLE[rowidx][1];
}
public synchronized void dispose() {
rowAARLE = null;
alphaRLELength = 0;
minTouched = null;
rowOffsetsRLE = null;
alphaRows = 0;
bboxX0 = bboxY0 = bboxX1 = bboxY1 = 0;
private void addTupleToRow(int row, int a, int b) {
int end = rowAARLE[row][1];
rowAARLE[row] = Helpers.widenArray(rowAARLE[row], end, 2);
rowAARLE[row][end++] = a;
rowAARLE[row][end++] = b;
rowAARLE[row][1] = end;
}
public void print(java.io.PrintStream out) {
synchronized (out) {
out.println("bbox = ["+
bboxX0+", "+bboxY0+" => "+
bboxX1+", "+bboxY1+"]");
out.println("alphRLELength = "+alphaRLELength);
for (int y = bboxY0; y < bboxY1; y++) {
int i = y-bboxY0;
out.println("row["+i+"] == {"+
"minX = "+minTouched[i]+
", off = "+rowOffsetsRLE[i]+"}");
}
for (int i = 0; i < alphaRLELength; i += 2) {
out.println("rle["+i+"] = "+
(rowAARLE[i+1]&0xff)+" of "+(rowAARLE[i]&0xff));
@Override
public String toString() {
String ret = "bbox = ["+
bboxX0+", "+bboxY0+" => "+
bboxX1+", "+bboxY1+"]\n";
for (int[] row : rowAARLE) {
if (row != null) {
ret += ("minTouchedX=" + row[0] +
"\tRLE Entries: " + Arrays.toString(
Arrays.copyOfRange(row, 2, row[1])) + "\n");
} else {
ret += "[]\n";
}
}
}
return ret;
}
}
......@@ -27,7 +27,7 @@ package sun.java2d.pisces;
import java.awt.Shape;
import java.awt.BasicStroke;
import java.awt.geom.FlatteningPathIterator;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Path2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.PathIterator;
......@@ -38,8 +38,6 @@ import sun.java2d.pipe.RenderingEngine;
import sun.java2d.pipe.AATileGenerator;
public class PiscesRenderingEngine extends RenderingEngine {
public static double defaultFlat = 0.1;
private static enum NormMode {OFF, ON_NO_AA, ON_WITH_AA}
/**
......@@ -78,20 +76,29 @@ public class PiscesRenderingEngine extends RenderingEngine {
miterlimit,
dashes,
dashphase,
new LineSink() {
new PathConsumer2D() {
public void moveTo(float x0, float y0) {
p2d.moveTo(x0, y0);
}
public void lineJoin() {}
public void lineTo(float x1, float y1) {
p2d.lineTo(x1, y1);
}
public void close() {
public void closePath() {
p2d.closePath();
}
public void end() {}
public void pathDone() {}
public void curveTo(float x1, float y1,
float x2, float y2,
float x3, float y3) {
p2d.curveTo(x1, y1, x2, y2, x3, y3);
}
public void quadTo(float x1, float y1, float x2, float y2) {
p2d.quadTo(x1, y1, x2, y2);
}
public long getNativeConsumer() {
throw new InternalError("Not using a native peer");
}
});
return p2d;
}
......@@ -133,22 +140,7 @@ public class PiscesRenderingEngine extends RenderingEngine {
NormMode norm = (normalize) ?
((antialias) ? NormMode.ON_WITH_AA : NormMode.ON_NO_AA)
: NormMode.OFF;
strokeTo(src, at, bs, thin, norm, antialias,
new LineSink() {
public void moveTo(float x0, float y0) {
consumer.moveTo(x0, y0);
}
public void lineJoin() {}
public void lineTo(float x1, float y1) {
consumer.lineTo(x1, y1);
}
public void close() {
consumer.closePath();
}
public void end() {
consumer.pathDone();
}
});
strokeTo(src, at, bs, thin, norm, antialias, consumer);
}
void strokeTo(Shape src,
......@@ -157,7 +149,7 @@ public class PiscesRenderingEngine extends RenderingEngine {
boolean thin,
NormMode normalize,
boolean antialias,
LineSink lsink)
PathConsumer2D pc2d)
{
float lw;
if (thin) {
......@@ -178,7 +170,7 @@ public class PiscesRenderingEngine extends RenderingEngine {
bs.getMiterLimit(),
bs.getDashArray(),
bs.getDashPhase(),
lsink);
pc2d);
}
private float userSpaceLineWidth(AffineTransform at, float lw) {
......@@ -256,28 +248,113 @@ public class PiscesRenderingEngine extends RenderingEngine {
float miterlimit,
float dashes[],
float dashphase,
LineSink lsink)
PathConsumer2D pc2d)
{
float a00 = 1f, a01 = 0f, a10 = 0f, a11 = 1f;
// We use inat 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
// as needed. This would be faster but I do not think the complexity
// of working with both untransformed and transformed coordinates in
// the same code is worth it.
// However, if a path's width is constant after a transformation,
// we can skip all this untransforming.
// If normalization is off we save some transformations by not
// transforming the input to pisces. Instead, we apply the
// 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 outat = null;
PathIterator pi = null;
if (at != null && !at.isIdentity()) {
a00 = (float)at.getScaleX();
a01 = (float)at.getShearX();
a10 = (float)at.getShearY();
a11 = (float)at.getScaleY();
final double a = at.getScaleX();
final double b = at.getShearX();
final double c = at.getShearY();
final double d = at.getScaleY();
final double det = a * d - c * b;
if (Math.abs(det) <= 2 * Float.MIN_VALUE) {
// this rendering engine takes one dimensional curves and turns
// them into 2D shapes by giving them width.
// However, if everything is to be passed through a singular
// transformation, these 2D shapes will be squashed down to 1D
// 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
// of writing of this comment (September 16, 2010)). Actually,
// I'm not sure if the moveTo is necessary to avoid the SIGSEV
// but the pathDone is definitely needed.
pc2d.moveTo(0, 0);
pc2d.pathDone();
return;
}
// If the transform is a constant multiple of an orthogonal transformation
// then every length is just multiplied by a constant, so we just
// need to transform input paths to stroker and tell stroker
// the scaled width. This condition is satisfied if
// a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we
// leave a bit of room for error.
if (nearZero(a*b + c*d, 2) && nearZero(a*a+c*c - (b*b+d*d), 2)) {
double scale = Math.sqrt(a*a + c*c);
if (dashes != null) {
dashes = java.util.Arrays.copyOf(dashes, dashes.length);
for (int i = 0; i < dashes.length; i++) {
dashes[i] = (float)(scale * dashes[i]);
}
dashphase = (float)(scale * dashphase);
}
width = (float)(scale * width);
pi = src.getPathIterator(at);
if (normalize != NormMode.OFF) {
pi = new NormalizingPathIterator(pi, normalize);
}
// leave inat and outat null.
} 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();
}
pi = src.getPathIterator(at);
pi = new NormalizingPathIterator(pi, normalize);
} else {
pi = src.getPathIterator(null);
}
}
} else {
// either at is null or it's the identity. In either case
// we don't transform the path.
pi = src.getPathIterator(null);
if (normalize != NormMode.OFF) {
pi = new NormalizingPathIterator(pi, normalize);
}
}
lsink = new Stroker(lsink, width, caps, join, miterlimit, a00, a01, a10, a11);
pc2d = TransformingPathConsumer2D.transformConsumer(pc2d, outat);
pc2d = new Stroker(pc2d, width, caps, join, miterlimit);
if (dashes != null) {
lsink = new Dasher(lsink, dashes, dashphase, a00, a01, a10, a11);
}
PathIterator pi;
if (normalize != NormMode.OFF) {
pi = new FlatteningPathIterator(
new NormalizingPathIterator(src.getPathIterator(at), normalize),
defaultFlat);
} else {
pi = src.getPathIterator(at, defaultFlat);
pc2d = new Dasher(pc2d, dashes, dashphase);
}
pathTo(pi, lsink);
pc2d = TransformingPathConsumer2D.transformConsumer(pc2d, inat);
pathTo(pi, pc2d);
}
private static boolean nearZero(double num, int nulps) {
return Math.abs(num) < nulps * Math.ulp(num);
}
private static class NormalizingPathIterator implements PathIterator {
......@@ -337,10 +414,10 @@ public class PiscesRenderingEngine extends RenderingEngine {
}
// normalize endpoint
float x_adjust = (float)Math.floor(coords[lastCoord] + lval) + rval -
coords[lastCoord];
float y_adjust = (float)Math.floor(coords[lastCoord+1] + lval) + rval -
coords[lastCoord + 1];
float x_adjust = (float)Math.floor(coords[lastCoord] + lval) +
rval - coords[lastCoord];
float y_adjust = (float)Math.floor(coords[lastCoord+1] + lval) +
rval - coords[lastCoord + 1];
coords[lastCoord ] += x_adjust;
coords[lastCoord + 1] += y_adjust;
......@@ -393,27 +470,9 @@ public class PiscesRenderingEngine extends RenderingEngine {
}
}
void pathTo(PathIterator pi, LineSink lsink) {
float coords[] = new float[2];
while (!pi.isDone()) {
switch (pi.currentSegment(coords)) {
case PathIterator.SEG_MOVETO:
lsink.moveTo(coords[0], coords[1]);
break;
case PathIterator.SEG_LINETO:
lsink.lineJoin();
lsink.lineTo(coords[0], coords[1]);
break;
case PathIterator.SEG_CLOSE:
lsink.lineJoin();
lsink.close();
break;
default:
throw new InternalError("unknown flattened segment type");
}
pi.next();
}
lsink.end();
static void pathTo(PathIterator pi, PathConsumer2D pc2d) {
RenderingEngine.feedConsumer(pi, pc2d);
pc2d.pathDone();
}
/**
......@@ -471,32 +530,29 @@ public class PiscesRenderingEngine extends RenderingEngine {
boolean normalize,
int bbox[])
{
PiscesCache pc = PiscesCache.createInstance();
Renderer r;
NormMode norm = (normalize) ? NormMode.ON_WITH_AA : NormMode.OFF;
if (bs == null) {
PathIterator pi;
if (normalize) {
pi = new FlatteningPathIterator(
new NormalizingPathIterator(s.getPathIterator(at), norm),
defaultFlat);
pi = new NormalizingPathIterator(s.getPathIterator(at), norm);
} else {
pi = s.getPathIterator(at, defaultFlat);
pi = s.getPathIterator(at);
}
r = new Renderer(3, 3,
clip.getLoX(), clip.getLoY(),
clip.getWidth(), clip.getHeight(),
pi.getWindingRule(), pc);
pi.getWindingRule());
pathTo(pi, r);
} else {
r = new Renderer(3, 3,
clip.getLoX(), clip.getLoY(),
clip.getWidth(), clip.getHeight(),
PathIterator.WIND_NON_ZERO, pc);
PathIterator.WIND_NON_ZERO);
strokeTo(s, at, bs, thin, norm, true, r);
}
r.endRendering();
PiscesTileGenerator ptg = new PiscesTileGenerator(pc, r.MAX_AA_ALPHA);
PiscesTileGenerator ptg = new PiscesTileGenerator(r, r.MAX_AA_ALPHA);
ptg.getBbox(bbox);
return ptg;
}
......
......@@ -25,40 +25,54 @@
package sun.java2d.pisces;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import sun.java2d.pipe.AATileGenerator;
public class PiscesTileGenerator implements AATileGenerator {
public static final int TILE_SIZE = 32;
public 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
// that's not necessary. The way the renderer is, this map will
// never contain more than one element - the one with key 64, since
// we only do 8x8 supersampling.
private static final Map<Integer, byte[]> alphaMapsCache = new
ConcurrentHashMap<Integer, byte[]>();
PiscesCache cache;
int x, y;
int maxalpha;
final int maxalpha;
private final int maxTileAlphaSum;
// The alpha map used by this object (taken out of our map cache) to convert
// pixel coverage counts gotten from PiscesCache (which are in the range
// [0, maxalpha]) into alpha values, which are in [0,256).
byte alphaMap[];
public PiscesTileGenerator(PiscesCache cache, int maxalpha) {
this.cache = cache;
public PiscesTileGenerator(Renderer r, int maxalpha) {
this.cache = r.getCache();
this.x = cache.bboxX0;
this.y = cache.bboxY0;
this.alphaMap = getAlphaMap(maxalpha);
this.maxalpha = maxalpha;
this.maxTileAlphaSum = TILE_SIZE*TILE_SIZE*maxalpha;
}
static int prevMaxAlpha;
static byte prevAlphaMap[];
private static byte[] buildAlphaMap(int maxalpha) {
byte[] alMap = new byte[maxalpha+1];
int halfmaxalpha = maxalpha>>2;
for (int i = 0; i <= maxalpha; i++) {
alMap[i] = (byte) ((i * 255 + halfmaxalpha) / maxalpha);
}
return alMap;
}
public synchronized static byte[] getAlphaMap(int maxalpha) {
if (maxalpha != prevMaxAlpha) {
prevAlphaMap = new byte[maxalpha+300];
int halfmaxalpha = maxalpha>>2;
for (int i = 0; i <= maxalpha; i++) {
prevAlphaMap[i] = (byte) ((i * 255 + halfmaxalpha) / maxalpha);
}
for (int i = maxalpha; i < prevAlphaMap.length; i++) {
prevAlphaMap[i] = (byte) 255;
}
prevMaxAlpha = maxalpha;
public static byte[] getAlphaMap(int maxalpha) {
if (!alphaMapsCache.containsKey(maxalpha)) {
alphaMapsCache.put(maxalpha, buildAlphaMap(maxalpha));
}
return prevAlphaMap;
return alphaMapsCache.get(maxalpha);
}
public void getBbox(int bbox[]) {
......@@ -96,53 +110,24 @@ public class PiscesTileGenerator implements AATileGenerator {
* value for partial coverage of the tile
*/
public int getTypicalAlpha() {
if (true) return 0x80;
// Decode run-length encoded alpha mask data
// The data for row j begins at cache.rowOffsetsRLE[j]
// and is encoded as a set of 2-byte pairs (val, runLen)
// terminated by a (0, 0) pair.
int x0 = this.x;
int x1 = x0 + TILE_SIZE;
int y0 = this.y;
int y1 = y0 + TILE_SIZE;
if (x1 > cache.bboxX1) x1 = cache.bboxX1;
if (y1 > cache.bboxY1) y1 = cache.bboxY1;
y0 -= cache.bboxY0;
y1 -= cache.bboxY0;
int ret = -1;
for (int cy = y0; cy < y1; cy++) {
int pos = cache.rowOffsetsRLE[cy];
int cx = cache.minTouched[cy];
if (cx > x0) {
if (ret > 0) return 0x80;
ret = 0x00;
}
while (cx < x1) {
int runLen = cache.rowAARLE[pos + 1] & 0xff;
if (runLen == 0) {
if (ret > 0) return 0x80;
ret = 0x00;
break;
}
cx += runLen;
if (cx > x0) {
int val = cache.rowAARLE[pos] & 0xff;
if (ret != val) {
if (ret < 0) {
if (val != 0x00 && val != maxalpha) return 0x80;
ret = val;
} else {
return 0x80;
}
}
}
pos += 2;
}
}
return ret;
int al = cache.alphaSumInTile(x, y);
// Note: if we have a filled rectangle that doesn't end on a tile
// border, we could still return 0xff, even though al!=maxTileAlphaSum
// This is because if we return 0xff, our users will fill a rectangle
// starting at x,y that has width = Math.min(TILE_SIZE, bboxX1-x),
// and height min(TILE_SIZE,bboxY1-y), which is what should happen.
// However, to support this, we would have to use 2 Math.min's
// and 2 multiplications per tile, instead of just 2 multiplications
// to compute maxTileAlphaSum. The savings offered would probably
// not be worth it, considering how rare this case is.
// Note: I have not tested this, so in the future if it is determined
// that it is worth it, it should be implemented. Perhaps this method's
// interface should be changed to take arguments the width and height
// of the current tile. This would eliminate the 2 Math.min calls that
// would be needed here, since our caller needs to compute these 2
// values anyway.
return (al == 0x00 ? 0x00 :
(al == maxTileAlphaSum ? 0xff : 0x80));
}
/**
......@@ -179,22 +164,24 @@ public class PiscesTileGenerator implements AATileGenerator {
int idx = offset;
for (int cy = y0; cy < y1; cy++) {
int pos = cache.rowOffsetsRLE[cy];
int cx = cache.minTouched[cy];
int[] row = cache.rowAARLE[cy];
assert row != null;
int cx = cache.minTouched(cy);
if (cx > x1) cx = x1;
if (cx > x0) {
//System.out.println("L["+(cx-x0)+"]");
for (int i = x0; i < cx; i++) {
tile[idx++] = 0x00;
}
for (int i = x0; i < cx; i++) {
tile[idx++] = 0x00;
}
while (cx < x1) {
int pos = 2;
while (cx < x1 && pos < row[1]) {
byte val;
int runLen = 0;
assert row[1] > 2;
try {
val = alphaMap[cache.rowAARLE[pos] & 0xff];
runLen = cache.rowAARLE[pos + 1] & 0xff;
val = alphaMap[row[pos]];
runLen = row[pos + 1];
assert runLen > 0;
} catch (RuntimeException e0) {
System.out.println("maxalpha = "+maxalpha);
System.out.println("tile["+x0+", "+y0+
......@@ -202,14 +189,12 @@ public class PiscesTileGenerator implements AATileGenerator {
System.out.println("cx = "+cx+", cy = "+cy);
System.out.println("idx = "+idx+", pos = "+pos);
System.out.println("len = "+runLen);
cache.print(System.out);
System.out.print(cache.toString());
e0.printStackTrace();
System.exit(1);
return;
}
if (runLen == 0) {
break;
}
int rx0 = cx;
cx += runLen;
int rx1 = cx;
......@@ -228,7 +213,7 @@ public class PiscesTileGenerator implements AATileGenerator {
System.out.println("idx = "+idx+", pos = "+pos);
System.out.println("rx0 = "+rx0+", rx1 = "+rx1);
System.out.println("len = "+runLen);
cache.print(System.out);
System.out.print(cache.toString());
e.printStackTrace();
System.exit(1);
return;
......@@ -265,4 +250,4 @@ public class PiscesTileGenerator implements AATileGenerator {
* No further calls will be made on this instance.
*/
public void dispose() {}
}
}
\ No newline at end of file
/*
* Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.java2d.pisces;
import sun.awt.geom.PathConsumer2D;
import java.awt.geom.AffineTransform;
public class TransformingPathConsumer2D {
public static PathConsumer2D
transformConsumer(PathConsumer2D out,
AffineTransform at)
{
if (at == null) {
return out;
}
float Mxx = (float) at.getScaleX();
float Mxy = (float) at.getShearX();
float Mxt = (float) at.getTranslateX();
float Myx = (float) at.getShearY();
float Myy = (float) at.getScaleY();
float Myt = (float) at.getTranslateY();
if (Mxy == 0f && Myx == 0f) {
if (Mxx == 1f && Myy == 1f) {
if (Mxt == 0f && Myt == 0f) {
return out;
} else {
return new TranslateFilter(out, Mxt, Myt);
}
} else {
return new ScaleFilter(out, Mxx, Myy, Mxt, Myt);
}
} else {
return new TransformFilter(out, Mxx, Mxy, Mxt, Myx, Myy, Myt);
}
}
static class TranslateFilter implements PathConsumer2D {
PathConsumer2D out;
float tx;
float ty;
TranslateFilter(PathConsumer2D out,
float tx, float ty)
{
this.out = out;
this.tx = tx;
this.ty = ty;
}
public void moveTo(float x0, float y0) {
out.moveTo(x0 + tx, y0 + ty);
}
public void lineTo(float x1, float y1) {
out.lineTo(x1 + tx, y1 + ty);
}
public void quadTo(float x1, float y1,
float x2, float y2)
{
out.quadTo(x1 + tx, y1 + ty,
x2 + tx, y2 + ty);
}
public void curveTo(float x1, float y1,
float x2, float y2,
float x3, float y3)
{
out.curveTo(x1 + tx, y1 + ty,
x2 + tx, y2 + ty,
x3 + tx, y3 + ty);
}
public void closePath() {
out.closePath();
}
public void pathDone() {
out.pathDone();
}
public long getNativeConsumer() {
return 0;
}
}
static class ScaleFilter implements PathConsumer2D {
PathConsumer2D out;
float sx;
float sy;
float tx;
float ty;
ScaleFilter(PathConsumer2D out,
float sx, float sy, float tx, float ty)
{
this.out = out;
this.sx = sx;
this.sy = sy;
this.tx = tx;
this.ty = ty;
}
public void moveTo(float x0, float y0) {
out.moveTo(x0 * sx + tx, y0 * sy + ty);
}
public void lineTo(float x1, float y1) {
out.lineTo(x1 * sx + tx, y1 * sy + ty);
}
public void quadTo(float x1, float y1,
float x2, float y2)
{
out.quadTo(x1 * sx + tx, y1 * sy + ty,
x2 * sx + tx, y2 * sy + ty);
}
public void curveTo(float x1, float y1,
float x2, float y2,
float x3, float y3)
{
out.curveTo(x1 * sx + tx, y1 * sy + ty,
x2 * sx + tx, y2 * sy + ty,
x3 * sx + tx, y3 * sy + ty);
}
public void closePath() {
out.closePath();
}
public void pathDone() {
out.pathDone();
}
public long getNativeConsumer() {
return 0;
}
}
static class TransformFilter implements PathConsumer2D {
PathConsumer2D out;
float Mxx;
float Mxy;
float Mxt;
float Myx;
float Myy;
float Myt;
TransformFilter(PathConsumer2D out,
float Mxx, float Mxy, float Mxt,
float Myx, float Myy, float Myt)
{
this.out = out;
this.Mxx = Mxx;
this.Mxy = Mxy;
this.Mxt = Mxt;
this.Myx = Myx;
this.Myy = Myy;
this.Myt = Myt;
}
public void moveTo(float x0, float y0) {
out.moveTo(x0 * Mxx + y0 * Mxy + Mxt,
x0 * Myx + y0 * Myy + Myt);
}
public void lineTo(float x1, float y1) {
out.lineTo(x1 * Mxx + y1 * Mxy + Mxt,
x1 * Myx + y1 * Myy + Myt);
}
public void quadTo(float x1, float y1,
float x2, float y2)
{
out.quadTo(x1 * Mxx + y1 * Mxy + Mxt,
x1 * Myx + y1 * Myy + Myt,
x2 * Mxx + y2 * Mxy + Mxt,
x2 * Myx + y2 * Myy + Myt);
}
public void curveTo(float x1, float y1,
float x2, float y2,
float x3, float y3)
{
out.curveTo(x1 * Mxx + y1 * Mxy + Mxt,
x1 * Myx + y1 * Myy + Myt,
x2 * Mxx + y2 * Mxy + Mxt,
x2 * Myx + y2 * Myy + Myt,
x3 * Mxx + y3 * Mxy + Mxt,
x3 * Myx + y3 * Myy + Myt);
}
public void closePath() {
out.closePath();
}
public void pathDone() {
out.pathDone();
}
public long getNativeConsumer() {
return 0;
}
}
}
......@@ -116,14 +116,26 @@
jint Y0 = (fY0) >> MDP_PREC; \
jint X1 = (fX1) >> MDP_PREC; \
jint Y1 = (fY1) >> MDP_PREC; \
/* Handling lines having just one pixel */\
if (((X0^X1) | (Y0^Y1)) == 0) { \
if (checkBounds && \
(hnd->dhnd->yMin > Y0 || \
hnd->dhnd->yMax <= Y0 || \
hnd->dhnd->xMin > X0 || \
hnd->dhnd->xMax <= X0)) break; \
jint res; \
\
/* Checking bounds and clipping if necessary */ \
if (checkBounds) { \
TESTANDCLIP(hnd->dhnd->yMin, hnd->dhnd->yMax, Y0, X0, Y1, X1, \
jint, res); \
if (res == CRES_INVISIBLE) break; \
TESTANDCLIP(hnd->dhnd->yMin, hnd->dhnd->yMax, Y1, X1, Y0, X0, \
jint, res); \
if (res == CRES_INVISIBLE) break; \
TESTANDCLIP(hnd->dhnd->xMin, hnd->dhnd->xMax, X0, Y0, X1, Y1, \
jint, res); \
if (res == CRES_INVISIBLE) break; \
TESTANDCLIP(hnd->dhnd->xMin, hnd->dhnd->xMax, X1, Y1, X0, Y0, \
jint, res); \
if (res == CRES_INVISIBLE) break; \
} \
\
/* Handling lines having just one pixel */ \
if (((X0^X1) | (Y0^Y1)) == 0) { \
if (pixelInfo[0] == 0) { \
pixelInfo[0] = 1; \
pixelInfo[1] = X0; \
......@@ -140,18 +152,11 @@
break; \
} \
\
if (!checkBounds || \
(hnd->dhnd->yMin <= Y0 && \
hnd->dhnd->yMax > Y0 && \
hnd->dhnd->xMin <= X0 && \
hnd->dhnd->xMax > X0)) \
if (pixelInfo[0] && \
((pixelInfo[1] == X0 && pixelInfo[2] == Y0) || \
(pixelInfo[3] == X0 && pixelInfo[4] == Y0))) \
{ \
if (pixelInfo[0] && \
((pixelInfo[1] == X0 && pixelInfo[2] == Y0) || \
(pixelInfo[3] == X0 && pixelInfo[4] == Y0))) \
{ \
hnd->dhnd->pDrawPixel(hnd->dhnd, X0, Y0); \
} \
hnd->dhnd->pDrawPixel(hnd->dhnd, X0, Y0); \
} \
\
hnd->dhnd->pDrawLine(hnd->dhnd, X0, Y0, X1, Y1); \
......@@ -170,14 +175,6 @@
if ((pixelInfo[1] == X1 && pixelInfo[2] == Y1) || \
(pixelInfo[3] == X1 && pixelInfo[4] == Y1)) \
{ \
if (checkBounds && \
(hnd->dhnd->yMin > Y1 || \
hnd->dhnd->yMax <= Y1 || \
hnd->dhnd->xMin > X1 || \
hnd->dhnd->xMax <= X1)) { \
break; \
} \
\
hnd->dhnd->pDrawPixel(hnd->dhnd, X1, Y1); \
} \
pixelInfo[3] = X1; \
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册