8191814: Marlin rasterizer spends time computing geometry for stroked segments that do not intersect the clip
Upgrade to Marlin 0.8.2 providing efficient path clipping (Stroker and Filler) Reviewed-by: prr, serb
This commit is contained in:
parent
ee9c644643
commit
5f530a331b
@ -56,12 +56,16 @@ final class Curve {
|
||||
float x3, float y3,
|
||||
float x4, float y4)
|
||||
{
|
||||
ax = 3.0f * (x2 - x3) + x4 - x1;
|
||||
ay = 3.0f * (y2 - y3) + y4 - y1;
|
||||
bx = 3.0f * (x1 - 2.0f * x2 + x3);
|
||||
by = 3.0f * (y1 - 2.0f * y2 + y3);
|
||||
cx = 3.0f * (x2 - x1);
|
||||
cy = 3.0f * (y2 - y1);
|
||||
final float dx32 = 3.0f * (x3 - x2);
|
||||
final float dy32 = 3.0f * (y3 - y2);
|
||||
final float dx21 = 3.0f * (x2 - x1);
|
||||
final float dy21 = 3.0f * (y2 - y1);
|
||||
ax = (x4 - x1) - dx32;
|
||||
ay = (y4 - y1) - dy32;
|
||||
bx = (dx32 - dx21);
|
||||
by = (dy32 - dy21);
|
||||
cx = dx21;
|
||||
cy = dy21;
|
||||
dx = x1;
|
||||
dy = y1;
|
||||
dax = 3.0f * ax; day = 3.0f * ay;
|
||||
@ -72,11 +76,13 @@ final class Curve {
|
||||
float x2, float y2,
|
||||
float x3, float y3)
|
||||
{
|
||||
final float dx21 = (x2 - x1);
|
||||
final float dy21 = (y2 - y1);
|
||||
ax = 0.0f; ay = 0.0f;
|
||||
bx = x1 - 2.0f * x2 + x3;
|
||||
by = y1 - 2.0f * y2 + y3;
|
||||
cx = 2.0f * (x2 - x1);
|
||||
cy = 2.0f * (y2 - y1);
|
||||
bx = (x3 - x2) - dx21;
|
||||
by = (y3 - y2) - dy21;
|
||||
cx = 2.0f * dx21;
|
||||
cy = 2.0f * dy21;
|
||||
dx = x1;
|
||||
dy = y1;
|
||||
dax = 0.0f; day = 0.0f;
|
||||
|
@ -56,12 +56,16 @@ final class DCurve {
|
||||
double x3, double y3,
|
||||
double x4, double y4)
|
||||
{
|
||||
ax = 3.0d * (x2 - x3) + x4 - x1;
|
||||
ay = 3.0d * (y2 - y3) + y4 - y1;
|
||||
bx = 3.0d * (x1 - 2.0d * x2 + x3);
|
||||
by = 3.0d * (y1 - 2.0d * y2 + y3);
|
||||
cx = 3.0d * (x2 - x1);
|
||||
cy = 3.0d * (y2 - y1);
|
||||
final double dx32 = 3.0d * (x3 - x2);
|
||||
final double dy32 = 3.0d * (y3 - y2);
|
||||
final double dx21 = 3.0d * (x2 - x1);
|
||||
final double dy21 = 3.0d * (y2 - y1);
|
||||
ax = (x4 - x1) - dx32;
|
||||
ay = (y4 - y1) - dy32;
|
||||
bx = (dx32 - dx21);
|
||||
by = (dy32 - dy21);
|
||||
cx = dx21;
|
||||
cy = dy21;
|
||||
dx = x1;
|
||||
dy = y1;
|
||||
dax = 3.0d * ax; day = 3.0d * ay;
|
||||
@ -72,11 +76,13 @@ final class DCurve {
|
||||
double x2, double y2,
|
||||
double x3, double y3)
|
||||
{
|
||||
final double dx21 = (x2 - x1);
|
||||
final double dy21 = (y2 - y1);
|
||||
ax = 0.0d; ay = 0.0d;
|
||||
bx = x1 - 2.0d * x2 + x3;
|
||||
by = y1 - 2.0d * y2 + y3;
|
||||
cx = 2.0d * (x2 - x1);
|
||||
cy = 2.0d * (y2 - y1);
|
||||
bx = (x3 - x2) - dx21;
|
||||
by = (y3 - y2) - dy21;
|
||||
cx = 2.0d * dx21;
|
||||
cy = 2.0d * dy21;
|
||||
dx = x1;
|
||||
dy = y1;
|
||||
dax = 0.0d; day = 0.0d;
|
||||
|
@ -137,7 +137,7 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
|
||||
dashOn = !dashOn;
|
||||
}
|
||||
}
|
||||
} else if (phase > 0) {
|
||||
} else if (phase > 0.0d) {
|
||||
if (cycles >= MAX_CYCLES) {
|
||||
phase = 0.0d;
|
||||
} else {
|
||||
@ -157,12 +157,13 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
|
||||
|
||||
this.dash = dash;
|
||||
this.dashLen = dashLen;
|
||||
this.startPhase = this.phase = phase;
|
||||
this.phase = phase;
|
||||
this.startPhase = phase;
|
||||
this.startDashOn = dashOn;
|
||||
this.startIdx = sidx;
|
||||
this.starting = true;
|
||||
needsMoveTo = false;
|
||||
firstSegidx = 0;
|
||||
this.needsMoveTo = false;
|
||||
this.firstSegidx = 0;
|
||||
|
||||
this.recycleDashes = recycleDashes;
|
||||
|
||||
@ -201,8 +202,8 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveTo(double x0, double y0) {
|
||||
if (firstSegidx > 0) {
|
||||
public void moveTo(final double x0, final double y0) {
|
||||
if (firstSegidx != 0) {
|
||||
out.moveTo(sx, sy);
|
||||
emitFirstSegments();
|
||||
}
|
||||
@ -210,8 +211,10 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
|
||||
this.idx = startIdx;
|
||||
this.dashOn = this.startDashOn;
|
||||
this.phase = this.startPhase;
|
||||
this.sx = this.x0 = x0;
|
||||
this.sy = this.y0 = y0;
|
||||
this.sx = x0;
|
||||
this.sy = y0;
|
||||
this.x0 = x0;
|
||||
this.y0 = y0;
|
||||
this.starting = true;
|
||||
}
|
||||
|
||||
@ -236,7 +239,7 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
|
||||
private void emitFirstSegments() {
|
||||
final double[] fSegBuf = firstSegmentsBuffer;
|
||||
|
||||
for (int i = 0; i < firstSegidx; ) {
|
||||
for (int i = 0, len = firstSegidx; i < len; ) {
|
||||
int type = (int)fSegBuf[i];
|
||||
emitSeg(fSegBuf, i + 1, type);
|
||||
i += (type - 1);
|
||||
@ -251,48 +254,59 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
|
||||
private int firstSegidx;
|
||||
|
||||
// precondition: pts must be in relative coordinates (relative to x0,y0)
|
||||
private void goTo(double[] pts, int off, final int type) {
|
||||
double x = pts[off + type - 4];
|
||||
double y = pts[off + type - 3];
|
||||
if (dashOn) {
|
||||
private void goTo(final double[] pts, final int off, final int type,
|
||||
final boolean on)
|
||||
{
|
||||
final int index = off + type;
|
||||
final double x = pts[index - 4];
|
||||
final double y = pts[index - 3];
|
||||
|
||||
if (on) {
|
||||
if (starting) {
|
||||
int len = type - 1; // - 2 + 1
|
||||
int segIdx = firstSegidx;
|
||||
double[] buf = firstSegmentsBuffer;
|
||||
if (segIdx + len > buf.length) {
|
||||
if (DO_STATS) {
|
||||
rdrCtx.stats.stat_array_dasher_firstSegmentsBuffer
|
||||
.add(segIdx + len);
|
||||
}
|
||||
firstSegmentsBuffer = buf
|
||||
= firstSegmentsBuffer_ref.widenArray(buf, segIdx,
|
||||
segIdx + len);
|
||||
}
|
||||
buf[segIdx++] = type;
|
||||
len--;
|
||||
// small arraycopy (2, 4 or 6) but with offset:
|
||||
System.arraycopy(pts, off, buf, segIdx, len);
|
||||
segIdx += len;
|
||||
firstSegidx = segIdx;
|
||||
goTo_starting(pts, off, type);
|
||||
} else {
|
||||
if (needsMoveTo) {
|
||||
out.moveTo(x0, y0);
|
||||
needsMoveTo = false;
|
||||
out.moveTo(x0, y0);
|
||||
}
|
||||
emitSeg(pts, off, type);
|
||||
}
|
||||
} else {
|
||||
starting = false;
|
||||
if (starting) {
|
||||
// low probability test (hotspot)
|
||||
starting = false;
|
||||
}
|
||||
needsMoveTo = true;
|
||||
}
|
||||
this.x0 = x;
|
||||
this.y0 = y;
|
||||
}
|
||||
|
||||
private void goTo_starting(final double[] pts, final int off, final int type) {
|
||||
int len = type - 1; // - 2 + 1
|
||||
int segIdx = firstSegidx;
|
||||
double[] buf = firstSegmentsBuffer;
|
||||
|
||||
if (segIdx + len > buf.length) {
|
||||
if (DO_STATS) {
|
||||
rdrCtx.stats.stat_array_dasher_firstSegmentsBuffer
|
||||
.add(segIdx + len);
|
||||
}
|
||||
firstSegmentsBuffer = buf
|
||||
= firstSegmentsBuffer_ref.widenArray(buf, segIdx,
|
||||
segIdx + len);
|
||||
}
|
||||
buf[segIdx++] = type;
|
||||
len--;
|
||||
// small arraycopy (2, 4 or 6) but with offset:
|
||||
System.arraycopy(pts, off, buf, segIdx, len);
|
||||
firstSegidx = segIdx + len;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lineTo(double x1, double y1) {
|
||||
double dx = x1 - x0;
|
||||
double dy = y1 - y0;
|
||||
public void lineTo(final double x1, final double y1) {
|
||||
final double dx = x1 - x0;
|
||||
final double dy = y1 - y0;
|
||||
|
||||
double len = dx*dx + dy*dy;
|
||||
if (len == 0.0d) {
|
||||
@ -307,48 +321,61 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
|
||||
|
||||
final double[] _curCurvepts = curCurvepts;
|
||||
final double[] _dash = dash;
|
||||
final int _dashLen = this.dashLen;
|
||||
|
||||
int _idx = idx;
|
||||
boolean _dashOn = dashOn;
|
||||
double _phase = phase;
|
||||
|
||||
double leftInThisDashSegment;
|
||||
double dashdx, dashdy, p;
|
||||
double d, dashdx, dashdy, p;
|
||||
|
||||
while (true) {
|
||||
leftInThisDashSegment = _dash[idx] - phase;
|
||||
d = _dash[_idx];
|
||||
leftInThisDashSegment = d - _phase;
|
||||
|
||||
if (len <= leftInThisDashSegment) {
|
||||
_curCurvepts[0] = x1;
|
||||
_curCurvepts[1] = y1;
|
||||
goTo(_curCurvepts, 0, 4);
|
||||
|
||||
goTo(_curCurvepts, 0, 4, _dashOn);
|
||||
|
||||
// Advance phase within current dash segment
|
||||
phase += len;
|
||||
_phase += len;
|
||||
|
||||
// TODO: compare double values using epsilon:
|
||||
if (len == leftInThisDashSegment) {
|
||||
phase = 0.0d;
|
||||
idx = (idx + 1) % dashLen;
|
||||
dashOn = !dashOn;
|
||||
_phase = 0.0d;
|
||||
_idx = (_idx + 1) % _dashLen;
|
||||
_dashOn = !_dashOn;
|
||||
}
|
||||
|
||||
// Save local state:
|
||||
idx = _idx;
|
||||
dashOn = _dashOn;
|
||||
phase = _phase;
|
||||
return;
|
||||
}
|
||||
|
||||
dashdx = _dash[idx] * cx;
|
||||
dashdy = _dash[idx] * cy;
|
||||
dashdx = d * cx;
|
||||
dashdy = d * cy;
|
||||
|
||||
if (phase == 0.0d) {
|
||||
if (_phase == 0.0d) {
|
||||
_curCurvepts[0] = x0 + dashdx;
|
||||
_curCurvepts[1] = y0 + dashdy;
|
||||
} else {
|
||||
p = leftInThisDashSegment / _dash[idx];
|
||||
p = leftInThisDashSegment / d;
|
||||
_curCurvepts[0] = x0 + p * dashdx;
|
||||
_curCurvepts[1] = y0 + p * dashdy;
|
||||
}
|
||||
|
||||
goTo(_curCurvepts, 0, 4);
|
||||
goTo(_curCurvepts, 0, 4, _dashOn);
|
||||
|
||||
len -= leftInThisDashSegment;
|
||||
// Advance to next dash segment
|
||||
idx = (idx + 1) % dashLen;
|
||||
dashOn = !dashOn;
|
||||
phase = 0.0d;
|
||||
_idx = (_idx + 1) % _dashLen;
|
||||
_dashOn = !_dashOn;
|
||||
_phase = 0.0d;
|
||||
}
|
||||
}
|
||||
|
||||
@ -357,43 +384,59 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
|
||||
|
||||
// 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) {
|
||||
private void somethingTo(final int type) {
|
||||
if (pointCurve(curCurvepts, type)) {
|
||||
return;
|
||||
}
|
||||
li.initializeIterationOnCurve(curCurvepts, type);
|
||||
final LengthIterator _li = li;
|
||||
final double[] _curCurvepts = curCurvepts;
|
||||
final double[] _dash = dash;
|
||||
final int _dashLen = this.dashLen;
|
||||
|
||||
_li.initializeIterationOnCurve(_curCurvepts, type);
|
||||
|
||||
int _idx = idx;
|
||||
boolean _dashOn = dashOn;
|
||||
double _phase = phase;
|
||||
|
||||
// initially the current curve is at curCurvepts[0...type]
|
||||
int curCurveoff = 0;
|
||||
double lastSplitT = 0.0d;
|
||||
double t;
|
||||
double leftInThisDashSegment = dash[idx] - phase;
|
||||
double leftInThisDashSegment = _dash[_idx] - _phase;
|
||||
|
||||
while ((t = li.next(leftInThisDashSegment)) < 1.0d) {
|
||||
while ((t = _li.next(leftInThisDashSegment)) < 1.0d) {
|
||||
if (t != 0.0d) {
|
||||
DHelpers.subdivideAt((t - lastSplitT) / (1.0d - lastSplitT),
|
||||
curCurvepts, curCurveoff,
|
||||
curCurvepts, 0,
|
||||
curCurvepts, type, type);
|
||||
_curCurvepts, curCurveoff,
|
||||
_curCurvepts, 0,
|
||||
_curCurvepts, type, type);
|
||||
lastSplitT = t;
|
||||
goTo(curCurvepts, 2, type);
|
||||
goTo(_curCurvepts, 2, type, _dashOn);
|
||||
curCurveoff = type;
|
||||
}
|
||||
// Advance to next dash segment
|
||||
idx = (idx + 1) % dashLen;
|
||||
dashOn = !dashOn;
|
||||
phase = 0.0d;
|
||||
leftInThisDashSegment = dash[idx];
|
||||
_idx = (_idx + 1) % _dashLen;
|
||||
_dashOn = !_dashOn;
|
||||
_phase = 0.0d;
|
||||
leftInThisDashSegment = _dash[_idx];
|
||||
}
|
||||
goTo(curCurvepts, curCurveoff+2, type);
|
||||
phase += li.lastSegLen();
|
||||
if (phase >= dash[idx]) {
|
||||
phase = 0.0d;
|
||||
idx = (idx + 1) % dashLen;
|
||||
dashOn = !dashOn;
|
||||
|
||||
goTo(_curCurvepts, curCurveoff + 2, type, _dashOn);
|
||||
|
||||
_phase += _li.lastSegLen();
|
||||
if (_phase >= _dash[_idx]) {
|
||||
_phase = 0.0d;
|
||||
_idx = (_idx + 1) % _dashLen;
|
||||
_dashOn = !_dashOn;
|
||||
}
|
||||
// Save local state:
|
||||
idx = _idx;
|
||||
dashOn = _dashOn;
|
||||
phase = _phase;
|
||||
|
||||
// reset LengthIterator:
|
||||
li.reset();
|
||||
_li.reset();
|
||||
}
|
||||
|
||||
private static boolean pointCurve(double[] curve, int type) {
|
||||
@ -419,7 +462,7 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
|
||||
// tree; however, the trees we are interested in have the property that
|
||||
// every non leaf node has exactly 2 children
|
||||
static final class LengthIterator {
|
||||
private enum Side {LEFT, RIGHT};
|
||||
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
|
||||
@ -669,22 +712,23 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
|
||||
// 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 double onLeaf() {
|
||||
double[] curve = recCurveStack[recLevel];
|
||||
final double[] curve = recCurveStack[recLevel];
|
||||
final int _curveType = curveType;
|
||||
double polyLen = 0.0d;
|
||||
|
||||
double x0 = curve[0], y0 = curve[1];
|
||||
for (int i = 2; i < curveType; i += 2) {
|
||||
for (int i = 2; i < _curveType; i += 2) {
|
||||
final double x1 = curve[i], y1 = curve[i+1];
|
||||
final double len = DHelpers.linelen(x0, y0, x1, y1);
|
||||
polyLen += len;
|
||||
curLeafCtrlPolyLengths[i/2 - 1] = len;
|
||||
curLeafCtrlPolyLengths[(i >> 1) - 1] = len;
|
||||
x0 = x1;
|
||||
y0 = y1;
|
||||
}
|
||||
|
||||
final double lineLen = DHelpers.linelen(curve[0], curve[1],
|
||||
curve[curveType-2],
|
||||
curve[curveType-1]);
|
||||
curve[_curveType-2],
|
||||
curve[_curveType-1]);
|
||||
if ((polyLen - lineLen) < ERR || recLevel == REC_LIMIT) {
|
||||
return (polyLen + lineLen) / 2.0d;
|
||||
}
|
||||
@ -693,9 +737,9 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void curveTo(double x1, double y1,
|
||||
double x2, double y2,
|
||||
double x3, double y3)
|
||||
public void curveTo(final double x1, final double y1,
|
||||
final double x2, final double y2,
|
||||
final double x3, final double y3)
|
||||
{
|
||||
final double[] _curCurvepts = curCurvepts;
|
||||
_curCurvepts[0] = x0; _curCurvepts[1] = y0;
|
||||
@ -706,7 +750,9 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void quadTo(double x1, double y1, double x2, double y2) {
|
||||
public void quadTo(final double x1, final double y1,
|
||||
final double x2, final double y2)
|
||||
{
|
||||
final double[] _curCurvepts = curCurvepts;
|
||||
_curCurvepts[0] = x0; _curCurvepts[1] = y0;
|
||||
_curCurvepts[2] = x1; _curCurvepts[3] = y1;
|
||||
@ -717,7 +763,7 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
|
||||
@Override
|
||||
public void closePath() {
|
||||
lineTo(sx, sy);
|
||||
if (firstSegidx > 0) {
|
||||
if (firstSegidx != 0) {
|
||||
if (!dashOn || needsMoveTo) {
|
||||
out.moveTo(sx, sy);
|
||||
}
|
||||
@ -728,7 +774,7 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
|
||||
|
||||
@Override
|
||||
public void pathDone() {
|
||||
if (firstSegidx > 0) {
|
||||
if (firstSegidx != 0) {
|
||||
out.moveTo(sx, sy);
|
||||
emitFirstSegments();
|
||||
}
|
||||
|
@ -26,10 +26,9 @@
|
||||
package sun.java2d.marlin;
|
||||
|
||||
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;
|
||||
import java.util.Arrays;
|
||||
import sun.java2d.marlin.stats.Histogram;
|
||||
import sun.java2d.marlin.stats.StatLong;
|
||||
|
||||
final class DHelpers implements MarlinConst {
|
||||
|
||||
@ -115,17 +114,17 @@ final class DHelpers implements MarlinConst {
|
||||
int num;
|
||||
if (D < 0.0d) {
|
||||
// see: http://en.wikipedia.org/wiki/Cubic_function#Trigonometric_.28and_hyperbolic.29_method
|
||||
final double phi = (1.0d/3.0d) * acos(-q / sqrt(-cb_p));
|
||||
final double t = 2.0d * sqrt(-p);
|
||||
final double phi = (1.0d/3.0d) * Math.acos(-q / Math.sqrt(-cb_p));
|
||||
final double t = 2.0d * Math.sqrt(-p);
|
||||
|
||||
pts[ off+0 ] = ( t * cos(phi));
|
||||
pts[ off+1 ] = (-t * cos(phi + (PI / 3.0d)));
|
||||
pts[ off+2 ] = (-t * cos(phi - (PI / 3.0d)));
|
||||
pts[ off+0 ] = ( t * Math.cos(phi));
|
||||
pts[ off+1 ] = (-t * Math.cos(phi + (PI / 3.0d)));
|
||||
pts[ off+2 ] = (-t * Math.cos(phi - (PI / 3.0d)));
|
||||
num = 3;
|
||||
} else {
|
||||
final double sqrt_D = sqrt(D);
|
||||
final double u = cbrt(sqrt_D - q);
|
||||
final double v = - cbrt(sqrt_D + q);
|
||||
final double sqrt_D = Math.sqrt(D);
|
||||
final double u = Math.cbrt(sqrt_D - q);
|
||||
final double v = - Math.cbrt(sqrt_D + q);
|
||||
|
||||
pts[ off ] = (u + v);
|
||||
num = 1;
|
||||
@ -171,15 +170,6 @@ final class DHelpers implements MarlinConst {
|
||||
return ret;
|
||||
}
|
||||
|
||||
static double polyLineLength(double[] poly, final int off, final int nCoords) {
|
||||
assert nCoords % 2 == 0 && poly.length >= off + nCoords : "";
|
||||
double acc = 0.0d;
|
||||
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 double linelen(double x1, double y1, double x2, double y2) {
|
||||
final double dx = x2 - x1;
|
||||
final double dy = y2 - y1;
|
||||
@ -433,4 +423,388 @@ final class DHelpers implements MarlinConst {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// From sun.java2d.loops.GeneralRenderer:
|
||||
|
||||
static int outcode(final double x, final double y,
|
||||
final double[] clipRect)
|
||||
{
|
||||
int code;
|
||||
if (y < clipRect[0]) {
|
||||
code = OUTCODE_TOP;
|
||||
} else if (y >= clipRect[1]) {
|
||||
code = OUTCODE_BOTTOM;
|
||||
} else {
|
||||
code = 0;
|
||||
}
|
||||
if (x < clipRect[2]) {
|
||||
code |= OUTCODE_LEFT;
|
||||
} else if (x >= clipRect[3]) {
|
||||
code |= OUTCODE_RIGHT;
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
// a stack of polynomial curves where each curve shares endpoints with
|
||||
// adjacent ones.
|
||||
static final class PolyStack {
|
||||
private static final byte TYPE_LINETO = (byte) 0;
|
||||
private static final byte TYPE_QUADTO = (byte) 1;
|
||||
private static final byte TYPE_CUBICTO = (byte) 2;
|
||||
|
||||
// curves capacity = edges count (8192) = edges x 2 (coords)
|
||||
private static final int INITIAL_CURVES_COUNT = INITIAL_EDGES_COUNT << 1;
|
||||
|
||||
// types capacity = edges count (4096)
|
||||
private static final int INITIAL_TYPES_COUNT = INITIAL_EDGES_COUNT;
|
||||
|
||||
double[] curves;
|
||||
int end;
|
||||
byte[] curveTypes;
|
||||
int numCurves;
|
||||
|
||||
// curves ref (dirty)
|
||||
final DoubleArrayCache.Reference curves_ref;
|
||||
// curveTypes ref (dirty)
|
||||
final ByteArrayCache.Reference curveTypes_ref;
|
||||
|
||||
// used marks (stats only)
|
||||
int curveTypesUseMark;
|
||||
int curvesUseMark;
|
||||
|
||||
private final StatLong stat_polystack_types;
|
||||
private final StatLong stat_polystack_curves;
|
||||
private final Histogram hist_polystack_curves;
|
||||
private final StatLong stat_array_polystack_curves;
|
||||
private final StatLong stat_array_polystack_curveTypes;
|
||||
|
||||
PolyStack(final DRendererContext rdrCtx) {
|
||||
this(rdrCtx, null, null, null, null, null);
|
||||
}
|
||||
|
||||
PolyStack(final DRendererContext rdrCtx,
|
||||
final StatLong stat_polystack_types,
|
||||
final StatLong stat_polystack_curves,
|
||||
final Histogram hist_polystack_curves,
|
||||
final StatLong stat_array_polystack_curves,
|
||||
final StatLong stat_array_polystack_curveTypes)
|
||||
{
|
||||
curves_ref = rdrCtx.newDirtyDoubleArrayRef(INITIAL_CURVES_COUNT); // 32K
|
||||
curves = curves_ref.initial;
|
||||
|
||||
curveTypes_ref = rdrCtx.newDirtyByteArrayRef(INITIAL_TYPES_COUNT); // 4K
|
||||
curveTypes = curveTypes_ref.initial;
|
||||
numCurves = 0;
|
||||
end = 0;
|
||||
|
||||
if (DO_STATS) {
|
||||
curveTypesUseMark = 0;
|
||||
curvesUseMark = 0;
|
||||
}
|
||||
this.stat_polystack_types = stat_polystack_types;
|
||||
this.stat_polystack_curves = stat_polystack_curves;
|
||||
this.hist_polystack_curves = hist_polystack_curves;
|
||||
this.stat_array_polystack_curves = stat_array_polystack_curves;
|
||||
this.stat_array_polystack_curveTypes = stat_array_polystack_curveTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes this PolyStack:
|
||||
* clean up before reusing this instance
|
||||
*/
|
||||
void dispose() {
|
||||
end = 0;
|
||||
numCurves = 0;
|
||||
|
||||
if (DO_STATS) {
|
||||
stat_polystack_types.add(curveTypesUseMark);
|
||||
stat_polystack_curves.add(curvesUseMark);
|
||||
hist_polystack_curves.add(curvesUseMark);
|
||||
|
||||
// reset marks
|
||||
curveTypesUseMark = 0;
|
||||
curvesUseMark = 0;
|
||||
}
|
||||
|
||||
// Return arrays:
|
||||
// curves and curveTypes are kept dirty
|
||||
curves = curves_ref.putArray(curves);
|
||||
curveTypes = curveTypes_ref.putArray(curveTypes);
|
||||
}
|
||||
|
||||
private void ensureSpace(final int n) {
|
||||
// use substraction to avoid integer overflow:
|
||||
if (curves.length - end < n) {
|
||||
if (DO_STATS) {
|
||||
stat_array_polystack_curves.add(end + n);
|
||||
}
|
||||
curves = curves_ref.widenArray(curves, end, end + n);
|
||||
}
|
||||
if (curveTypes.length <= numCurves) {
|
||||
if (DO_STATS) {
|
||||
stat_array_polystack_curveTypes.add(numCurves + 1);
|
||||
}
|
||||
curveTypes = curveTypes_ref.widenArray(curveTypes,
|
||||
numCurves,
|
||||
numCurves + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void pushCubic(double x0, double y0,
|
||||
double x1, double y1,
|
||||
double x2, double y2)
|
||||
{
|
||||
ensureSpace(6);
|
||||
curveTypes[numCurves++] = TYPE_CUBICTO;
|
||||
// we reverse the coordinate order to make popping easier
|
||||
final double[] _curves = curves;
|
||||
int e = end;
|
||||
_curves[e++] = x2; _curves[e++] = y2;
|
||||
_curves[e++] = x1; _curves[e++] = y1;
|
||||
_curves[e++] = x0; _curves[e++] = y0;
|
||||
end = e;
|
||||
}
|
||||
|
||||
void pushQuad(double x0, double y0,
|
||||
double x1, double y1)
|
||||
{
|
||||
ensureSpace(4);
|
||||
curveTypes[numCurves++] = TYPE_QUADTO;
|
||||
final double[] _curves = curves;
|
||||
int e = end;
|
||||
_curves[e++] = x1; _curves[e++] = y1;
|
||||
_curves[e++] = x0; _curves[e++] = y0;
|
||||
end = e;
|
||||
}
|
||||
|
||||
void pushLine(double x, double y) {
|
||||
ensureSpace(2);
|
||||
curveTypes[numCurves++] = TYPE_LINETO;
|
||||
curves[end++] = x; curves[end++] = y;
|
||||
}
|
||||
|
||||
void pullAll(final DPathConsumer2D io) {
|
||||
final int nc = numCurves;
|
||||
if (nc == 0) {
|
||||
return;
|
||||
}
|
||||
if (DO_STATS) {
|
||||
// update used marks:
|
||||
if (numCurves > curveTypesUseMark) {
|
||||
curveTypesUseMark = numCurves;
|
||||
}
|
||||
if (end > curvesUseMark) {
|
||||
curvesUseMark = end;
|
||||
}
|
||||
}
|
||||
final byte[] _curveTypes = curveTypes;
|
||||
final double[] _curves = curves;
|
||||
int e = 0;
|
||||
|
||||
for (int i = 0; i < nc; i++) {
|
||||
switch(_curveTypes[i]) {
|
||||
case TYPE_LINETO:
|
||||
io.lineTo(_curves[e], _curves[e+1]);
|
||||
e += 2;
|
||||
continue;
|
||||
case TYPE_QUADTO:
|
||||
io.quadTo(_curves[e+0], _curves[e+1],
|
||||
_curves[e+2], _curves[e+3]);
|
||||
e += 4;
|
||||
continue;
|
||||
case TYPE_CUBICTO:
|
||||
io.curveTo(_curves[e+0], _curves[e+1],
|
||||
_curves[e+2], _curves[e+3],
|
||||
_curves[e+4], _curves[e+5]);
|
||||
e += 6;
|
||||
continue;
|
||||
default:
|
||||
}
|
||||
}
|
||||
numCurves = 0;
|
||||
end = 0;
|
||||
}
|
||||
|
||||
void popAll(final DPathConsumer2D io) {
|
||||
int nc = numCurves;
|
||||
if (nc == 0) {
|
||||
return;
|
||||
}
|
||||
if (DO_STATS) {
|
||||
// update used marks:
|
||||
if (numCurves > curveTypesUseMark) {
|
||||
curveTypesUseMark = numCurves;
|
||||
}
|
||||
if (end > curvesUseMark) {
|
||||
curvesUseMark = end;
|
||||
}
|
||||
}
|
||||
final byte[] _curveTypes = curveTypes;
|
||||
final double[] _curves = curves;
|
||||
int e = end;
|
||||
|
||||
while (nc != 0) {
|
||||
switch(_curveTypes[--nc]) {
|
||||
case TYPE_LINETO:
|
||||
e -= 2;
|
||||
io.lineTo(_curves[e], _curves[e+1]);
|
||||
continue;
|
||||
case TYPE_QUADTO:
|
||||
e -= 4;
|
||||
io.quadTo(_curves[e+0], _curves[e+1],
|
||||
_curves[e+2], _curves[e+3]);
|
||||
continue;
|
||||
case TYPE_CUBICTO:
|
||||
e -= 6;
|
||||
io.curveTo(_curves[e+0], _curves[e+1],
|
||||
_curves[e+2], _curves[e+3],
|
||||
_curves[e+4], _curves[e+5]);
|
||||
continue;
|
||||
default:
|
||||
}
|
||||
}
|
||||
numCurves = 0;
|
||||
end = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String ret = "";
|
||||
int nc = numCurves;
|
||||
int last = end;
|
||||
int len;
|
||||
while (nc != 0) {
|
||||
switch(curveTypes[--nc]) {
|
||||
case TYPE_LINETO:
|
||||
len = 2;
|
||||
ret += "line: ";
|
||||
break;
|
||||
case TYPE_QUADTO:
|
||||
len = 4;
|
||||
ret += "quad: ";
|
||||
break;
|
||||
case TYPE_CUBICTO:
|
||||
len = 6;
|
||||
ret += "cubic: ";
|
||||
break;
|
||||
default:
|
||||
len = 0;
|
||||
}
|
||||
last -= len;
|
||||
ret += Arrays.toString(Arrays.copyOfRange(curves, last, last+len))
|
||||
+ "\n";
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
// a stack of integer indices
|
||||
static final class IndexStack {
|
||||
|
||||
// integer capacity = edges count / 4 ~ 1024
|
||||
private static final int INITIAL_COUNT = INITIAL_EDGES_COUNT >> 2;
|
||||
|
||||
private int end;
|
||||
private int[] indices;
|
||||
|
||||
// indices ref (dirty)
|
||||
private final IntArrayCache.Reference indices_ref;
|
||||
|
||||
// used marks (stats only)
|
||||
private int indicesUseMark;
|
||||
|
||||
private final StatLong stat_idxstack_indices;
|
||||
private final Histogram hist_idxstack_indices;
|
||||
private final StatLong stat_array_idxstack_indices;
|
||||
|
||||
IndexStack(final DRendererContext rdrCtx) {
|
||||
this(rdrCtx, null, null, null);
|
||||
}
|
||||
|
||||
IndexStack(final DRendererContext rdrCtx,
|
||||
final StatLong stat_idxstack_indices,
|
||||
final Histogram hist_idxstack_indices,
|
||||
final StatLong stat_array_idxstack_indices)
|
||||
{
|
||||
indices_ref = rdrCtx.newDirtyIntArrayRef(INITIAL_COUNT); // 4K
|
||||
indices = indices_ref.initial;
|
||||
end = 0;
|
||||
|
||||
if (DO_STATS) {
|
||||
indicesUseMark = 0;
|
||||
}
|
||||
this.stat_idxstack_indices = stat_idxstack_indices;
|
||||
this.hist_idxstack_indices = hist_idxstack_indices;
|
||||
this.stat_array_idxstack_indices = stat_array_idxstack_indices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes this PolyStack:
|
||||
* clean up before reusing this instance
|
||||
*/
|
||||
void dispose() {
|
||||
end = 0;
|
||||
|
||||
if (DO_STATS) {
|
||||
stat_idxstack_indices.add(indicesUseMark);
|
||||
hist_idxstack_indices.add(indicesUseMark);
|
||||
|
||||
// reset marks
|
||||
indicesUseMark = 0;
|
||||
}
|
||||
|
||||
// Return arrays:
|
||||
// values is kept dirty
|
||||
indices = indices_ref.putArray(indices);
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
return (end == 0);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
end = 0;
|
||||
}
|
||||
|
||||
void push(final int v) {
|
||||
// remove redundant values (reverse order):
|
||||
int[] _values = indices;
|
||||
final int nc = end;
|
||||
if (nc != 0) {
|
||||
if (_values[nc - 1] == v) {
|
||||
// remove both duplicated values:
|
||||
end--;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (_values.length <= nc) {
|
||||
if (DO_STATS) {
|
||||
stat_array_idxstack_indices.add(nc + 1);
|
||||
}
|
||||
indices = _values = indices_ref.widenArray(_values, nc, nc + 1);
|
||||
}
|
||||
_values[end++] = v;
|
||||
|
||||
if (DO_STATS) {
|
||||
// update used marks:
|
||||
if (end > indicesUseMark) {
|
||||
indicesUseMark = end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pullAll(final double[] points, final DPathConsumer2D io) {
|
||||
final int nc = end;
|
||||
if (nc == 0) {
|
||||
return;
|
||||
}
|
||||
final int[] _values = indices;
|
||||
|
||||
for (int i = 0, j; i < nc; i++) {
|
||||
j = _values[i] << 1;
|
||||
io.lineTo(points[j], points[j + 1]);
|
||||
}
|
||||
end = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -84,6 +84,13 @@ public final class DMarlinRenderingEngine extends RenderingEngine
|
||||
static final double UPPER_BND = Float.MAX_VALUE / 2.0d;
|
||||
static final double LOWER_BND = -UPPER_BND;
|
||||
|
||||
static final boolean DO_CLIP = MarlinProperties.isDoClip();
|
||||
static final boolean DO_CLIP_FILL = true;
|
||||
|
||||
static final boolean DO_TRACE_PATH = false;
|
||||
|
||||
static final boolean DO_CLIP_RUNTIME_ENABLE = MarlinProperties.isDoClipRuntimeFlag();
|
||||
|
||||
/**
|
||||
* Public constructor
|
||||
*/
|
||||
@ -133,7 +140,7 @@ public final class DMarlinRenderingEngine extends RenderingEngine
|
||||
miterlimit,
|
||||
dashes,
|
||||
dashphase,
|
||||
rdrCtx.transformerPC2D.wrapPath2d(p2d)
|
||||
rdrCtx.transformerPC2D.wrapPath2D(p2d)
|
||||
);
|
||||
|
||||
// Use Path2D copy constructor (trim)
|
||||
@ -195,14 +202,14 @@ public final class DMarlinRenderingEngine extends RenderingEngine
|
||||
}
|
||||
}
|
||||
|
||||
final void strokeTo(final DRendererContext rdrCtx,
|
||||
Shape src,
|
||||
AffineTransform at,
|
||||
BasicStroke bs,
|
||||
boolean thin,
|
||||
NormMode normalize,
|
||||
boolean antialias,
|
||||
DPathConsumer2D pc2d)
|
||||
void strokeTo(final DRendererContext rdrCtx,
|
||||
Shape src,
|
||||
AffineTransform at,
|
||||
BasicStroke bs,
|
||||
boolean thin,
|
||||
NormMode normalize,
|
||||
boolean antialias,
|
||||
DPathConsumer2D pc2d)
|
||||
{
|
||||
double lw;
|
||||
if (thin) {
|
||||
@ -295,17 +302,17 @@ public final class DMarlinRenderingEngine extends RenderingEngine
|
||||
return (lw / widthScale);
|
||||
}
|
||||
|
||||
final void strokeTo(final DRendererContext rdrCtx,
|
||||
Shape src,
|
||||
AffineTransform at,
|
||||
double width,
|
||||
NormMode norm,
|
||||
int caps,
|
||||
int join,
|
||||
float miterlimit,
|
||||
float[] dashes,
|
||||
float dashphase,
|
||||
DPathConsumer2D pc2d)
|
||||
void strokeTo(final DRendererContext rdrCtx,
|
||||
Shape src,
|
||||
AffineTransform at,
|
||||
double width,
|
||||
NormMode norm,
|
||||
int caps,
|
||||
int join,
|
||||
float miterlimit,
|
||||
float[] dashes,
|
||||
float dashphase,
|
||||
DPathConsumer2D pc2d)
|
||||
{
|
||||
// We use strokerat so that in Stroker and Dasher we can work only
|
||||
// with the pre-transformation coordinates. This will repeat a lot of
|
||||
@ -324,6 +331,7 @@ public final class DMarlinRenderingEngine extends RenderingEngine
|
||||
|
||||
int dashLen = -1;
|
||||
boolean recycleDashes = false;
|
||||
double scale = 1.0d;
|
||||
double[] dashesD = null;
|
||||
|
||||
// Ensure converting dashes to double precision:
|
||||
@ -364,7 +372,7 @@ public final class DMarlinRenderingEngine extends RenderingEngine
|
||||
// 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) && nearZero(a*a + c*c - (b*b + d*d))) {
|
||||
final double scale = Math.sqrt(a*a + c*c);
|
||||
scale = Math.sqrt(a*a + c*c);
|
||||
|
||||
if (dashesD != null) {
|
||||
for (int i = 0; i < dashLen; i++) {
|
||||
@ -399,23 +407,44 @@ public final class DMarlinRenderingEngine extends RenderingEngine
|
||||
at = null;
|
||||
}
|
||||
|
||||
final DTransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;
|
||||
|
||||
if (DO_TRACE_PATH) {
|
||||
// trace Stroker:
|
||||
pc2d = transformerPC2D.traceStroker(pc2d);
|
||||
}
|
||||
|
||||
if (USE_SIMPLIFIER) {
|
||||
// Use simplifier after stroker before Renderer
|
||||
// to remove collinear segments (notably due to cap square)
|
||||
pc2d = rdrCtx.simplifier.init(pc2d);
|
||||
}
|
||||
|
||||
final DTransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;
|
||||
// deltaTransformConsumer may adjust the clip rectangle:
|
||||
pc2d = transformerPC2D.deltaTransformConsumer(pc2d, strokerat);
|
||||
|
||||
pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit);
|
||||
// stroker will adjust the clip rectangle (width / miter limit):
|
||||
pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit, scale);
|
||||
|
||||
if (dashesD != null) {
|
||||
pc2d = rdrCtx.dasher.init(pc2d, dashesD, dashLen, dashphase,
|
||||
recycleDashes);
|
||||
} else if (rdrCtx.doClip && (caps != Stroker.CAP_BUTT)) {
|
||||
if (DO_TRACE_PATH) {
|
||||
pc2d = transformerPC2D.traceClosedPathDetector(pc2d);
|
||||
}
|
||||
|
||||
// If no dash and clip is enabled:
|
||||
// detect closedPaths (polygons) for caps
|
||||
pc2d = transformerPC2D.detectClosedPath(pc2d);
|
||||
}
|
||||
pc2d = transformerPC2D.inverseDeltaTransformConsumer(pc2d, strokerat);
|
||||
|
||||
if (DO_TRACE_PATH) {
|
||||
// trace Input:
|
||||
pc2d = transformerPC2D.traceInput(pc2d);
|
||||
}
|
||||
|
||||
final PathIterator pi = norm.getNormalizingPathIterator(rdrCtx,
|
||||
src.getPathIterator(at));
|
||||
|
||||
@ -596,14 +625,12 @@ public final class DMarlinRenderingEngine extends RenderingEngine
|
||||
}
|
||||
|
||||
private static void pathTo(final DRendererContext rdrCtx, final PathIterator pi,
|
||||
final DPathConsumer2D pc2d)
|
||||
DPathConsumer2D pc2d)
|
||||
{
|
||||
// mark context as DIRTY:
|
||||
rdrCtx.dirty = true;
|
||||
|
||||
final double[] coords = rdrCtx.double6;
|
||||
|
||||
pathToLoop(coords, pi, pc2d);
|
||||
pathToLoop(rdrCtx.double6, pi, pc2d);
|
||||
|
||||
// mark context as CLEAN:
|
||||
rdrCtx.dirty = false;
|
||||
@ -781,6 +808,19 @@ public final class DMarlinRenderingEngine extends RenderingEngine
|
||||
|
||||
final DRendererContext rdrCtx = getRendererContext();
|
||||
try {
|
||||
if (DO_CLIP || (DO_CLIP_RUNTIME_ENABLE && MarlinProperties.isDoClipAtRuntime())) {
|
||||
// Define the initial clip bounds:
|
||||
final double[] clipRect = rdrCtx.clipRect;
|
||||
|
||||
clipRect[0] = clip.getLoY();
|
||||
clipRect[1] = clip.getLoY() + clip.getHeight();
|
||||
clipRect[2] = clip.getLoX();
|
||||
clipRect[3] = clip.getLoX() + clip.getWidth();
|
||||
|
||||
// Enable clipping:
|
||||
rdrCtx.doClip = true;
|
||||
}
|
||||
|
||||
// Test if at is identity:
|
||||
final AffineTransform _at = (at != null && !at.isIdentity()) ? at
|
||||
: null;
|
||||
@ -797,13 +837,29 @@ public final class DMarlinRenderingEngine extends RenderingEngine
|
||||
clip.getWidth(), clip.getHeight(),
|
||||
pi.getWindingRule());
|
||||
|
||||
DPathConsumer2D pc2d = r;
|
||||
|
||||
if (DO_CLIP_FILL && rdrCtx.doClip) {
|
||||
if (DO_TRACE_PATH) {
|
||||
// trace Filler:
|
||||
pc2d = rdrCtx.transformerPC2D.traceFiller(pc2d);
|
||||
}
|
||||
pc2d = rdrCtx.transformerPC2D.pathClipper(pc2d);
|
||||
}
|
||||
|
||||
if (DO_TRACE_PATH) {
|
||||
// trace Input:
|
||||
pc2d = rdrCtx.transformerPC2D.traceInput(pc2d);
|
||||
}
|
||||
|
||||
// TODO: subdivide quad/cubic curves into monotonic curves ?
|
||||
pathTo(rdrCtx, pi, r);
|
||||
pathTo(rdrCtx, pi, pc2d);
|
||||
|
||||
} else {
|
||||
// draw shape with given stroke:
|
||||
r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),
|
||||
clip.getWidth(), clip.getHeight(),
|
||||
PathIterator.WIND_NON_ZERO);
|
||||
WIND_NON_ZERO);
|
||||
|
||||
strokeTo(rdrCtx, s, _at, bs, thin, norm, true, r);
|
||||
}
|
||||
@ -826,12 +882,12 @@ public final class DMarlinRenderingEngine extends RenderingEngine
|
||||
}
|
||||
|
||||
@Override
|
||||
public final AATileGenerator getAATileGenerator(double x, double y,
|
||||
double dx1, double dy1,
|
||||
double dx2, double dy2,
|
||||
double lw1, double lw2,
|
||||
Region clip,
|
||||
int[] bbox)
|
||||
public AATileGenerator getAATileGenerator(double x, double y,
|
||||
double dx1, double dy1,
|
||||
double dx2, double dy2,
|
||||
double lw1, double lw2,
|
||||
Region clip,
|
||||
int[] bbox)
|
||||
{
|
||||
// REMIND: Deal with large coordinates!
|
||||
double ldx1, ldy1, ldx2, ldy2;
|
||||
@ -862,8 +918,8 @@ public final class DMarlinRenderingEngine extends RenderingEngine
|
||||
final DRendererContext rdrCtx = getRendererContext();
|
||||
try {
|
||||
r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),
|
||||
clip.getWidth(), clip.getHeight(),
|
||||
DRenderer.WIND_EVEN_ODD);
|
||||
clip.getWidth(), clip.getHeight(),
|
||||
WIND_EVEN_ODD);
|
||||
|
||||
r.moveTo( x, y);
|
||||
r.lineTo( (x+dx1), (y+dy1));
|
||||
@ -915,14 +971,14 @@ public final class DMarlinRenderingEngine extends RenderingEngine
|
||||
}
|
||||
|
||||
static {
|
||||
if (PathIterator.WIND_NON_ZERO != DRenderer.WIND_NON_ZERO ||
|
||||
PathIterator.WIND_EVEN_ODD != DRenderer.WIND_EVEN_ODD ||
|
||||
BasicStroke.JOIN_MITER != DStroker.JOIN_MITER ||
|
||||
BasicStroke.JOIN_ROUND != DStroker.JOIN_ROUND ||
|
||||
BasicStroke.JOIN_BEVEL != DStroker.JOIN_BEVEL ||
|
||||
BasicStroke.CAP_BUTT != DStroker.CAP_BUTT ||
|
||||
BasicStroke.CAP_ROUND != DStroker.CAP_ROUND ||
|
||||
BasicStroke.CAP_SQUARE != DStroker.CAP_SQUARE)
|
||||
if (PathIterator.WIND_NON_ZERO != WIND_NON_ZERO ||
|
||||
PathIterator.WIND_EVEN_ODD != WIND_EVEN_ODD ||
|
||||
BasicStroke.JOIN_MITER != JOIN_MITER ||
|
||||
BasicStroke.JOIN_ROUND != JOIN_ROUND ||
|
||||
BasicStroke.JOIN_BEVEL != JOIN_BEVEL ||
|
||||
BasicStroke.CAP_BUTT != CAP_BUTT ||
|
||||
BasicStroke.CAP_ROUND != CAP_ROUND ||
|
||||
BasicStroke.CAP_SQUARE != CAP_SQUARE)
|
||||
{
|
||||
throw new InternalError("mismatched renderer constants");
|
||||
}
|
||||
@ -1045,6 +1101,11 @@ public final class DMarlinRenderingEngine extends RenderingEngine
|
||||
logInfo("sun.java2d.renderer.useSimplifier = "
|
||||
+ MarlinConst.USE_SIMPLIFIER);
|
||||
|
||||
logInfo("sun.java2d.renderer.clip = "
|
||||
+ MarlinProperties.isDoClip());
|
||||
logInfo("sun.java2d.renderer.clip.runtime.enable = "
|
||||
+ MarlinProperties.isDoClipRuntimeFlag());
|
||||
|
||||
// debugging parameters
|
||||
logInfo("sun.java2d.renderer.doStats = "
|
||||
+ MarlinConst.DO_STATS);
|
||||
|
@ -46,6 +46,9 @@ final class DRenderer implements DPathConsumer2D, MarlinRenderer {
|
||||
static final int SUBPIXEL_MASK_X = SUBPIXEL_POSITIONS_X - 1;
|
||||
static final int SUBPIXEL_MASK_Y = SUBPIXEL_POSITIONS_Y - 1;
|
||||
|
||||
static final double RDR_OFFSET_X = 0.5d / SUBPIXEL_SCALE_X;
|
||||
static final double RDR_OFFSET_Y = 0.5d / SUBPIXEL_SCALE_Y;
|
||||
|
||||
// number of subpixels corresponding to a tile line
|
||||
private static final int SUBPIXEL_TILE
|
||||
= TILE_H << SUBPIXEL_LG_POSITIONS_Y;
|
||||
@ -57,9 +60,6 @@ final class DRenderer implements DPathConsumer2D, MarlinRenderer {
|
||||
// crossing capacity = edges count / 4 ~ 1024
|
||||
static final int INITIAL_CROSSING_COUNT = INITIAL_EDGES_COUNT >> 2;
|
||||
|
||||
public static final int WIND_EVEN_ODD = 0;
|
||||
public static final int WIND_NON_ZERO = 1;
|
||||
|
||||
// common to all types of input path segments.
|
||||
// OFFSET as bytes
|
||||
// only integer values:
|
||||
@ -522,11 +522,11 @@ final class DRenderer implements DPathConsumer2D, MarlinRenderer {
|
||||
|
||||
DRenderer(final DRendererContext rdrCtx) {
|
||||
this.rdrCtx = rdrCtx;
|
||||
this.curve = rdrCtx.curve;
|
||||
this.cache = rdrCtx.cache;
|
||||
|
||||
this.edges = rdrCtx.newOffHeapArray(INITIAL_EDGES_CAPACITY); // 96K
|
||||
|
||||
this.curve = rdrCtx.curve;
|
||||
|
||||
edgeBuckets_ref = rdrCtx.newCleanIntArrayRef(INITIAL_BUCKET_ARRAY); // 64K
|
||||
edgeBucketCounts_ref = rdrCtx.newCleanIntArrayRef(INITIAL_BUCKET_ARRAY); // 64K
|
||||
|
||||
@ -537,8 +537,6 @@ final class DRenderer implements DPathConsumer2D, MarlinRenderer {
|
||||
alphaLine_ref = rdrCtx.newCleanIntArrayRef(INITIAL_AA_ARRAY); // 8K
|
||||
alphaLine = alphaLine_ref.initial;
|
||||
|
||||
this.cache = rdrCtx.cache;
|
||||
|
||||
crossings_ref = rdrCtx.newDirtyIntArrayRef(INITIAL_CROSSING_COUNT); // 2K
|
||||
aux_crossings_ref = rdrCtx.newDirtyIntArrayRef(INITIAL_CROSSING_COUNT); // 2K
|
||||
edgePtrs_ref = rdrCtx.newDirtyIntArrayRef(INITIAL_CROSSING_COUNT); // 2K
|
||||
@ -668,7 +666,7 @@ final class DRenderer implements DPathConsumer2D, MarlinRenderer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveTo(double pix_x0, double pix_y0) {
|
||||
public void moveTo(final double pix_x0, final double pix_y0) {
|
||||
closePath();
|
||||
final double sx = tosubpixx(pix_x0);
|
||||
final double sy = tosubpixy(pix_y0);
|
||||
@ -679,7 +677,7 @@ final class DRenderer implements DPathConsumer2D, MarlinRenderer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lineTo(double pix_x1, double pix_y1) {
|
||||
public void lineTo(final double pix_x1, final double pix_y1) {
|
||||
final double x1 = tosubpixx(pix_x1);
|
||||
final double y1 = tosubpixy(pix_y1);
|
||||
addLine(x0, y0, x1, y1);
|
||||
@ -688,24 +686,26 @@ final class DRenderer implements DPathConsumer2D, MarlinRenderer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void curveTo(double x1, double y1,
|
||||
double x2, double y2,
|
||||
double x3, double y3)
|
||||
public void curveTo(final double pix_x1, final double pix_y1,
|
||||
final double pix_x2, final double pix_y2,
|
||||
final double pix_x3, final double pix_y3)
|
||||
{
|
||||
final double xe = tosubpixx(x3);
|
||||
final double ye = tosubpixy(y3);
|
||||
curve.set(x0, y0, tosubpixx(x1), tosubpixy(y1),
|
||||
tosubpixx(x2), tosubpixy(y2), xe, ye);
|
||||
final double xe = tosubpixx(pix_x3);
|
||||
final double ye = tosubpixy(pix_y3);
|
||||
curve.set(x0, y0, tosubpixx(pix_x1), tosubpixy(pix_y1),
|
||||
tosubpixx(pix_x2), tosubpixy(pix_y2), xe, ye);
|
||||
curveBreakIntoLinesAndAdd(x0, y0, curve, xe, ye);
|
||||
x0 = xe;
|
||||
y0 = ye;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void quadTo(double x1, double y1, double x2, double y2) {
|
||||
final double xe = tosubpixx(x2);
|
||||
final double ye = tosubpixy(y2);
|
||||
curve.set(x0, y0, tosubpixx(x1), tosubpixy(y1), xe, ye);
|
||||
public void quadTo(final double pix_x1, final double pix_y1,
|
||||
final double pix_x2, final double pix_y2)
|
||||
{
|
||||
final double xe = tosubpixx(pix_x2);
|
||||
final double ye = tosubpixy(pix_y2);
|
||||
curve.set(x0, y0, tosubpixx(pix_x1), tosubpixy(pix_y1), xe, ye);
|
||||
quadBreakIntoLinesAndAdd(x0, y0, curve, xe, ye);
|
||||
x0 = xe;
|
||||
y0 = ye;
|
||||
@ -713,9 +713,11 @@ final class DRenderer implements DPathConsumer2D, MarlinRenderer {
|
||||
|
||||
@Override
|
||||
public void closePath() {
|
||||
addLine(x0, y0, sx0, sy0);
|
||||
x0 = sx0;
|
||||
y0 = sy0;
|
||||
if (x0 != sx0 || y0 != sy0) {
|
||||
addLine(x0, y0, sx0, sy0);
|
||||
x0 = sx0;
|
||||
y0 = sy0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -75,16 +75,22 @@ final class DRendererContext extends ReentrantContext implements IRendererContex
|
||||
final MarlinCache cache;
|
||||
// flag indicating the shape is stroked (1) or filled (0)
|
||||
int stroking = 0;
|
||||
// flag indicating to clip the shape
|
||||
boolean doClip = false;
|
||||
// flag indicating if the path is closed or not (in advance) to handle properly caps
|
||||
boolean closedPath = false;
|
||||
// clip rectangle (ymin, ymax, xmin, xmax):
|
||||
final double[] clipRect = new double[4];
|
||||
|
||||
// Array caches:
|
||||
/* clean int[] cache (zero-filled) = 5 refs */
|
||||
private final IntArrayCache cleanIntCache = new IntArrayCache(true, 5);
|
||||
/* dirty int[] cache = 4 refs */
|
||||
private final IntArrayCache dirtyIntCache = new IntArrayCache(false, 4);
|
||||
/* dirty double[] cache = 3 refs */
|
||||
private final DoubleArrayCache dirtyDoubleCache = new DoubleArrayCache(false, 3);
|
||||
/* dirty byte[] cache = 1 ref */
|
||||
private final ByteArrayCache dirtyByteCache = new ByteArrayCache(false, 1);
|
||||
/* dirty int[] cache = 5 refs */
|
||||
private final IntArrayCache dirtyIntCache = new IntArrayCache(false, 5);
|
||||
/* dirty double[] cache = 4 refs (2 polystack) */
|
||||
private final DoubleArrayCache dirtyDoubleCache = new DoubleArrayCache(false, 4);
|
||||
/* dirty byte[] cache = 2 ref (2 polystack) */
|
||||
private final ByteArrayCache dirtyByteCache = new ByteArrayCache(false, 2);
|
||||
|
||||
// RendererContext statistics
|
||||
final RendererStats stats;
|
||||
@ -119,7 +125,7 @@ final class DRendererContext extends ReentrantContext implements IRendererContex
|
||||
nPQPathIterator = new NormalizingPathIterator.NearestPixelQuarter(double6);
|
||||
|
||||
// MarlinRenderingEngine.TransformingPathConsumer2D
|
||||
transformerPC2D = new DTransformingPathConsumer2D();
|
||||
transformerPC2D = new DTransformingPathConsumer2D(this);
|
||||
|
||||
// Renderer:
|
||||
cache = new MarlinCache(this);
|
||||
@ -141,7 +147,10 @@ final class DRendererContext extends ReentrantContext implements IRendererContex
|
||||
}
|
||||
stats.totalOffHeap = 0L;
|
||||
}
|
||||
stroking = 0;
|
||||
stroking = 0;
|
||||
doClip = false;
|
||||
closedPath = false;
|
||||
|
||||
// if context is maked as DIRTY:
|
||||
if (dirty) {
|
||||
// may happen if an exception if thrown in the pipeline processing:
|
||||
@ -162,12 +171,11 @@ final class DRendererContext extends ReentrantContext implements IRendererContex
|
||||
|
||||
Path2D.Double getPath2D() {
|
||||
// resolve reference:
|
||||
Path2D.Double p2d
|
||||
= (refPath2D != null) ? refPath2D.get() : null;
|
||||
Path2D.Double p2d = (refPath2D != null) ? refPath2D.get() : null;
|
||||
|
||||
// create a new Path2D ?
|
||||
if (p2d == null) {
|
||||
p2d = new Path2D.Double(Path2D.WIND_NON_ZERO, INITIAL_EDGES_COUNT); // 32K
|
||||
p2d = new Path2D.Double(WIND_NON_ZERO, INITIAL_EDGES_COUNT); // 32K
|
||||
|
||||
// update weak reference:
|
||||
refPath2D = new WeakReference<Path2D.Double>(p2d);
|
||||
|
@ -26,6 +26,7 @@
|
||||
package sun.java2d.marlin;
|
||||
|
||||
import java.util.Arrays;
|
||||
import sun.java2d.marlin.DHelpers.PolyStack;
|
||||
|
||||
// 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
|
||||
@ -36,42 +37,16 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
|
||||
private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad
|
||||
private static final int CLOSE = 2;
|
||||
|
||||
/**
|
||||
* Constant value for join style.
|
||||
*/
|
||||
public static final int JOIN_MITER = 0;
|
||||
|
||||
/**
|
||||
* Constant value for join style.
|
||||
*/
|
||||
public static final int JOIN_ROUND = 1;
|
||||
|
||||
/**
|
||||
* Constant value for join style.
|
||||
*/
|
||||
public static final int JOIN_BEVEL = 2;
|
||||
|
||||
/**
|
||||
* Constant value for end cap style.
|
||||
*/
|
||||
public static final int CAP_BUTT = 0;
|
||||
|
||||
/**
|
||||
* Constant value for end cap style.
|
||||
*/
|
||||
public static final int CAP_ROUND = 1;
|
||||
|
||||
/**
|
||||
* Constant value for end cap style.
|
||||
*/
|
||||
public static final int CAP_SQUARE = 2;
|
||||
|
||||
// pisces used to use fixed point arithmetic with 16 decimal digits. I
|
||||
// didn't want to change the values of the constant below when I converted
|
||||
// it to floating point, so that's why the divisions by 2^16 are there.
|
||||
private static final double ROUND_JOIN_THRESHOLD = 1000.0d/65536.0d;
|
||||
|
||||
private static final double C = 0.5522847498307933d;
|
||||
// kappa = (4/3) * (SQRT(2) - 1)
|
||||
private static final double C = (4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d);
|
||||
|
||||
// SQRT(2)
|
||||
private static final double SQRT_2 = Math.sqrt(2.0d);
|
||||
|
||||
private static final int MAX_N_CURVES = 11;
|
||||
|
||||
@ -118,6 +93,20 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
|
||||
// dirty curve
|
||||
final DCurve curve;
|
||||
|
||||
// Bounds of the drawing region, at pixel precision.
|
||||
private double[] clipRect;
|
||||
|
||||
// the outcode of the current point
|
||||
private int cOutCode = 0;
|
||||
|
||||
// the outcode of the starting point
|
||||
private int sOutCode = 0;
|
||||
|
||||
// flag indicating if the path is opened (clipped)
|
||||
private boolean opened = false;
|
||||
// flag indicating if the starting point's cap is done
|
||||
private boolean capStart = false;
|
||||
|
||||
/**
|
||||
* Constructs a <code>DStroker</code>.
|
||||
* @param rdrCtx per-thread renderer context
|
||||
@ -125,7 +114,15 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
|
||||
DStroker(final DRendererContext rdrCtx) {
|
||||
this.rdrCtx = rdrCtx;
|
||||
|
||||
this.reverse = new PolyStack(rdrCtx);
|
||||
this.reverse = (rdrCtx.stats != null) ?
|
||||
new PolyStack(rdrCtx,
|
||||
rdrCtx.stats.stat_str_polystack_types,
|
||||
rdrCtx.stats.stat_str_polystack_curves,
|
||||
rdrCtx.stats.hist_str_polystack_curves,
|
||||
rdrCtx.stats.stat_array_str_polystack_curves,
|
||||
rdrCtx.stats.stat_array_str_polystack_types)
|
||||
: new PolyStack(rdrCtx);
|
||||
|
||||
this.curve = rdrCtx.curve;
|
||||
}
|
||||
|
||||
@ -141,13 +138,15 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
|
||||
* <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or
|
||||
* <code>JOIN_BEVEL</code>.
|
||||
* @param miterLimit the desired miter limit
|
||||
* @param scale scaling factor applied to clip boundaries
|
||||
* @return this instance
|
||||
*/
|
||||
DStroker init(DPathConsumer2D pc2d,
|
||||
double lineWidth,
|
||||
int capStyle,
|
||||
int joinStyle,
|
||||
double miterLimit)
|
||||
DStroker init(final DPathConsumer2D pc2d,
|
||||
final double lineWidth,
|
||||
final int capStyle,
|
||||
final int joinStyle,
|
||||
final double miterLimit,
|
||||
final double scale)
|
||||
{
|
||||
this.out = pc2d;
|
||||
|
||||
@ -156,13 +155,45 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
|
||||
this.capStyle = capStyle;
|
||||
this.joinStyle = joinStyle;
|
||||
|
||||
double limit = miterLimit * lineWidth2;
|
||||
final double limit = miterLimit * lineWidth2;
|
||||
this.miterLimitSq = limit * limit;
|
||||
|
||||
this.prev = CLOSE;
|
||||
|
||||
rdrCtx.stroking = 1;
|
||||
|
||||
if (rdrCtx.doClip) {
|
||||
// Adjust the clipping rectangle with the stroker margin (miter limit, width)
|
||||
double rdrOffX = 0.0d, rdrOffY = 0.0d;
|
||||
double margin = lineWidth2;
|
||||
|
||||
if (capStyle == CAP_SQUARE) {
|
||||
margin *= SQRT_2;
|
||||
}
|
||||
if ((joinStyle == JOIN_MITER) && (margin < limit)) {
|
||||
margin = limit;
|
||||
}
|
||||
if (scale != 1.0d) {
|
||||
margin *= scale;
|
||||
rdrOffX = scale * DRenderer.RDR_OFFSET_X;
|
||||
rdrOffY = scale * DRenderer.RDR_OFFSET_Y;
|
||||
}
|
||||
// add a small rounding error:
|
||||
margin += 1e-3d;
|
||||
|
||||
// bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY
|
||||
// adjust clip rectangle (ymin, ymax, xmin, xmax):
|
||||
final double[] _clipRect = rdrCtx.clipRect;
|
||||
_clipRect[0] -= margin - rdrOffY;
|
||||
_clipRect[1] += margin + rdrOffY;
|
||||
_clipRect[2] -= margin - rdrOffX;
|
||||
_clipRect[3] += margin + rdrOffX;
|
||||
this.clipRect = _clipRect;
|
||||
} else {
|
||||
this.clipRect = null;
|
||||
this.cOutCode = 0;
|
||||
this.sOutCode = 0;
|
||||
}
|
||||
return this; // fluent API
|
||||
}
|
||||
|
||||
@ -173,6 +204,9 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
|
||||
void dispose() {
|
||||
reverse.dispose();
|
||||
|
||||
opened = false;
|
||||
capStart = false;
|
||||
|
||||
if (DO_CLEAN_DIRTY) {
|
||||
// Force zero-fill dirty arrays:
|
||||
Arrays.fill(offset0, 0.0d);
|
||||
@ -443,19 +477,62 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveTo(double x0, double y0) {
|
||||
if (prev == DRAWING_OP_TO) {
|
||||
finish();
|
||||
public void moveTo(final double x0, final double y0) {
|
||||
moveTo(x0, y0, cOutCode);
|
||||
// update starting point:
|
||||
this.sx0 = x0;
|
||||
this.sy0 = y0;
|
||||
this.sdx = 1.0d;
|
||||
this.sdy = 0.0d;
|
||||
this.opened = false;
|
||||
this.capStart = false;
|
||||
|
||||
if (clipRect != null) {
|
||||
final int outcode = DHelpers.outcode(x0, y0, clipRect);
|
||||
this.cOutCode = outcode;
|
||||
this.sOutCode = outcode;
|
||||
}
|
||||
}
|
||||
|
||||
private void moveTo(final double x0, final double y0,
|
||||
final int outcode)
|
||||
{
|
||||
if (prev == MOVE_TO) {
|
||||
this.cx0 = x0;
|
||||
this.cy0 = y0;
|
||||
} else {
|
||||
if (prev == DRAWING_OP_TO) {
|
||||
finish(outcode);
|
||||
}
|
||||
this.prev = MOVE_TO;
|
||||
this.cx0 = x0;
|
||||
this.cy0 = y0;
|
||||
this.cdx = 1.0d;
|
||||
this.cdy = 0.0d;
|
||||
}
|
||||
this.sx0 = this.cx0 = x0;
|
||||
this.sy0 = this.cy0 = y0;
|
||||
this.cdx = this.sdx = 1.0d;
|
||||
this.cdy = this.sdy = 0.0d;
|
||||
this.prev = MOVE_TO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lineTo(double x1, double y1) {
|
||||
public void lineTo(final double x1, final double y1) {
|
||||
lineTo(x1, y1, false);
|
||||
}
|
||||
|
||||
private void lineTo(final double x1, final double y1,
|
||||
final boolean force)
|
||||
{
|
||||
final int outcode0 = this.cOutCode;
|
||||
if (!force && clipRect != null) {
|
||||
final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
|
||||
this.cOutCode = outcode1;
|
||||
|
||||
// basic rejection criteria
|
||||
if ((outcode0 & outcode1) != 0) {
|
||||
moveTo(x1, y1, outcode0);
|
||||
opened = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
double dx = x1 - cx0;
|
||||
double dy = y1 - cy0;
|
||||
if (dx == 0.0d && dy == 0.0d) {
|
||||
@ -465,7 +542,7 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
|
||||
final double mx = offset0[0];
|
||||
final double my = offset0[1];
|
||||
|
||||
drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my);
|
||||
drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my, outcode0);
|
||||
|
||||
emitLineTo(cx0 + mx, cy0 + my);
|
||||
emitLineTo( x1 + mx, y1 + my);
|
||||
@ -473,43 +550,65 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
|
||||
emitLineToRev(cx0 - mx, cy0 - my);
|
||||
emitLineToRev( x1 - mx, y1 - my);
|
||||
|
||||
this.cmx = mx;
|
||||
this.cmy = my;
|
||||
this.cdx = dx;
|
||||
this.cdy = dy;
|
||||
this.prev = DRAWING_OP_TO;
|
||||
this.cx0 = x1;
|
||||
this.cy0 = y1;
|
||||
this.prev = DRAWING_OP_TO;
|
||||
this.cdx = dx;
|
||||
this.cdy = dy;
|
||||
this.cmx = mx;
|
||||
this.cmy = my;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closePath() {
|
||||
if (prev != DRAWING_OP_TO) {
|
||||
// distinguish empty path at all vs opened path ?
|
||||
if (prev != DRAWING_OP_TO && !opened) {
|
||||
if (prev == CLOSE) {
|
||||
return;
|
||||
}
|
||||
emitMoveTo(cx0, cy0 - lineWidth2);
|
||||
this.cmx = this.smx = 0.0d;
|
||||
this.cmy = this.smy = -lineWidth2;
|
||||
this.cdx = this.sdx = 1.0d;
|
||||
this.cdy = this.sdy = 0.0d;
|
||||
finish();
|
||||
|
||||
this.sdx = 1.0d;
|
||||
this.sdy = 0.0d;
|
||||
this.cdx = 1.0d;
|
||||
this.cdy = 0.0d;
|
||||
|
||||
this.smx = 0.0d;
|
||||
this.smy = -lineWidth2;
|
||||
this.cmx = 0.0d;
|
||||
this.cmy = -lineWidth2;
|
||||
|
||||
finish(cOutCode);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cx0 != sx0 || cy0 != sy0) {
|
||||
lineTo(sx0, sy0);
|
||||
// basic acceptance criteria
|
||||
if ((sOutCode & cOutCode) == 0) {
|
||||
if (cx0 != sx0 || cy0 != sy0) {
|
||||
lineTo(sx0, sy0, true);
|
||||
}
|
||||
|
||||
drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy, sOutCode);
|
||||
|
||||
emitLineTo(sx0 + smx, sy0 + smy);
|
||||
|
||||
if (opened) {
|
||||
emitLineTo(sx0 - smx, sy0 - smy);
|
||||
} else {
|
||||
emitMoveTo(sx0 - smx, sy0 - smy);
|
||||
}
|
||||
}
|
||||
|
||||
drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy);
|
||||
|
||||
emitLineTo(sx0 + smx, sy0 + smy);
|
||||
|
||||
emitMoveTo(sx0 - smx, sy0 - smy);
|
||||
// Ignore caps like finish(false)
|
||||
emitReverse();
|
||||
|
||||
this.prev = CLOSE;
|
||||
emitClose();
|
||||
|
||||
if (opened) {
|
||||
// do not emit close
|
||||
opened = false;
|
||||
} else {
|
||||
emitClose();
|
||||
}
|
||||
}
|
||||
|
||||
private void emitReverse() {
|
||||
@ -519,7 +618,7 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
|
||||
@Override
|
||||
public void pathDone() {
|
||||
if (prev == DRAWING_OP_TO) {
|
||||
finish();
|
||||
finish(cOutCode);
|
||||
}
|
||||
|
||||
out.pathDone();
|
||||
@ -532,23 +631,39 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
|
||||
dispose();
|
||||
}
|
||||
|
||||
private void finish() {
|
||||
if (capStyle == CAP_ROUND) {
|
||||
drawRoundCap(cx0, cy0, cmx, cmy);
|
||||
} else if (capStyle == CAP_SQUARE) {
|
||||
emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy);
|
||||
emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy);
|
||||
private void finish(final int outcode) {
|
||||
// Problem: impossible to guess if the path will be closed in advance
|
||||
// i.e. if caps must be drawn or not ?
|
||||
// Solution: use the ClosedPathDetector before Stroker to determine
|
||||
// if the path is a closed path or not
|
||||
if (!rdrCtx.closedPath) {
|
||||
if (outcode == 0) {
|
||||
// current point = end's cap:
|
||||
if (capStyle == CAP_ROUND) {
|
||||
drawRoundCap(cx0, cy0, cmx, cmy);
|
||||
} else if (capStyle == CAP_SQUARE) {
|
||||
emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy);
|
||||
emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy);
|
||||
}
|
||||
}
|
||||
emitReverse();
|
||||
|
||||
if (!capStart) {
|
||||
capStart = true;
|
||||
|
||||
if (sOutCode == 0) {
|
||||
// starting point = initial cap:
|
||||
if (capStyle == CAP_ROUND) {
|
||||
drawRoundCap(sx0, sy0, -smx, -smy);
|
||||
} else if (capStyle == CAP_SQUARE) {
|
||||
emitLineTo(sx0 + smy - smx, sy0 - smx - smy);
|
||||
emitLineTo(sx0 + smy + smx, sy0 - smx + smy);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
emitReverse();
|
||||
}
|
||||
|
||||
emitReverse();
|
||||
|
||||
if (capStyle == CAP_ROUND) {
|
||||
drawRoundCap(sx0, sy0, -smx, -smy);
|
||||
} else if (capStyle == CAP_SQUARE) {
|
||||
emitLineTo(sx0 + smy - smx, sy0 - smx - smy);
|
||||
emitLineTo(sx0 + smy + smx, sy0 - smx + smy);
|
||||
}
|
||||
|
||||
emitClose();
|
||||
}
|
||||
|
||||
@ -620,23 +735,28 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
|
||||
double x0, double y0,
|
||||
double dx, double dy,
|
||||
double omx, double omy,
|
||||
double mx, double my)
|
||||
double mx, double my,
|
||||
final int outcode)
|
||||
{
|
||||
if (prev != DRAWING_OP_TO) {
|
||||
emitMoveTo(x0 + mx, y0 + my);
|
||||
this.sdx = dx;
|
||||
this.sdy = dy;
|
||||
this.smx = mx;
|
||||
this.smy = my;
|
||||
if (!opened) {
|
||||
this.sdx = dx;
|
||||
this.sdy = dy;
|
||||
this.smx = mx;
|
||||
this.smy = my;
|
||||
}
|
||||
} else {
|
||||
boolean cw = isCW(pdx, pdy, dx, dy);
|
||||
if (joinStyle == JOIN_MITER) {
|
||||
drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
|
||||
} else if (joinStyle == JOIN_ROUND) {
|
||||
drawRoundJoin(x0, y0,
|
||||
omx, omy,
|
||||
mx, my, cw,
|
||||
ROUND_JOIN_THRESHOLD);
|
||||
final boolean cw = isCW(pdx, pdy, dx, dy);
|
||||
if (outcode == 0) {
|
||||
if (joinStyle == JOIN_MITER) {
|
||||
drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
|
||||
} else if (joinStyle == JOIN_ROUND) {
|
||||
drawRoundJoin(x0, y0,
|
||||
omx, omy,
|
||||
mx, my, cw,
|
||||
ROUND_JOIN_THRESHOLD);
|
||||
}
|
||||
}
|
||||
emitLineTo(x0, y0, !cw);
|
||||
}
|
||||
@ -941,10 +1061,29 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override public void curveTo(double x1, double y1,
|
||||
double x2, double y2,
|
||||
double x3, double y3)
|
||||
@Override
|
||||
public void curveTo(final double x1, final double y1,
|
||||
final double x2, final double y2,
|
||||
final double x3, final double y3)
|
||||
{
|
||||
final int outcode0 = this.cOutCode;
|
||||
if (clipRect != null) {
|
||||
final int outcode3 = DHelpers.outcode(x3, y3, clipRect);
|
||||
this.cOutCode = outcode3;
|
||||
|
||||
if ((outcode0 & outcode3) != 0) {
|
||||
final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
|
||||
final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
|
||||
|
||||
// basic rejection criteria
|
||||
if ((outcode0 & outcode1 & outcode2 & outcode3) != 0) {
|
||||
moveTo(x3, y3, outcode0);
|
||||
opened = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final double[] mid = middle;
|
||||
|
||||
mid[0] = cx0; mid[1] = cy0;
|
||||
@ -953,7 +1092,7 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
|
||||
mid[6] = x3; mid[7] = y3;
|
||||
|
||||
// need these so we can update the state at the end of this method
|
||||
final double xf = mid[6], yf = mid[7];
|
||||
final double xf = x3, yf = y3;
|
||||
double dxs = mid[2] - mid[0];
|
||||
double dys = mid[3] - mid[1];
|
||||
double dxf = mid[6] - mid[4];
|
||||
@ -979,6 +1118,10 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
|
||||
}
|
||||
if (dxs == 0.0d && dys == 0.0d) {
|
||||
// this happens if the "curve" is just a point
|
||||
// fix outcode0 for lineTo() call:
|
||||
if (clipRect != null) {
|
||||
this.cOutCode = outcode0;
|
||||
}
|
||||
lineTo(mid[0], mid[1]);
|
||||
return;
|
||||
}
|
||||
@ -997,7 +1140,7 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
|
||||
}
|
||||
|
||||
computeOffset(dxs, dys, lineWidth2, offset0);
|
||||
drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]);
|
||||
drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
|
||||
|
||||
final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2);
|
||||
|
||||
@ -1032,16 +1175,36 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
|
||||
emitLineToRev(r[kind - 2], r[kind - 1]);
|
||||
}
|
||||
|
||||
this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
|
||||
this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
|
||||
this.cdx = dxf;
|
||||
this.cdy = dyf;
|
||||
this.prev = DRAWING_OP_TO;
|
||||
this.cx0 = xf;
|
||||
this.cy0 = yf;
|
||||
this.prev = DRAWING_OP_TO;
|
||||
this.cdx = dxf;
|
||||
this.cdy = dyf;
|
||||
this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
|
||||
this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
|
||||
}
|
||||
|
||||
@Override public void quadTo(double x1, double y1, double x2, double y2) {
|
||||
@Override
|
||||
public void quadTo(final double x1, final double y1,
|
||||
final double x2, final double y2)
|
||||
{
|
||||
final int outcode0 = this.cOutCode;
|
||||
if (clipRect != null) {
|
||||
final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
|
||||
this.cOutCode = outcode2;
|
||||
|
||||
if ((outcode0 & outcode2) != 0) {
|
||||
final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
|
||||
|
||||
// basic rejection criteria
|
||||
if ((outcode0 & outcode1 & outcode2) != 0) {
|
||||
moveTo(x2, y2, outcode0);
|
||||
opened = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final double[] mid = middle;
|
||||
|
||||
mid[0] = cx0; mid[1] = cy0;
|
||||
@ -1049,7 +1212,7 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
|
||||
mid[4] = x2; mid[5] = y2;
|
||||
|
||||
// need these so we can update the state at the end of this method
|
||||
final double xf = mid[4], yf = mid[5];
|
||||
final double xf = x2, yf = y2;
|
||||
double dxs = mid[2] - mid[0];
|
||||
double dys = mid[3] - mid[1];
|
||||
double dxf = mid[4] - mid[2];
|
||||
@ -1060,6 +1223,10 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
|
||||
}
|
||||
if (dxs == 0.0d && dys == 0.0d) {
|
||||
// this happens if the "curve" is just a point
|
||||
// fix outcode0 for lineTo() call:
|
||||
if (clipRect != null) {
|
||||
this.cOutCode = outcode0;
|
||||
}
|
||||
lineTo(mid[0], mid[1]);
|
||||
return;
|
||||
}
|
||||
@ -1077,7 +1244,7 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
|
||||
}
|
||||
|
||||
computeOffset(dxs, dys, lineWidth2, offset0);
|
||||
drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]);
|
||||
drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
|
||||
|
||||
int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2);
|
||||
|
||||
@ -1112,214 +1279,16 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
|
||||
emitLineToRev(r[kind - 2], r[kind - 1]);
|
||||
}
|
||||
|
||||
this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
|
||||
this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
|
||||
this.cdx = dxf;
|
||||
this.cdy = dyf;
|
||||
this.prev = DRAWING_OP_TO;
|
||||
this.cx0 = xf;
|
||||
this.cy0 = yf;
|
||||
this.prev = DRAWING_OP_TO;
|
||||
this.cdx = dxf;
|
||||
this.cdy = dyf;
|
||||
this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
|
||||
this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
|
||||
}
|
||||
|
||||
@Override public long getNativeConsumer() {
|
||||
throw new InternalError("Stroker doesn't use a native consumer");
|
||||
}
|
||||
|
||||
// a stack of polynomial curves where each curve shares endpoints with
|
||||
// adjacent ones.
|
||||
static final class PolyStack {
|
||||
private static final byte TYPE_LINETO = (byte) 0;
|
||||
private static final byte TYPE_QUADTO = (byte) 1;
|
||||
private static final byte TYPE_CUBICTO = (byte) 2;
|
||||
|
||||
// curves capacity = edges count (8192) = edges x 2 (coords)
|
||||
private static final int INITIAL_CURVES_COUNT = INITIAL_EDGES_COUNT << 1;
|
||||
|
||||
// types capacity = edges count (4096)
|
||||
private static final int INITIAL_TYPES_COUNT = INITIAL_EDGES_COUNT;
|
||||
|
||||
double[] curves;
|
||||
int end;
|
||||
byte[] curveTypes;
|
||||
int numCurves;
|
||||
|
||||
// per-thread renderer context
|
||||
final DRendererContext rdrCtx;
|
||||
|
||||
// curves ref (dirty)
|
||||
final DoubleArrayCache.Reference curves_ref;
|
||||
// curveTypes ref (dirty)
|
||||
final ByteArrayCache.Reference curveTypes_ref;
|
||||
|
||||
// used marks (stats only)
|
||||
int curveTypesUseMark;
|
||||
int curvesUseMark;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param rdrCtx per-thread renderer context
|
||||
*/
|
||||
PolyStack(final DRendererContext rdrCtx) {
|
||||
this.rdrCtx = rdrCtx;
|
||||
|
||||
curves_ref = rdrCtx.newDirtyDoubleArrayRef(INITIAL_CURVES_COUNT); // 32K
|
||||
curves = curves_ref.initial;
|
||||
|
||||
curveTypes_ref = rdrCtx.newDirtyByteArrayRef(INITIAL_TYPES_COUNT); // 4K
|
||||
curveTypes = curveTypes_ref.initial;
|
||||
numCurves = 0;
|
||||
end = 0;
|
||||
|
||||
if (DO_STATS) {
|
||||
curveTypesUseMark = 0;
|
||||
curvesUseMark = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes this PolyStack:
|
||||
* clean up before reusing this instance
|
||||
*/
|
||||
void dispose() {
|
||||
end = 0;
|
||||
numCurves = 0;
|
||||
|
||||
if (DO_STATS) {
|
||||
rdrCtx.stats.stat_rdr_poly_stack_types.add(curveTypesUseMark);
|
||||
rdrCtx.stats.stat_rdr_poly_stack_curves.add(curvesUseMark);
|
||||
rdrCtx.stats.hist_rdr_poly_stack_curves.add(curvesUseMark);
|
||||
|
||||
// reset marks
|
||||
curveTypesUseMark = 0;
|
||||
curvesUseMark = 0;
|
||||
}
|
||||
|
||||
// Return arrays:
|
||||
// curves and curveTypes are kept dirty
|
||||
curves = curves_ref.putArray(curves);
|
||||
curveTypes = curveTypes_ref.putArray(curveTypes);
|
||||
}
|
||||
|
||||
private void ensureSpace(final int n) {
|
||||
// use substraction to avoid integer overflow:
|
||||
if (curves.length - end < n) {
|
||||
if (DO_STATS) {
|
||||
rdrCtx.stats.stat_array_stroker_polystack_curves
|
||||
.add(end + n);
|
||||
}
|
||||
curves = curves_ref.widenArray(curves, end, end + n);
|
||||
}
|
||||
if (curveTypes.length <= numCurves) {
|
||||
if (DO_STATS) {
|
||||
rdrCtx.stats.stat_array_stroker_polystack_curveTypes
|
||||
.add(numCurves + 1);
|
||||
}
|
||||
curveTypes = curveTypes_ref.widenArray(curveTypes,
|
||||
numCurves,
|
||||
numCurves + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void pushCubic(double x0, double y0,
|
||||
double x1, double y1,
|
||||
double x2, double y2)
|
||||
{
|
||||
ensureSpace(6);
|
||||
curveTypes[numCurves++] = TYPE_CUBICTO;
|
||||
// we reverse the coordinate order to make popping easier
|
||||
final double[] _curves = curves;
|
||||
int e = end;
|
||||
_curves[e++] = x2; _curves[e++] = y2;
|
||||
_curves[e++] = x1; _curves[e++] = y1;
|
||||
_curves[e++] = x0; _curves[e++] = y0;
|
||||
end = e;
|
||||
}
|
||||
|
||||
void pushQuad(double x0, double y0,
|
||||
double x1, double y1)
|
||||
{
|
||||
ensureSpace(4);
|
||||
curveTypes[numCurves++] = TYPE_QUADTO;
|
||||
final double[] _curves = curves;
|
||||
int e = end;
|
||||
_curves[e++] = x1; _curves[e++] = y1;
|
||||
_curves[e++] = x0; _curves[e++] = y0;
|
||||
end = e;
|
||||
}
|
||||
|
||||
void pushLine(double x, double y) {
|
||||
ensureSpace(2);
|
||||
curveTypes[numCurves++] = TYPE_LINETO;
|
||||
curves[end++] = x; curves[end++] = y;
|
||||
}
|
||||
|
||||
void popAll(DPathConsumer2D io) {
|
||||
if (DO_STATS) {
|
||||
// update used marks:
|
||||
if (numCurves > curveTypesUseMark) {
|
||||
curveTypesUseMark = numCurves;
|
||||
}
|
||||
if (end > curvesUseMark) {
|
||||
curvesUseMark = end;
|
||||
}
|
||||
}
|
||||
final byte[] _curveTypes = curveTypes;
|
||||
final double[] _curves = curves;
|
||||
int nc = numCurves;
|
||||
int e = end;
|
||||
|
||||
while (nc != 0) {
|
||||
switch(_curveTypes[--nc]) {
|
||||
case TYPE_LINETO:
|
||||
e -= 2;
|
||||
io.lineTo(_curves[e], _curves[e+1]);
|
||||
continue;
|
||||
case TYPE_QUADTO:
|
||||
e -= 4;
|
||||
io.quadTo(_curves[e+0], _curves[e+1],
|
||||
_curves[e+2], _curves[e+3]);
|
||||
continue;
|
||||
case TYPE_CUBICTO:
|
||||
e -= 6;
|
||||
io.curveTo(_curves[e+0], _curves[e+1],
|
||||
_curves[e+2], _curves[e+3],
|
||||
_curves[e+4], _curves[e+5]);
|
||||
continue;
|
||||
default:
|
||||
}
|
||||
}
|
||||
numCurves = 0;
|
||||
end = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String ret = "";
|
||||
int nc = numCurves;
|
||||
int last = end;
|
||||
int len;
|
||||
while (nc != 0) {
|
||||
switch(curveTypes[--nc]) {
|
||||
case TYPE_LINETO:
|
||||
len = 2;
|
||||
ret += "line: ";
|
||||
break;
|
||||
case TYPE_QUADTO:
|
||||
len = 4;
|
||||
ret += "quad: ";
|
||||
break;
|
||||
case TYPE_CUBICTO:
|
||||
len = 6;
|
||||
ret += "cubic: ";
|
||||
break;
|
||||
default:
|
||||
len = 0;
|
||||
}
|
||||
last -= len;
|
||||
ret += Arrays.toString(Arrays.copyOfRange(curves, last, last+len))
|
||||
+ "\n";
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,50 +27,169 @@ package sun.java2d.marlin;
|
||||
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Path2D;
|
||||
import sun.java2d.marlin.DHelpers.IndexStack;
|
||||
import sun.java2d.marlin.DHelpers.PolyStack;
|
||||
|
||||
final class DTransformingPathConsumer2D {
|
||||
|
||||
DTransformingPathConsumer2D() {
|
||||
// used by DRendererContext
|
||||
}
|
||||
private final DRendererContext rdrCtx;
|
||||
|
||||
// recycled DPathConsumer2D instance from wrapPath2d()
|
||||
// recycled ClosedPathDetector instance from detectClosedPath()
|
||||
private final ClosedPathDetector cpDetector;
|
||||
|
||||
// recycled PathClipFilter instance from pathClipper()
|
||||
private final PathClipFilter pathClipper;
|
||||
|
||||
// recycled DPathConsumer2D instance from wrapPath2D()
|
||||
private final Path2DWrapper wp_Path2DWrapper = new Path2DWrapper();
|
||||
|
||||
DPathConsumer2D wrapPath2d(Path2D.Double p2d)
|
||||
{
|
||||
return wp_Path2DWrapper.init(p2d);
|
||||
}
|
||||
|
||||
// recycled DPathConsumer2D instances from deltaTransformConsumer()
|
||||
private final DeltaScaleFilter dt_DeltaScaleFilter = new DeltaScaleFilter();
|
||||
private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter();
|
||||
|
||||
// recycled DPathConsumer2D instances from inverseDeltaTransformConsumer()
|
||||
private final DeltaScaleFilter iv_DeltaScaleFilter = new DeltaScaleFilter();
|
||||
private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter();
|
||||
|
||||
// recycled PathTracer instances from tracer...() methods
|
||||
private final PathTracer tracerInput = new PathTracer("[Input]");
|
||||
private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector");
|
||||
private final PathTracer tracerFiller = new PathTracer("Filler");
|
||||
private final PathTracer tracerStroker = new PathTracer("Stroker");
|
||||
|
||||
DTransformingPathConsumer2D(final DRendererContext rdrCtx) {
|
||||
// used by RendererContext
|
||||
this.rdrCtx = rdrCtx;
|
||||
this.cpDetector = new ClosedPathDetector(rdrCtx);
|
||||
this.pathClipper = new PathClipFilter(rdrCtx);
|
||||
}
|
||||
|
||||
DPathConsumer2D wrapPath2D(Path2D.Double p2d) {
|
||||
return wp_Path2DWrapper.init(p2d);
|
||||
}
|
||||
|
||||
DPathConsumer2D traceInput(DPathConsumer2D out) {
|
||||
return tracerInput.init(out);
|
||||
}
|
||||
|
||||
DPathConsumer2D traceClosedPathDetector(DPathConsumer2D out) {
|
||||
return tracerCPDetector.init(out);
|
||||
}
|
||||
|
||||
DPathConsumer2D traceFiller(DPathConsumer2D out) {
|
||||
return tracerFiller.init(out);
|
||||
}
|
||||
|
||||
DPathConsumer2D traceStroker(DPathConsumer2D out) {
|
||||
return tracerStroker.init(out);
|
||||
}
|
||||
|
||||
DPathConsumer2D detectClosedPath(DPathConsumer2D out) {
|
||||
return cpDetector.init(out);
|
||||
}
|
||||
|
||||
DPathConsumer2D pathClipper(DPathConsumer2D out) {
|
||||
return pathClipper.init(out);
|
||||
}
|
||||
|
||||
DPathConsumer2D deltaTransformConsumer(DPathConsumer2D out,
|
||||
AffineTransform at)
|
||||
{
|
||||
if (at == null) {
|
||||
return out;
|
||||
}
|
||||
double mxx = at.getScaleX();
|
||||
double mxy = at.getShearX();
|
||||
double myx = at.getShearY();
|
||||
double myy = at.getScaleY();
|
||||
final double mxx = at.getScaleX();
|
||||
final double mxy = at.getShearX();
|
||||
final double myx = at.getShearY();
|
||||
final double myy = at.getScaleY();
|
||||
|
||||
if (mxy == 0.0d && myx == 0.0d) {
|
||||
if (mxx == 1.0d && myy == 1.0d) {
|
||||
return out;
|
||||
} else {
|
||||
// Scale only
|
||||
if (rdrCtx.doClip) {
|
||||
// adjust clip rectangle (ymin, ymax, xmin, xmax):
|
||||
adjustClipScale(rdrCtx.clipRect, mxx, myy);
|
||||
}
|
||||
return dt_DeltaScaleFilter.init(out, mxx, myy);
|
||||
}
|
||||
} else {
|
||||
if (rdrCtx.doClip) {
|
||||
// adjust clip rectangle (ymin, ymax, xmin, xmax):
|
||||
adjustClipInverseDelta(rdrCtx.clipRect, mxx, mxy, myx, myy);
|
||||
}
|
||||
return dt_DeltaTransformFilter.init(out, mxx, mxy, myx, myy);
|
||||
}
|
||||
}
|
||||
|
||||
// recycled DPathConsumer2D instances from inverseDeltaTransformConsumer()
|
||||
private final DeltaScaleFilter iv_DeltaScaleFilter = new DeltaScaleFilter();
|
||||
private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter();
|
||||
private static void adjustClipOffset(final double[] clipRect) {
|
||||
clipRect[0] += Renderer.RDR_OFFSET_Y;
|
||||
clipRect[1] += Renderer.RDR_OFFSET_Y;
|
||||
clipRect[2] += Renderer.RDR_OFFSET_X;
|
||||
clipRect[3] += Renderer.RDR_OFFSET_X;
|
||||
}
|
||||
|
||||
private static void adjustClipScale(final double[] clipRect,
|
||||
final double mxx, final double myy)
|
||||
{
|
||||
adjustClipOffset(clipRect);
|
||||
|
||||
// Adjust the clipping rectangle (iv_DeltaScaleFilter):
|
||||
clipRect[0] /= myy;
|
||||
clipRect[1] /= myy;
|
||||
clipRect[2] /= mxx;
|
||||
clipRect[3] /= mxx;
|
||||
}
|
||||
|
||||
private static void adjustClipInverseDelta(final double[] clipRect,
|
||||
final double mxx, final double mxy,
|
||||
final double myx, final double myy)
|
||||
{
|
||||
adjustClipOffset(clipRect);
|
||||
|
||||
// Adjust the clipping rectangle (iv_DeltaTransformFilter):
|
||||
final double det = mxx * myy - mxy * myx;
|
||||
final double imxx = myy / det;
|
||||
final double imxy = -mxy / det;
|
||||
final double imyx = -myx / det;
|
||||
final double imyy = mxx / det;
|
||||
|
||||
double xmin, xmax, ymin, ymax;
|
||||
double x, y;
|
||||
// xmin, ymin:
|
||||
x = clipRect[2] * imxx + clipRect[0] * imxy;
|
||||
y = clipRect[2] * imyx + clipRect[0] * imyy;
|
||||
|
||||
xmin = xmax = x;
|
||||
ymin = ymax = y;
|
||||
|
||||
// xmax, ymin:
|
||||
x = clipRect[3] * imxx + clipRect[0] * imxy;
|
||||
y = clipRect[3] * imyx + clipRect[0] * imyy;
|
||||
|
||||
if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; }
|
||||
if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; }
|
||||
|
||||
// xmin, ymax:
|
||||
x = clipRect[2] * imxx + clipRect[1] * imxy;
|
||||
y = clipRect[2] * imyx + clipRect[1] * imyy;
|
||||
|
||||
if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; }
|
||||
if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; }
|
||||
|
||||
// xmax, ymax:
|
||||
x = clipRect[3] * imxx + clipRect[1] * imxy;
|
||||
y = clipRect[3] * imyx + clipRect[1] * imyy;
|
||||
|
||||
if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; }
|
||||
if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; }
|
||||
|
||||
clipRect[0] = ymin;
|
||||
clipRect[1] = ymax;
|
||||
clipRect[2] = xmin;
|
||||
clipRect[3] = xmax;
|
||||
}
|
||||
|
||||
DPathConsumer2D inverseDeltaTransformConsumer(DPathConsumer2D out,
|
||||
AffineTransform at)
|
||||
@ -90,7 +209,7 @@ final class DTransformingPathConsumer2D {
|
||||
return iv_DeltaScaleFilter.init(out, 1.0d/mxx, 1.0d/myy);
|
||||
}
|
||||
} else {
|
||||
double det = mxx * myy - mxy * myx;
|
||||
final double det = mxx * myy - mxy * myx;
|
||||
return iv_DeltaTransformFilter.init(out,
|
||||
myy / det,
|
||||
-mxy / det,
|
||||
@ -99,7 +218,6 @@ final class DTransformingPathConsumer2D {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static final class DeltaScaleFilter implements DPathConsumer2D {
|
||||
private DPathConsumer2D out;
|
||||
private double sx, sy;
|
||||
@ -274,4 +392,427 @@ final class DTransformingPathConsumer2D {
|
||||
throw new InternalError("Not using a native peer");
|
||||
}
|
||||
}
|
||||
|
||||
static final class ClosedPathDetector implements DPathConsumer2D {
|
||||
|
||||
private final DRendererContext rdrCtx;
|
||||
private final PolyStack stack;
|
||||
|
||||
private DPathConsumer2D out;
|
||||
|
||||
ClosedPathDetector(final DRendererContext rdrCtx) {
|
||||
this.rdrCtx = rdrCtx;
|
||||
this.stack = (rdrCtx.stats != null) ?
|
||||
new PolyStack(rdrCtx,
|
||||
rdrCtx.stats.stat_cpd_polystack_types,
|
||||
rdrCtx.stats.stat_cpd_polystack_curves,
|
||||
rdrCtx.stats.hist_cpd_polystack_curves,
|
||||
rdrCtx.stats.stat_array_cpd_polystack_curves,
|
||||
rdrCtx.stats.stat_array_cpd_polystack_types)
|
||||
: new PolyStack(rdrCtx);
|
||||
}
|
||||
|
||||
ClosedPathDetector init(DPathConsumer2D out) {
|
||||
this.out = out;
|
||||
return this; // fluent API
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes this instance:
|
||||
* clean up before reusing this instance
|
||||
*/
|
||||
void dispose() {
|
||||
stack.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pathDone() {
|
||||
// previous path is not closed:
|
||||
finish(false);
|
||||
out.pathDone();
|
||||
|
||||
// TODO: fix possible leak if exception happened
|
||||
// Dispose this instance:
|
||||
dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closePath() {
|
||||
// path is closed
|
||||
finish(true);
|
||||
out.closePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveTo(double x0, double y0) {
|
||||
// previous path is not closed:
|
||||
finish(false);
|
||||
out.moveTo(x0, y0);
|
||||
}
|
||||
|
||||
private void finish(final boolean closed) {
|
||||
rdrCtx.closedPath = closed;
|
||||
stack.pullAll(out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lineTo(double x1, double y1) {
|
||||
stack.pushLine(x1, y1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void curveTo(double x3, double y3,
|
||||
double x2, double y2,
|
||||
double x1, double y1)
|
||||
{
|
||||
stack.pushCubic(x1, y1, x2, y2, x3, y3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void quadTo(double x2, double y2, double x1, double y1) {
|
||||
stack.pushQuad(x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNativeConsumer() {
|
||||
throw new InternalError("Not using a native peer");
|
||||
}
|
||||
}
|
||||
|
||||
static final class PathClipFilter implements DPathConsumer2D {
|
||||
|
||||
private DPathConsumer2D out;
|
||||
|
||||
// Bounds of the drawing region, at pixel precision.
|
||||
private final double[] clipRect;
|
||||
|
||||
private final double[] corners = new double[8];
|
||||
private boolean init_corners = false;
|
||||
|
||||
private final IndexStack stack;
|
||||
|
||||
// the current outcode of the current sub path
|
||||
private int cOutCode = 0;
|
||||
|
||||
// the cumulated (and) outcode of the complete path
|
||||
private int gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
|
||||
|
||||
private boolean outside = false;
|
||||
|
||||
// The current point OUTSIDE
|
||||
private double cx0, cy0;
|
||||
|
||||
PathClipFilter(final DRendererContext rdrCtx) {
|
||||
this.clipRect = rdrCtx.clipRect;
|
||||
this.stack = (rdrCtx.stats != null) ?
|
||||
new IndexStack(rdrCtx,
|
||||
rdrCtx.stats.stat_pcf_idxstack_indices,
|
||||
rdrCtx.stats.hist_pcf_idxstack_indices,
|
||||
rdrCtx.stats.stat_array_pcf_idxstack_indices)
|
||||
: new IndexStack(rdrCtx);
|
||||
}
|
||||
|
||||
PathClipFilter init(final DPathConsumer2D out) {
|
||||
this.out = out;
|
||||
|
||||
// Adjust the clipping rectangle with the renderer offsets
|
||||
final double rdrOffX = DRenderer.RDR_OFFSET_X;
|
||||
final double rdrOffY = DRenderer.RDR_OFFSET_Y;
|
||||
|
||||
// add a small rounding error:
|
||||
final double margin = 1e-3d;
|
||||
|
||||
final double[] _clipRect = this.clipRect;
|
||||
_clipRect[0] -= margin - rdrOffY;
|
||||
_clipRect[1] += margin + rdrOffY;
|
||||
_clipRect[2] -= margin - rdrOffX;
|
||||
_clipRect[3] += margin + rdrOffX;
|
||||
|
||||
this.init_corners = true;
|
||||
this.gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
|
||||
|
||||
return this; // fluent API
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes this instance:
|
||||
* clean up before reusing this instance
|
||||
*/
|
||||
void dispose() {
|
||||
stack.dispose();
|
||||
}
|
||||
|
||||
private void finishPath() {
|
||||
if (outside) {
|
||||
// criteria: inside or totally outside ?
|
||||
if (gOutCode == 0) {
|
||||
finish();
|
||||
} else {
|
||||
this.outside = false;
|
||||
stack.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void finish() {
|
||||
this.outside = false;
|
||||
|
||||
if (!stack.isEmpty()) {
|
||||
if (init_corners) {
|
||||
init_corners = false;
|
||||
|
||||
final double[] _corners = corners;
|
||||
final double[] _clipRect = clipRect;
|
||||
// Top Left (0):
|
||||
_corners[0] = _clipRect[2];
|
||||
_corners[1] = _clipRect[0];
|
||||
// Bottom Left (1):
|
||||
_corners[2] = _clipRect[2];
|
||||
_corners[3] = _clipRect[1];
|
||||
// Top right (2):
|
||||
_corners[4] = _clipRect[3];
|
||||
_corners[5] = _clipRect[0];
|
||||
// Bottom Right (3):
|
||||
_corners[6] = _clipRect[3];
|
||||
_corners[7] = _clipRect[1];
|
||||
}
|
||||
stack.pullAll(corners, out);
|
||||
}
|
||||
out.lineTo(cx0, cy0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pathDone() {
|
||||
finishPath();
|
||||
|
||||
out.pathDone();
|
||||
|
||||
// TODO: fix possible leak if exception happened
|
||||
// Dispose this instance:
|
||||
dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closePath() {
|
||||
finishPath();
|
||||
|
||||
out.closePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveTo(final double x0, final double y0) {
|
||||
finishPath();
|
||||
|
||||
final int outcode = DHelpers.outcode(x0, y0, clipRect);
|
||||
this.cOutCode = outcode;
|
||||
this.outside = false;
|
||||
out.moveTo(x0, y0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lineTo(final double xe, final double ye) {
|
||||
final int outcode0 = this.cOutCode;
|
||||
final int outcode1 = DHelpers.outcode(xe, ye, clipRect);
|
||||
this.cOutCode = outcode1;
|
||||
|
||||
final int sideCode = (outcode0 & outcode1);
|
||||
|
||||
// basic rejection criteria:
|
||||
if (sideCode == 0) {
|
||||
this.gOutCode = 0;
|
||||
} else {
|
||||
this.gOutCode &= sideCode;
|
||||
// keep last point coordinate before entering the clip again:
|
||||
this.outside = true;
|
||||
this.cx0 = xe;
|
||||
this.cy0 = ye;
|
||||
|
||||
clip(sideCode, outcode0, outcode1);
|
||||
return;
|
||||
}
|
||||
if (outside) {
|
||||
finish();
|
||||
}
|
||||
// clipping disabled:
|
||||
out.lineTo(xe, ye);
|
||||
}
|
||||
|
||||
private void clip(final int sideCode,
|
||||
final int outcode0,
|
||||
final int outcode1)
|
||||
{
|
||||
// corner or cross-boundary on left or right side:
|
||||
if ((outcode0 != outcode1)
|
||||
&& ((sideCode & MarlinConst.OUTCODE_MASK_L_R) != 0))
|
||||
{
|
||||
// combine outcodes:
|
||||
final int mergeCode = (outcode0 | outcode1);
|
||||
final int tbCode = mergeCode & MarlinConst.OUTCODE_MASK_T_B;
|
||||
final int lrCode = mergeCode & MarlinConst.OUTCODE_MASK_L_R;
|
||||
final int off = (lrCode == MarlinConst.OUTCODE_LEFT) ? 0 : 2;
|
||||
|
||||
// add corners to outside stack:
|
||||
switch (tbCode) {
|
||||
case MarlinConst.OUTCODE_TOP:
|
||||
// System.out.println("TOP "+ ((off == 0) ? "LEFT" : "RIGHT"));
|
||||
stack.push(off); // top
|
||||
return;
|
||||
case MarlinConst.OUTCODE_BOTTOM:
|
||||
// System.out.println("BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT"));
|
||||
stack.push(off + 1); // bottom
|
||||
return;
|
||||
default:
|
||||
// both TOP / BOTTOM:
|
||||
if ((outcode0 & MarlinConst.OUTCODE_TOP) != 0) {
|
||||
// System.out.println("TOP + BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT"));
|
||||
// top to bottom
|
||||
stack.push(off); // top
|
||||
stack.push(off + 1); // bottom
|
||||
} else {
|
||||
// System.out.println("BOTTOM + TOP "+ ((off == 0) ? "LEFT" : "RIGHT"));
|
||||
// bottom to top
|
||||
stack.push(off + 1); // bottom
|
||||
stack.push(off); // top
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void curveTo(final double x1, final double y1,
|
||||
final double x2, final double y2,
|
||||
final double xe, final double ye)
|
||||
{
|
||||
final int outcode0 = this.cOutCode;
|
||||
final int outcode3 = DHelpers.outcode(xe, ye, clipRect);
|
||||
this.cOutCode = outcode3;
|
||||
|
||||
int sideCode = outcode0 & outcode3;
|
||||
|
||||
if (sideCode == 0) {
|
||||
this.gOutCode = 0;
|
||||
} else {
|
||||
sideCode &= DHelpers.outcode(x1, y1, clipRect);
|
||||
sideCode &= DHelpers.outcode(x2, y2, clipRect);
|
||||
this.gOutCode &= sideCode;
|
||||
|
||||
// basic rejection criteria:
|
||||
if (sideCode != 0) {
|
||||
// keep last point coordinate before entering the clip again:
|
||||
this.outside = true;
|
||||
this.cx0 = xe;
|
||||
this.cy0 = ye;
|
||||
|
||||
clip(sideCode, outcode0, outcode3);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (outside) {
|
||||
finish();
|
||||
}
|
||||
// clipping disabled:
|
||||
out.curveTo(x1, y1, x2, y2, xe, ye);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void quadTo(final double x1, final double y1,
|
||||
final double xe, final double ye)
|
||||
{
|
||||
final int outcode0 = this.cOutCode;
|
||||
final int outcode2 = DHelpers.outcode(xe, ye, clipRect);
|
||||
this.cOutCode = outcode2;
|
||||
|
||||
int sideCode = outcode0 & outcode2;
|
||||
|
||||
if (sideCode == 0) {
|
||||
this.gOutCode = 0;
|
||||
} else {
|
||||
sideCode &= DHelpers.outcode(x1, y1, clipRect);
|
||||
this.gOutCode &= sideCode;
|
||||
|
||||
// basic rejection criteria:
|
||||
if (sideCode != 0) {
|
||||
// keep last point coordinate before entering the clip again:
|
||||
this.outside = true;
|
||||
this.cx0 = xe;
|
||||
this.cy0 = ye;
|
||||
|
||||
clip(sideCode, outcode0, outcode2);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (outside) {
|
||||
finish();
|
||||
}
|
||||
// clipping disabled:
|
||||
out.quadTo(x1, y1, xe, ye);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNativeConsumer() {
|
||||
throw new InternalError("Not using a native peer");
|
||||
}
|
||||
}
|
||||
|
||||
static final class PathTracer implements DPathConsumer2D {
|
||||
private final String prefix;
|
||||
private DPathConsumer2D out;
|
||||
|
||||
PathTracer(String name) {
|
||||
this.prefix = name + ": ";
|
||||
}
|
||||
|
||||
PathTracer init(DPathConsumer2D out) {
|
||||
this.out = out;
|
||||
return this; // fluent API
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveTo(double x0, double y0) {
|
||||
log("moveTo (" + x0 + ", " + y0 + ')');
|
||||
out.moveTo(x0, y0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lineTo(double x1, double y1) {
|
||||
log("lineTo (" + x1 + ", " + y1 + ')');
|
||||
out.lineTo(x1, y1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void curveTo(double x1, double y1,
|
||||
double x2, double y2,
|
||||
double x3, double y3)
|
||||
{
|
||||
log("curveTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ')');
|
||||
out.curveTo(x1, y1, x2, y2, x3, y3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void quadTo(double x1, double y1, double x2, double y2) {
|
||||
log("quadTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ')');
|
||||
out.quadTo(x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closePath() {
|
||||
log("closePath");
|
||||
out.closePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pathDone() {
|
||||
log("pathDone");
|
||||
out.pathDone();
|
||||
}
|
||||
|
||||
private void log(final String message) {
|
||||
System.out.println(prefix + message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNativeConsumer() {
|
||||
throw new InternalError("Not using a native peer");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -138,7 +138,7 @@ final class Dasher implements PathConsumer2D, MarlinConst {
|
||||
dashOn = !dashOn;
|
||||
}
|
||||
}
|
||||
} else if (phase > 0) {
|
||||
} else if (phase > 0.0f) {
|
||||
if (cycles >= MAX_CYCLES) {
|
||||
phase = 0.0f;
|
||||
} else {
|
||||
@ -158,12 +158,13 @@ final class Dasher implements PathConsumer2D, MarlinConst {
|
||||
|
||||
this.dash = dash;
|
||||
this.dashLen = dashLen;
|
||||
this.startPhase = this.phase = phase;
|
||||
this.phase = phase;
|
||||
this.startPhase = phase;
|
||||
this.startDashOn = dashOn;
|
||||
this.startIdx = sidx;
|
||||
this.starting = true;
|
||||
needsMoveTo = false;
|
||||
firstSegidx = 0;
|
||||
this.needsMoveTo = false;
|
||||
this.firstSegidx = 0;
|
||||
|
||||
this.recycleDashes = recycleDashes;
|
||||
|
||||
@ -202,8 +203,8 @@ final class Dasher implements PathConsumer2D, MarlinConst {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveTo(float x0, float y0) {
|
||||
if (firstSegidx > 0) {
|
||||
public void moveTo(final float x0, final float y0) {
|
||||
if (firstSegidx != 0) {
|
||||
out.moveTo(sx, sy);
|
||||
emitFirstSegments();
|
||||
}
|
||||
@ -211,8 +212,10 @@ final class Dasher implements PathConsumer2D, MarlinConst {
|
||||
this.idx = startIdx;
|
||||
this.dashOn = this.startDashOn;
|
||||
this.phase = this.startPhase;
|
||||
this.sx = this.x0 = x0;
|
||||
this.sy = this.y0 = y0;
|
||||
this.sx = x0;
|
||||
this.sy = y0;
|
||||
this.x0 = x0;
|
||||
this.y0 = y0;
|
||||
this.starting = true;
|
||||
}
|
||||
|
||||
@ -237,7 +240,7 @@ final class Dasher implements PathConsumer2D, MarlinConst {
|
||||
private void emitFirstSegments() {
|
||||
final float[] fSegBuf = firstSegmentsBuffer;
|
||||
|
||||
for (int i = 0; i < firstSegidx; ) {
|
||||
for (int i = 0, len = firstSegidx; i < len; ) {
|
||||
int type = (int)fSegBuf[i];
|
||||
emitSeg(fSegBuf, i + 1, type);
|
||||
i += (type - 1);
|
||||
@ -252,48 +255,59 @@ final class Dasher implements PathConsumer2D, MarlinConst {
|
||||
private int firstSegidx;
|
||||
|
||||
// precondition: pts must be in relative coordinates (relative to x0,y0)
|
||||
private void goTo(float[] pts, int off, final int type) {
|
||||
float x = pts[off + type - 4];
|
||||
float y = pts[off + type - 3];
|
||||
if (dashOn) {
|
||||
private void goTo(final float[] pts, final int off, final int type,
|
||||
final boolean on)
|
||||
{
|
||||
final int index = off + type;
|
||||
final float x = pts[index - 4];
|
||||
final float y = pts[index - 3];
|
||||
|
||||
if (on) {
|
||||
if (starting) {
|
||||
int len = type - 1; // - 2 + 1
|
||||
int segIdx = firstSegidx;
|
||||
float[] buf = firstSegmentsBuffer;
|
||||
if (segIdx + len > buf.length) {
|
||||
if (DO_STATS) {
|
||||
rdrCtx.stats.stat_array_dasher_firstSegmentsBuffer
|
||||
.add(segIdx + len);
|
||||
}
|
||||
firstSegmentsBuffer = buf
|
||||
= firstSegmentsBuffer_ref.widenArray(buf, segIdx,
|
||||
segIdx + len);
|
||||
}
|
||||
buf[segIdx++] = type;
|
||||
len--;
|
||||
// small arraycopy (2, 4 or 6) but with offset:
|
||||
System.arraycopy(pts, off, buf, segIdx, len);
|
||||
segIdx += len;
|
||||
firstSegidx = segIdx;
|
||||
goTo_starting(pts, off, type);
|
||||
} else {
|
||||
if (needsMoveTo) {
|
||||
out.moveTo(x0, y0);
|
||||
needsMoveTo = false;
|
||||
out.moveTo(x0, y0);
|
||||
}
|
||||
emitSeg(pts, off, type);
|
||||
}
|
||||
} else {
|
||||
starting = false;
|
||||
if (starting) {
|
||||
// low probability test (hotspot)
|
||||
starting = false;
|
||||
}
|
||||
needsMoveTo = true;
|
||||
}
|
||||
this.x0 = x;
|
||||
this.y0 = y;
|
||||
}
|
||||
|
||||
private void goTo_starting(final float[] pts, final int off, final int type) {
|
||||
int len = type - 1; // - 2 + 1
|
||||
int segIdx = firstSegidx;
|
||||
float[] buf = firstSegmentsBuffer;
|
||||
|
||||
if (segIdx + len > buf.length) {
|
||||
if (DO_STATS) {
|
||||
rdrCtx.stats.stat_array_dasher_firstSegmentsBuffer
|
||||
.add(segIdx + len);
|
||||
}
|
||||
firstSegmentsBuffer = buf
|
||||
= firstSegmentsBuffer_ref.widenArray(buf, segIdx,
|
||||
segIdx + len);
|
||||
}
|
||||
buf[segIdx++] = type;
|
||||
len--;
|
||||
// small arraycopy (2, 4 or 6) but with offset:
|
||||
System.arraycopy(pts, off, buf, segIdx, len);
|
||||
firstSegidx = segIdx + len;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lineTo(float x1, float y1) {
|
||||
float dx = x1 - x0;
|
||||
float dy = y1 - y0;
|
||||
public void lineTo(final float x1, final float y1) {
|
||||
final float dx = x1 - x0;
|
||||
final float dy = y1 - y0;
|
||||
|
||||
float len = dx*dx + dy*dy;
|
||||
if (len == 0.0f) {
|
||||
@ -308,48 +322,61 @@ final class Dasher implements PathConsumer2D, MarlinConst {
|
||||
|
||||
final float[] _curCurvepts = curCurvepts;
|
||||
final float[] _dash = dash;
|
||||
final int _dashLen = this.dashLen;
|
||||
|
||||
int _idx = idx;
|
||||
boolean _dashOn = dashOn;
|
||||
float _phase = phase;
|
||||
|
||||
float leftInThisDashSegment;
|
||||
float dashdx, dashdy, p;
|
||||
float d, dashdx, dashdy, p;
|
||||
|
||||
while (true) {
|
||||
leftInThisDashSegment = _dash[idx] - phase;
|
||||
d = _dash[_idx];
|
||||
leftInThisDashSegment = d - _phase;
|
||||
|
||||
if (len <= leftInThisDashSegment) {
|
||||
_curCurvepts[0] = x1;
|
||||
_curCurvepts[1] = y1;
|
||||
goTo(_curCurvepts, 0, 4);
|
||||
|
||||
goTo(_curCurvepts, 0, 4, _dashOn);
|
||||
|
||||
// Advance phase within current dash segment
|
||||
phase += len;
|
||||
_phase += len;
|
||||
|
||||
// TODO: compare float values using epsilon:
|
||||
if (len == leftInThisDashSegment) {
|
||||
phase = 0.0f;
|
||||
idx = (idx + 1) % dashLen;
|
||||
dashOn = !dashOn;
|
||||
_phase = 0.0f;
|
||||
_idx = (_idx + 1) % _dashLen;
|
||||
_dashOn = !_dashOn;
|
||||
}
|
||||
|
||||
// Save local state:
|
||||
idx = _idx;
|
||||
dashOn = _dashOn;
|
||||
phase = _phase;
|
||||
return;
|
||||
}
|
||||
|
||||
dashdx = _dash[idx] * cx;
|
||||
dashdy = _dash[idx] * cy;
|
||||
dashdx = d * cx;
|
||||
dashdy = d * cy;
|
||||
|
||||
if (phase == 0.0f) {
|
||||
if (_phase == 0.0f) {
|
||||
_curCurvepts[0] = x0 + dashdx;
|
||||
_curCurvepts[1] = y0 + dashdy;
|
||||
} else {
|
||||
p = leftInThisDashSegment / _dash[idx];
|
||||
p = leftInThisDashSegment / d;
|
||||
_curCurvepts[0] = x0 + p * dashdx;
|
||||
_curCurvepts[1] = y0 + p * dashdy;
|
||||
}
|
||||
|
||||
goTo(_curCurvepts, 0, 4);
|
||||
goTo(_curCurvepts, 0, 4, _dashOn);
|
||||
|
||||
len -= leftInThisDashSegment;
|
||||
// Advance to next dash segment
|
||||
idx = (idx + 1) % dashLen;
|
||||
dashOn = !dashOn;
|
||||
phase = 0.0f;
|
||||
_idx = (_idx + 1) % _dashLen;
|
||||
_dashOn = !_dashOn;
|
||||
_phase = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
@ -358,43 +385,59 @@ final class Dasher implements PathConsumer2D, MarlinConst {
|
||||
|
||||
// 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) {
|
||||
private void somethingTo(final int type) {
|
||||
if (pointCurve(curCurvepts, type)) {
|
||||
return;
|
||||
}
|
||||
li.initializeIterationOnCurve(curCurvepts, type);
|
||||
final LengthIterator _li = li;
|
||||
final float[] _curCurvepts = curCurvepts;
|
||||
final float[] _dash = dash;
|
||||
final int _dashLen = this.dashLen;
|
||||
|
||||
_li.initializeIterationOnCurve(_curCurvepts, type);
|
||||
|
||||
int _idx = idx;
|
||||
boolean _dashOn = dashOn;
|
||||
float _phase = phase;
|
||||
|
||||
// initially the current curve is at curCurvepts[0...type]
|
||||
int curCurveoff = 0;
|
||||
float lastSplitT = 0.0f;
|
||||
float t;
|
||||
float leftInThisDashSegment = dash[idx] - phase;
|
||||
float leftInThisDashSegment = _dash[_idx] - _phase;
|
||||
|
||||
while ((t = li.next(leftInThisDashSegment)) < 1.0f) {
|
||||
while ((t = _li.next(leftInThisDashSegment)) < 1.0f) {
|
||||
if (t != 0.0f) {
|
||||
Helpers.subdivideAt((t - lastSplitT) / (1.0f - lastSplitT),
|
||||
curCurvepts, curCurveoff,
|
||||
curCurvepts, 0,
|
||||
curCurvepts, type, type);
|
||||
_curCurvepts, curCurveoff,
|
||||
_curCurvepts, 0,
|
||||
_curCurvepts, type, type);
|
||||
lastSplitT = t;
|
||||
goTo(curCurvepts, 2, type);
|
||||
goTo(_curCurvepts, 2, type, _dashOn);
|
||||
curCurveoff = type;
|
||||
}
|
||||
// Advance to next dash segment
|
||||
idx = (idx + 1) % dashLen;
|
||||
dashOn = !dashOn;
|
||||
phase = 0.0f;
|
||||
leftInThisDashSegment = dash[idx];
|
||||
_idx = (_idx + 1) % _dashLen;
|
||||
_dashOn = !_dashOn;
|
||||
_phase = 0.0f;
|
||||
leftInThisDashSegment = _dash[_idx];
|
||||
}
|
||||
goTo(curCurvepts, curCurveoff+2, type);
|
||||
phase += li.lastSegLen();
|
||||
if (phase >= dash[idx]) {
|
||||
phase = 0.0f;
|
||||
idx = (idx + 1) % dashLen;
|
||||
dashOn = !dashOn;
|
||||
|
||||
goTo(_curCurvepts, curCurveoff + 2, type, _dashOn);
|
||||
|
||||
_phase += _li.lastSegLen();
|
||||
if (_phase >= _dash[_idx]) {
|
||||
_phase = 0.0f;
|
||||
_idx = (_idx + 1) % _dashLen;
|
||||
_dashOn = !_dashOn;
|
||||
}
|
||||
// Save local state:
|
||||
idx = _idx;
|
||||
dashOn = _dashOn;
|
||||
phase = _phase;
|
||||
|
||||
// reset LengthIterator:
|
||||
li.reset();
|
||||
_li.reset();
|
||||
}
|
||||
|
||||
private static boolean pointCurve(float[] curve, int type) {
|
||||
@ -420,7 +463,7 @@ final class Dasher implements PathConsumer2D, MarlinConst {
|
||||
// tree; however, the trees we are interested in have the property that
|
||||
// every non leaf node has exactly 2 children
|
||||
static final class LengthIterator {
|
||||
private enum Side {LEFT, RIGHT};
|
||||
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
|
||||
@ -670,22 +713,23 @@ final class Dasher implements PathConsumer2D, MarlinConst {
|
||||
// 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[] curve = recCurveStack[recLevel];
|
||||
final float[] curve = recCurveStack[recLevel];
|
||||
final int _curveType = curveType;
|
||||
float polyLen = 0.0f;
|
||||
|
||||
float x0 = curve[0], y0 = curve[1];
|
||||
for (int i = 2; i < curveType; i += 2) {
|
||||
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;
|
||||
curLeafCtrlPolyLengths[(i >> 1) - 1] = len;
|
||||
x0 = x1;
|
||||
y0 = y1;
|
||||
}
|
||||
|
||||
final float lineLen = Helpers.linelen(curve[0], curve[1],
|
||||
curve[curveType-2],
|
||||
curve[curveType-1]);
|
||||
curve[_curveType-2],
|
||||
curve[_curveType-1]);
|
||||
if ((polyLen - lineLen) < ERR || recLevel == REC_LIMIT) {
|
||||
return (polyLen + lineLen) / 2.0f;
|
||||
}
|
||||
@ -694,9 +738,9 @@ final class Dasher implements PathConsumer2D, MarlinConst {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void curveTo(float x1, float y1,
|
||||
float x2, float y2,
|
||||
float x3, float y3)
|
||||
public void curveTo(final float x1, final float y1,
|
||||
final float x2, final float y2,
|
||||
final float x3, final float y3)
|
||||
{
|
||||
final float[] _curCurvepts = curCurvepts;
|
||||
_curCurvepts[0] = x0; _curCurvepts[1] = y0;
|
||||
@ -707,7 +751,9 @@ final class Dasher implements PathConsumer2D, MarlinConst {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void quadTo(float x1, float y1, float x2, float y2) {
|
||||
public void quadTo(final float x1, final float y1,
|
||||
final float x2, final float y2)
|
||||
{
|
||||
final float[] _curCurvepts = curCurvepts;
|
||||
_curCurvepts[0] = x0; _curCurvepts[1] = y0;
|
||||
_curCurvepts[2] = x1; _curCurvepts[3] = y1;
|
||||
@ -718,7 +764,7 @@ final class Dasher implements PathConsumer2D, MarlinConst {
|
||||
@Override
|
||||
public void closePath() {
|
||||
lineTo(sx, sy);
|
||||
if (firstSegidx > 0) {
|
||||
if (firstSegidx != 0) {
|
||||
if (!dashOn || needsMoveTo) {
|
||||
out.moveTo(sx, sy);
|
||||
}
|
||||
@ -729,7 +775,7 @@ final class Dasher implements PathConsumer2D, MarlinConst {
|
||||
|
||||
@Override
|
||||
public void pathDone() {
|
||||
if (firstSegidx > 0) {
|
||||
if (firstSegidx != 0) {
|
||||
out.moveTo(sx, sy);
|
||||
emitFirstSegments();
|
||||
}
|
||||
|
@ -26,10 +26,10 @@
|
||||
package sun.java2d.marlin;
|
||||
|
||||
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;
|
||||
import java.util.Arrays;
|
||||
import sun.awt.geom.PathConsumer2D;
|
||||
import sun.java2d.marlin.stats.Histogram;
|
||||
import sun.java2d.marlin.stats.StatLong;
|
||||
|
||||
final class Helpers implements MarlinConst {
|
||||
|
||||
@ -120,17 +120,17 @@ final class Helpers implements MarlinConst {
|
||||
int num;
|
||||
if (D < 0.0d) {
|
||||
// see: http://en.wikipedia.org/wiki/Cubic_function#Trigonometric_.28and_hyperbolic.29_method
|
||||
final double phi = (1.0d/3.0d) * acos(-q / sqrt(-cb_p));
|
||||
final double t = 2.0d * sqrt(-p);
|
||||
final double phi = (1.0d/3.0d) * Math.acos(-q / Math.sqrt(-cb_p));
|
||||
final double t = 2.0d * Math.sqrt(-p);
|
||||
|
||||
pts[ off+0 ] = (float) ( t * cos(phi));
|
||||
pts[ off+1 ] = (float) (-t * cos(phi + (PI / 3.0d)));
|
||||
pts[ off+2 ] = (float) (-t * cos(phi - (PI / 3.0d)));
|
||||
pts[ off+0 ] = (float) ( t * Math.cos(phi));
|
||||
pts[ off+1 ] = (float) (-t * Math.cos(phi + (PI / 3.0d)));
|
||||
pts[ off+2 ] = (float) (-t * Math.cos(phi - (PI / 3.0d)));
|
||||
num = 3;
|
||||
} else {
|
||||
final double sqrt_D = sqrt(D);
|
||||
final double u = cbrt(sqrt_D - q);
|
||||
final double v = - cbrt(sqrt_D + q);
|
||||
final double sqrt_D = Math.sqrt(D);
|
||||
final double u = Math.cbrt(sqrt_D - q);
|
||||
final double v = - Math.cbrt(sqrt_D + q);
|
||||
|
||||
pts[ off ] = (float) (u + v);
|
||||
num = 1;
|
||||
@ -176,15 +176,6 @@ final class Helpers implements MarlinConst {
|
||||
return ret;
|
||||
}
|
||||
|
||||
static float polyLineLength(float[] poly, final int off, final int nCoords) {
|
||||
assert nCoords % 2 == 0 && poly.length >= off + nCoords : "";
|
||||
float acc = 0.0f;
|
||||
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) {
|
||||
final float dx = x2 - x1;
|
||||
final float dy = y2 - y1;
|
||||
@ -438,4 +429,388 @@ final class Helpers implements MarlinConst {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// From sun.java2d.loops.GeneralRenderer:
|
||||
|
||||
static int outcode(final float x, final float y,
|
||||
final float[] clipRect)
|
||||
{
|
||||
int code;
|
||||
if (y < clipRect[0]) {
|
||||
code = OUTCODE_TOP;
|
||||
} else if (y >= clipRect[1]) {
|
||||
code = OUTCODE_BOTTOM;
|
||||
} else {
|
||||
code = 0;
|
||||
}
|
||||
if (x < clipRect[2]) {
|
||||
code |= OUTCODE_LEFT;
|
||||
} else if (x >= clipRect[3]) {
|
||||
code |= OUTCODE_RIGHT;
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
// a stack of polynomial curves where each curve shares endpoints with
|
||||
// adjacent ones.
|
||||
static final class PolyStack {
|
||||
private static final byte TYPE_LINETO = (byte) 0;
|
||||
private static final byte TYPE_QUADTO = (byte) 1;
|
||||
private static final byte TYPE_CUBICTO = (byte) 2;
|
||||
|
||||
// curves capacity = edges count (8192) = edges x 2 (coords)
|
||||
private static final int INITIAL_CURVES_COUNT = INITIAL_EDGES_COUNT << 1;
|
||||
|
||||
// types capacity = edges count (4096)
|
||||
private static final int INITIAL_TYPES_COUNT = INITIAL_EDGES_COUNT;
|
||||
|
||||
float[] curves;
|
||||
int end;
|
||||
byte[] curveTypes;
|
||||
int numCurves;
|
||||
|
||||
// curves ref (dirty)
|
||||
final FloatArrayCache.Reference curves_ref;
|
||||
// curveTypes ref (dirty)
|
||||
final ByteArrayCache.Reference curveTypes_ref;
|
||||
|
||||
// used marks (stats only)
|
||||
int curveTypesUseMark;
|
||||
int curvesUseMark;
|
||||
|
||||
private final StatLong stat_polystack_types;
|
||||
private final StatLong stat_polystack_curves;
|
||||
private final Histogram hist_polystack_curves;
|
||||
private final StatLong stat_array_polystack_curves;
|
||||
private final StatLong stat_array_polystack_curveTypes;
|
||||
|
||||
PolyStack(final RendererContext rdrCtx) {
|
||||
this(rdrCtx, null, null, null, null, null);
|
||||
}
|
||||
|
||||
PolyStack(final RendererContext rdrCtx,
|
||||
final StatLong stat_polystack_types,
|
||||
final StatLong stat_polystack_curves,
|
||||
final Histogram hist_polystack_curves,
|
||||
final StatLong stat_array_polystack_curves,
|
||||
final StatLong stat_array_polystack_curveTypes)
|
||||
{
|
||||
curves_ref = rdrCtx.newDirtyFloatArrayRef(INITIAL_CURVES_COUNT); // 32K
|
||||
curves = curves_ref.initial;
|
||||
|
||||
curveTypes_ref = rdrCtx.newDirtyByteArrayRef(INITIAL_TYPES_COUNT); // 4K
|
||||
curveTypes = curveTypes_ref.initial;
|
||||
numCurves = 0;
|
||||
end = 0;
|
||||
|
||||
if (DO_STATS) {
|
||||
curveTypesUseMark = 0;
|
||||
curvesUseMark = 0;
|
||||
}
|
||||
this.stat_polystack_types = stat_polystack_types;
|
||||
this.stat_polystack_curves = stat_polystack_curves;
|
||||
this.hist_polystack_curves = hist_polystack_curves;
|
||||
this.stat_array_polystack_curves = stat_array_polystack_curves;
|
||||
this.stat_array_polystack_curveTypes = stat_array_polystack_curveTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes this PolyStack:
|
||||
* clean up before reusing this instance
|
||||
*/
|
||||
void dispose() {
|
||||
end = 0;
|
||||
numCurves = 0;
|
||||
|
||||
if (DO_STATS) {
|
||||
stat_polystack_types.add(curveTypesUseMark);
|
||||
stat_polystack_curves.add(curvesUseMark);
|
||||
hist_polystack_curves.add(curvesUseMark);
|
||||
|
||||
// reset marks
|
||||
curveTypesUseMark = 0;
|
||||
curvesUseMark = 0;
|
||||
}
|
||||
|
||||
// Return arrays:
|
||||
// curves and curveTypes are kept dirty
|
||||
curves = curves_ref.putArray(curves);
|
||||
curveTypes = curveTypes_ref.putArray(curveTypes);
|
||||
}
|
||||
|
||||
private void ensureSpace(final int n) {
|
||||
// use substraction to avoid integer overflow:
|
||||
if (curves.length - end < n) {
|
||||
if (DO_STATS) {
|
||||
stat_array_polystack_curves.add(end + n);
|
||||
}
|
||||
curves = curves_ref.widenArray(curves, end, end + n);
|
||||
}
|
||||
if (curveTypes.length <= numCurves) {
|
||||
if (DO_STATS) {
|
||||
stat_array_polystack_curveTypes.add(numCurves + 1);
|
||||
}
|
||||
curveTypes = curveTypes_ref.widenArray(curveTypes,
|
||||
numCurves,
|
||||
numCurves + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void pushCubic(float x0, float y0,
|
||||
float x1, float y1,
|
||||
float x2, float y2)
|
||||
{
|
||||
ensureSpace(6);
|
||||
curveTypes[numCurves++] = TYPE_CUBICTO;
|
||||
// we reverse the coordinate order to make popping easier
|
||||
final float[] _curves = curves;
|
||||
int e = end;
|
||||
_curves[e++] = x2; _curves[e++] = y2;
|
||||
_curves[e++] = x1; _curves[e++] = y1;
|
||||
_curves[e++] = x0; _curves[e++] = y0;
|
||||
end = e;
|
||||
}
|
||||
|
||||
void pushQuad(float x0, float y0,
|
||||
float x1, float y1)
|
||||
{
|
||||
ensureSpace(4);
|
||||
curveTypes[numCurves++] = TYPE_QUADTO;
|
||||
final float[] _curves = curves;
|
||||
int e = end;
|
||||
_curves[e++] = x1; _curves[e++] = y1;
|
||||
_curves[e++] = x0; _curves[e++] = y0;
|
||||
end = e;
|
||||
}
|
||||
|
||||
void pushLine(float x, float y) {
|
||||
ensureSpace(2);
|
||||
curveTypes[numCurves++] = TYPE_LINETO;
|
||||
curves[end++] = x; curves[end++] = y;
|
||||
}
|
||||
|
||||
void pullAll(final PathConsumer2D io) {
|
||||
final int nc = numCurves;
|
||||
if (nc == 0) {
|
||||
return;
|
||||
}
|
||||
if (DO_STATS) {
|
||||
// update used marks:
|
||||
if (numCurves > curveTypesUseMark) {
|
||||
curveTypesUseMark = numCurves;
|
||||
}
|
||||
if (end > curvesUseMark) {
|
||||
curvesUseMark = end;
|
||||
}
|
||||
}
|
||||
final byte[] _curveTypes = curveTypes;
|
||||
final float[] _curves = curves;
|
||||
int e = 0;
|
||||
|
||||
for (int i = 0; i < nc; i++) {
|
||||
switch(_curveTypes[i]) {
|
||||
case TYPE_LINETO:
|
||||
io.lineTo(_curves[e], _curves[e+1]);
|
||||
e += 2;
|
||||
continue;
|
||||
case TYPE_QUADTO:
|
||||
io.quadTo(_curves[e+0], _curves[e+1],
|
||||
_curves[e+2], _curves[e+3]);
|
||||
e += 4;
|
||||
continue;
|
||||
case TYPE_CUBICTO:
|
||||
io.curveTo(_curves[e+0], _curves[e+1],
|
||||
_curves[e+2], _curves[e+3],
|
||||
_curves[e+4], _curves[e+5]);
|
||||
e += 6;
|
||||
continue;
|
||||
default:
|
||||
}
|
||||
}
|
||||
numCurves = 0;
|
||||
end = 0;
|
||||
}
|
||||
|
||||
void popAll(final PathConsumer2D io) {
|
||||
int nc = numCurves;
|
||||
if (nc == 0) {
|
||||
return;
|
||||
}
|
||||
if (DO_STATS) {
|
||||
// update used marks:
|
||||
if (numCurves > curveTypesUseMark) {
|
||||
curveTypesUseMark = numCurves;
|
||||
}
|
||||
if (end > curvesUseMark) {
|
||||
curvesUseMark = end;
|
||||
}
|
||||
}
|
||||
final byte[] _curveTypes = curveTypes;
|
||||
final float[] _curves = curves;
|
||||
int e = end;
|
||||
|
||||
while (nc != 0) {
|
||||
switch(_curveTypes[--nc]) {
|
||||
case TYPE_LINETO:
|
||||
e -= 2;
|
||||
io.lineTo(_curves[e], _curves[e+1]);
|
||||
continue;
|
||||
case TYPE_QUADTO:
|
||||
e -= 4;
|
||||
io.quadTo(_curves[e+0], _curves[e+1],
|
||||
_curves[e+2], _curves[e+3]);
|
||||
continue;
|
||||
case TYPE_CUBICTO:
|
||||
e -= 6;
|
||||
io.curveTo(_curves[e+0], _curves[e+1],
|
||||
_curves[e+2], _curves[e+3],
|
||||
_curves[e+4], _curves[e+5]);
|
||||
continue;
|
||||
default:
|
||||
}
|
||||
}
|
||||
numCurves = 0;
|
||||
end = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String ret = "";
|
||||
int nc = numCurves;
|
||||
int last = end;
|
||||
int len;
|
||||
while (nc != 0) {
|
||||
switch(curveTypes[--nc]) {
|
||||
case TYPE_LINETO:
|
||||
len = 2;
|
||||
ret += "line: ";
|
||||
break;
|
||||
case TYPE_QUADTO:
|
||||
len = 4;
|
||||
ret += "quad: ";
|
||||
break;
|
||||
case TYPE_CUBICTO:
|
||||
len = 6;
|
||||
ret += "cubic: ";
|
||||
break;
|
||||
default:
|
||||
len = 0;
|
||||
}
|
||||
last -= len;
|
||||
ret += Arrays.toString(Arrays.copyOfRange(curves, last, last+len))
|
||||
+ "\n";
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
// a stack of integer indices
|
||||
static final class IndexStack {
|
||||
|
||||
// integer capacity = edges count / 4 ~ 1024
|
||||
private static final int INITIAL_COUNT = INITIAL_EDGES_COUNT >> 2;
|
||||
|
||||
private int end;
|
||||
private int[] indices;
|
||||
|
||||
// indices ref (dirty)
|
||||
private final IntArrayCache.Reference indices_ref;
|
||||
|
||||
// used marks (stats only)
|
||||
private int indicesUseMark;
|
||||
|
||||
private final StatLong stat_idxstack_indices;
|
||||
private final Histogram hist_idxstack_indices;
|
||||
private final StatLong stat_array_idxstack_indices;
|
||||
|
||||
IndexStack(final RendererContext rdrCtx) {
|
||||
this(rdrCtx, null, null, null);
|
||||
}
|
||||
|
||||
IndexStack(final RendererContext rdrCtx,
|
||||
final StatLong stat_idxstack_indices,
|
||||
final Histogram hist_idxstack_indices,
|
||||
final StatLong stat_array_idxstack_indices)
|
||||
{
|
||||
indices_ref = rdrCtx.newDirtyIntArrayRef(INITIAL_COUNT); // 4K
|
||||
indices = indices_ref.initial;
|
||||
end = 0;
|
||||
|
||||
if (DO_STATS) {
|
||||
indicesUseMark = 0;
|
||||
}
|
||||
this.stat_idxstack_indices = stat_idxstack_indices;
|
||||
this.hist_idxstack_indices = hist_idxstack_indices;
|
||||
this.stat_array_idxstack_indices = stat_array_idxstack_indices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes this PolyStack:
|
||||
* clean up before reusing this instance
|
||||
*/
|
||||
void dispose() {
|
||||
end = 0;
|
||||
|
||||
if (DO_STATS) {
|
||||
stat_idxstack_indices.add(indicesUseMark);
|
||||
hist_idxstack_indices.add(indicesUseMark);
|
||||
|
||||
// reset marks
|
||||
indicesUseMark = 0;
|
||||
}
|
||||
|
||||
// Return arrays:
|
||||
// values is kept dirty
|
||||
indices = indices_ref.putArray(indices);
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
return (end == 0);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
end = 0;
|
||||
}
|
||||
|
||||
void push(final int v) {
|
||||
// remove redundant values (reverse order):
|
||||
int[] _values = indices;
|
||||
final int nc = end;
|
||||
if (nc != 0) {
|
||||
if (_values[nc - 1] == v) {
|
||||
// remove both duplicated values:
|
||||
end--;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (_values.length <= nc) {
|
||||
if (DO_STATS) {
|
||||
stat_array_idxstack_indices.add(nc + 1);
|
||||
}
|
||||
indices = _values = indices_ref.widenArray(_values, nc, nc + 1);
|
||||
}
|
||||
_values[end++] = v;
|
||||
|
||||
if (DO_STATS) {
|
||||
// update used marks:
|
||||
if (end > indicesUseMark) {
|
||||
indicesUseMark = end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pullAll(final float[] points, final PathConsumer2D io) {
|
||||
final int nc = end;
|
||||
if (nc == 0) {
|
||||
return;
|
||||
}
|
||||
final int[] _values = indices;
|
||||
|
||||
for (int i = 0, j; i < nc; i++) {
|
||||
j = _values[i] << 1;
|
||||
io.lineTo(points[j], points[j + 1]);
|
||||
}
|
||||
end = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -139,11 +139,7 @@ public final class MarlinCache implements MarlinConst {
|
||||
// ie number of primitives:
|
||||
|
||||
// fast check min and max width (maxx < 23bits):
|
||||
if (width <= RLE_MIN_WIDTH || width >= RLE_MAX_WIDTH) {
|
||||
useRLE = false;
|
||||
} else {
|
||||
useRLE = true;
|
||||
}
|
||||
useRLE = (width > RLE_MIN_WIDTH && width < RLE_MAX_WIDTH);
|
||||
}
|
||||
|
||||
// the ceiling of (maxy - miny + 1) / TILE_SIZE;
|
||||
|
@ -128,4 +128,47 @@ interface MarlinConst {
|
||||
|
||||
public static final int BLOCK_SIZE_LG = MarlinProperties.getBlockSize_Log2();
|
||||
public static final int BLOCK_SIZE = 1 << BLOCK_SIZE_LG;
|
||||
|
||||
// Constants
|
||||
public static final int WIND_EVEN_ODD = 0;
|
||||
public static final int WIND_NON_ZERO = 1;
|
||||
|
||||
/**
|
||||
* Constant value for join style.
|
||||
*/
|
||||
public static final int JOIN_MITER = 0;
|
||||
|
||||
/**
|
||||
* Constant value for join style.
|
||||
*/
|
||||
public static final int JOIN_ROUND = 1;
|
||||
|
||||
/**
|
||||
* Constant value for join style.
|
||||
*/
|
||||
public static final int JOIN_BEVEL = 2;
|
||||
|
||||
/**
|
||||
* Constant value for end cap style.
|
||||
*/
|
||||
public static final int CAP_BUTT = 0;
|
||||
|
||||
/**
|
||||
* Constant value for end cap style.
|
||||
*/
|
||||
public static final int CAP_ROUND = 1;
|
||||
|
||||
/**
|
||||
* Constant value for end cap style.
|
||||
*/
|
||||
public static final int CAP_SQUARE = 2;
|
||||
|
||||
// Out codes
|
||||
static final int OUTCODE_TOP = 1;
|
||||
static final int OUTCODE_BOTTOM = 2;
|
||||
static final int OUTCODE_LEFT = 4;
|
||||
static final int OUTCODE_RIGHT = 8;
|
||||
static final int OUTCODE_MASK_T_B = OUTCODE_TOP | OUTCODE_BOTTOM;
|
||||
static final int OUTCODE_MASK_L_R = OUTCODE_LEFT | OUTCODE_RIGHT;
|
||||
static final int OUTCODE_MASK_T_B_L_R = OUTCODE_MASK_T_B | OUTCODE_MASK_L_R;
|
||||
}
|
||||
|
@ -145,6 +145,18 @@ public final class MarlinProperties {
|
||||
return getBoolean("sun.java2d.renderer.useSimplifier", "false");
|
||||
}
|
||||
|
||||
public static boolean isDoClip() {
|
||||
return getBoolean("sun.java2d.renderer.clip", "true");
|
||||
}
|
||||
|
||||
public static boolean isDoClipRuntimeFlag() {
|
||||
return getBoolean("sun.java2d.renderer.clip.runtime.enable", "false");
|
||||
}
|
||||
|
||||
public static boolean isDoClipAtRuntime() {
|
||||
return getBoolean("sun.java2d.renderer.clip.runtime", "true");
|
||||
}
|
||||
|
||||
// debugging parameters
|
||||
|
||||
public static boolean isDoStats() {
|
||||
|
@ -85,6 +85,13 @@ public final class MarlinRenderingEngine extends RenderingEngine
|
||||
static final float UPPER_BND = Float.MAX_VALUE / 2.0f;
|
||||
static final float LOWER_BND = -UPPER_BND;
|
||||
|
||||
static final boolean DO_CLIP = MarlinProperties.isDoClip();
|
||||
static final boolean DO_CLIP_FILL = true;
|
||||
|
||||
static final boolean DO_TRACE_PATH = false;
|
||||
|
||||
static final boolean DO_CLIP_RUNTIME_ENABLE = MarlinProperties.isDoClipRuntimeFlag();
|
||||
|
||||
/**
|
||||
* Public constructor
|
||||
*/
|
||||
@ -134,7 +141,7 @@ public final class MarlinRenderingEngine extends RenderingEngine
|
||||
miterlimit,
|
||||
dashes,
|
||||
dashphase,
|
||||
rdrCtx.transformerPC2D.wrapPath2d(p2d)
|
||||
rdrCtx.transformerPC2D.wrapPath2D(p2d)
|
||||
);
|
||||
|
||||
// Use Path2D copy constructor (trim)
|
||||
@ -195,14 +202,14 @@ public final class MarlinRenderingEngine extends RenderingEngine
|
||||
}
|
||||
}
|
||||
|
||||
final void strokeTo(final RendererContext rdrCtx,
|
||||
Shape src,
|
||||
AffineTransform at,
|
||||
BasicStroke bs,
|
||||
boolean thin,
|
||||
NormMode normalize,
|
||||
boolean antialias,
|
||||
PathConsumer2D pc2d)
|
||||
void strokeTo(final RendererContext rdrCtx,
|
||||
Shape src,
|
||||
AffineTransform at,
|
||||
BasicStroke bs,
|
||||
boolean thin,
|
||||
NormMode normalize,
|
||||
boolean antialias,
|
||||
PathConsumer2D pc2d)
|
||||
{
|
||||
float lw;
|
||||
if (thin) {
|
||||
@ -295,17 +302,17 @@ public final class MarlinRenderingEngine extends RenderingEngine
|
||||
return (lw / widthScale);
|
||||
}
|
||||
|
||||
final void strokeTo(final RendererContext rdrCtx,
|
||||
Shape src,
|
||||
AffineTransform at,
|
||||
float width,
|
||||
NormMode norm,
|
||||
int caps,
|
||||
int join,
|
||||
float miterlimit,
|
||||
float[] dashes,
|
||||
float dashphase,
|
||||
PathConsumer2D pc2d)
|
||||
void strokeTo(final RendererContext rdrCtx,
|
||||
Shape src,
|
||||
AffineTransform at,
|
||||
float width,
|
||||
NormMode norm,
|
||||
int caps,
|
||||
int join,
|
||||
float miterlimit,
|
||||
float[] dashes,
|
||||
float dashphase,
|
||||
PathConsumer2D pc2d)
|
||||
{
|
||||
// We use strokerat so that in Stroker and Dasher we can work only
|
||||
// with the pre-transformation coordinates. This will repeat a lot of
|
||||
@ -324,6 +331,7 @@ public final class MarlinRenderingEngine extends RenderingEngine
|
||||
|
||||
int dashLen = -1;
|
||||
boolean recycleDashes = false;
|
||||
float scale = 1.0f;
|
||||
|
||||
if (at != null && !at.isIdentity()) {
|
||||
final double a = at.getScaleX();
|
||||
@ -356,7 +364,7 @@ public final class MarlinRenderingEngine extends RenderingEngine
|
||||
// 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) && nearZero(a*a + c*c - (b*b + d*d))) {
|
||||
final float scale = (float) Math.sqrt(a*a + c*c);
|
||||
scale = (float) Math.sqrt(a*a + c*c);
|
||||
|
||||
if (dashes != null) {
|
||||
recycleDashes = true;
|
||||
@ -394,16 +402,24 @@ public final class MarlinRenderingEngine extends RenderingEngine
|
||||
at = null;
|
||||
}
|
||||
|
||||
final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;
|
||||
|
||||
if (DO_TRACE_PATH) {
|
||||
// trace Stroker:
|
||||
pc2d = transformerPC2D.traceStroker(pc2d);
|
||||
}
|
||||
|
||||
if (USE_SIMPLIFIER) {
|
||||
// Use simplifier after stroker before Renderer
|
||||
// to remove collinear segments (notably due to cap square)
|
||||
pc2d = rdrCtx.simplifier.init(pc2d);
|
||||
}
|
||||
|
||||
final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;
|
||||
// deltaTransformConsumer may adjust the clip rectangle:
|
||||
pc2d = transformerPC2D.deltaTransformConsumer(pc2d, strokerat);
|
||||
|
||||
pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit);
|
||||
// stroker will adjust the clip rectangle (width / miter limit):
|
||||
pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit, scale);
|
||||
|
||||
if (dashes != null) {
|
||||
if (!recycleDashes) {
|
||||
@ -411,9 +427,22 @@ public final class MarlinRenderingEngine extends RenderingEngine
|
||||
}
|
||||
pc2d = rdrCtx.dasher.init(pc2d, dashes, dashLen, dashphase,
|
||||
recycleDashes);
|
||||
} else if (rdrCtx.doClip && (caps != Stroker.CAP_BUTT)) {
|
||||
if (DO_TRACE_PATH) {
|
||||
pc2d = transformerPC2D.traceClosedPathDetector(pc2d);
|
||||
}
|
||||
|
||||
// If no dash and clip is enabled:
|
||||
// detect closedPaths (polygons) for caps
|
||||
pc2d = transformerPC2D.detectClosedPath(pc2d);
|
||||
}
|
||||
pc2d = transformerPC2D.inverseDeltaTransformConsumer(pc2d, strokerat);
|
||||
|
||||
if (DO_TRACE_PATH) {
|
||||
// trace Input:
|
||||
pc2d = transformerPC2D.traceInput(pc2d);
|
||||
}
|
||||
|
||||
final PathIterator pi = norm.getNormalizingPathIterator(rdrCtx,
|
||||
src.getPathIterator(at));
|
||||
|
||||
@ -594,14 +623,12 @@ public final class MarlinRenderingEngine extends RenderingEngine
|
||||
}
|
||||
|
||||
private static void pathTo(final RendererContext rdrCtx, final PathIterator pi,
|
||||
final PathConsumer2D pc2d)
|
||||
PathConsumer2D pc2d)
|
||||
{
|
||||
// mark context as DIRTY:
|
||||
rdrCtx.dirty = true;
|
||||
|
||||
final float[] coords = rdrCtx.float6;
|
||||
|
||||
pathToLoop(coords, pi, pc2d);
|
||||
pathToLoop(rdrCtx.float6, pi, pc2d);
|
||||
|
||||
// mark context as CLEAN:
|
||||
rdrCtx.dirty = false;
|
||||
@ -779,6 +806,19 @@ public final class MarlinRenderingEngine extends RenderingEngine
|
||||
|
||||
final RendererContext rdrCtx = getRendererContext();
|
||||
try {
|
||||
if (DO_CLIP || (DO_CLIP_RUNTIME_ENABLE && MarlinProperties.isDoClipAtRuntime())) {
|
||||
// Define the initial clip bounds:
|
||||
final float[] clipRect = rdrCtx.clipRect;
|
||||
|
||||
clipRect[0] = clip.getLoY();
|
||||
clipRect[1] = clip.getLoY() + clip.getHeight();
|
||||
clipRect[2] = clip.getLoX();
|
||||
clipRect[3] = clip.getLoX() + clip.getWidth();
|
||||
|
||||
// Enable clipping:
|
||||
rdrCtx.doClip = true;
|
||||
}
|
||||
|
||||
// Test if at is identity:
|
||||
final AffineTransform _at = (at != null && !at.isIdentity()) ? at
|
||||
: null;
|
||||
@ -795,13 +835,29 @@ public final class MarlinRenderingEngine extends RenderingEngine
|
||||
clip.getWidth(), clip.getHeight(),
|
||||
pi.getWindingRule());
|
||||
|
||||
PathConsumer2D pc2d = r;
|
||||
|
||||
if (DO_CLIP_FILL && rdrCtx.doClip) {
|
||||
if (DO_TRACE_PATH) {
|
||||
// trace Filler:
|
||||
pc2d = rdrCtx.transformerPC2D.traceFiller(pc2d);
|
||||
}
|
||||
pc2d = rdrCtx.transformerPC2D.pathClipper(pc2d);
|
||||
}
|
||||
|
||||
if (DO_TRACE_PATH) {
|
||||
// trace Input:
|
||||
pc2d = rdrCtx.transformerPC2D.traceInput(pc2d);
|
||||
}
|
||||
|
||||
// TODO: subdivide quad/cubic curves into monotonic curves ?
|
||||
pathTo(rdrCtx, pi, r);
|
||||
pathTo(rdrCtx, pi, pc2d);
|
||||
|
||||
} else {
|
||||
// draw shape with given stroke:
|
||||
r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),
|
||||
clip.getWidth(), clip.getHeight(),
|
||||
PathIterator.WIND_NON_ZERO);
|
||||
WIND_NON_ZERO);
|
||||
|
||||
strokeTo(rdrCtx, s, _at, bs, thin, norm, true, r);
|
||||
}
|
||||
@ -824,12 +880,12 @@ public final class MarlinRenderingEngine extends RenderingEngine
|
||||
}
|
||||
|
||||
@Override
|
||||
public final AATileGenerator getAATileGenerator(double x, double y,
|
||||
double dx1, double dy1,
|
||||
double dx2, double dy2,
|
||||
double lw1, double lw2,
|
||||
Region clip,
|
||||
int[] bbox)
|
||||
public AATileGenerator getAATileGenerator(double x, double y,
|
||||
double dx1, double dy1,
|
||||
double dx2, double dy2,
|
||||
double lw1, double lw2,
|
||||
Region clip,
|
||||
int[] bbox)
|
||||
{
|
||||
// REMIND: Deal with large coordinates!
|
||||
double ldx1, ldy1, ldx2, ldy2;
|
||||
@ -860,8 +916,8 @@ public final class MarlinRenderingEngine extends RenderingEngine
|
||||
final RendererContext rdrCtx = getRendererContext();
|
||||
try {
|
||||
r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),
|
||||
clip.getWidth(), clip.getHeight(),
|
||||
Renderer.WIND_EVEN_ODD);
|
||||
clip.getWidth(), clip.getHeight(),
|
||||
WIND_EVEN_ODD);
|
||||
|
||||
r.moveTo((float) x, (float) y);
|
||||
r.lineTo((float) (x+dx1), (float) (y+dy1));
|
||||
@ -913,14 +969,14 @@ public final class MarlinRenderingEngine extends RenderingEngine
|
||||
}
|
||||
|
||||
static {
|
||||
if (PathIterator.WIND_NON_ZERO != Renderer.WIND_NON_ZERO ||
|
||||
PathIterator.WIND_EVEN_ODD != Renderer.WIND_EVEN_ODD ||
|
||||
BasicStroke.JOIN_MITER != Stroker.JOIN_MITER ||
|
||||
BasicStroke.JOIN_ROUND != Stroker.JOIN_ROUND ||
|
||||
BasicStroke.JOIN_BEVEL != Stroker.JOIN_BEVEL ||
|
||||
BasicStroke.CAP_BUTT != Stroker.CAP_BUTT ||
|
||||
BasicStroke.CAP_ROUND != Stroker.CAP_ROUND ||
|
||||
BasicStroke.CAP_SQUARE != Stroker.CAP_SQUARE)
|
||||
if (PathIterator.WIND_NON_ZERO != WIND_NON_ZERO ||
|
||||
PathIterator.WIND_EVEN_ODD != WIND_EVEN_ODD ||
|
||||
BasicStroke.JOIN_MITER != JOIN_MITER ||
|
||||
BasicStroke.JOIN_ROUND != JOIN_ROUND ||
|
||||
BasicStroke.JOIN_BEVEL != JOIN_BEVEL ||
|
||||
BasicStroke.CAP_BUTT != CAP_BUTT ||
|
||||
BasicStroke.CAP_ROUND != CAP_ROUND ||
|
||||
BasicStroke.CAP_SQUARE != CAP_SQUARE)
|
||||
{
|
||||
throw new InternalError("mismatched renderer constants");
|
||||
}
|
||||
@ -1046,6 +1102,11 @@ public final class MarlinRenderingEngine extends RenderingEngine
|
||||
logInfo("sun.java2d.renderer.useSimplifier = "
|
||||
+ MarlinConst.USE_SIMPLIFIER);
|
||||
|
||||
logInfo("sun.java2d.renderer.clip = "
|
||||
+ MarlinProperties.isDoClip());
|
||||
logInfo("sun.java2d.renderer.clip.runtime.enable = "
|
||||
+ MarlinProperties.isDoClipRuntimeFlag());
|
||||
|
||||
// debugging parameters
|
||||
logInfo("sun.java2d.renderer.doStats = "
|
||||
+ MarlinConst.DO_STATS);
|
||||
|
@ -47,6 +47,9 @@ final class Renderer implements PathConsumer2D, MarlinRenderer {
|
||||
static final int SUBPIXEL_MASK_X = SUBPIXEL_POSITIONS_X - 1;
|
||||
static final int SUBPIXEL_MASK_Y = SUBPIXEL_POSITIONS_Y - 1;
|
||||
|
||||
static final float RDR_OFFSET_X = 0.5f / SUBPIXEL_SCALE_X;
|
||||
static final float RDR_OFFSET_Y = 0.5f / SUBPIXEL_SCALE_Y;
|
||||
|
||||
// number of subpixels corresponding to a tile line
|
||||
private static final int SUBPIXEL_TILE
|
||||
= TILE_H << SUBPIXEL_LG_POSITIONS_Y;
|
||||
@ -58,9 +61,6 @@ final class Renderer implements PathConsumer2D, MarlinRenderer {
|
||||
// crossing capacity = edges count / 4 ~ 1024
|
||||
static final int INITIAL_CROSSING_COUNT = INITIAL_EDGES_COUNT >> 2;
|
||||
|
||||
public static final int WIND_EVEN_ODD = 0;
|
||||
public static final int WIND_NON_ZERO = 1;
|
||||
|
||||
// common to all types of input path segments.
|
||||
// OFFSET as bytes
|
||||
// only integer values:
|
||||
@ -526,11 +526,11 @@ final class Renderer implements PathConsumer2D, MarlinRenderer {
|
||||
|
||||
Renderer(final RendererContext rdrCtx) {
|
||||
this.rdrCtx = rdrCtx;
|
||||
this.curve = rdrCtx.curve;
|
||||
this.cache = rdrCtx.cache;
|
||||
|
||||
this.edges = rdrCtx.newOffHeapArray(INITIAL_EDGES_CAPACITY); // 96K
|
||||
|
||||
this.curve = rdrCtx.curve;
|
||||
|
||||
edgeBuckets_ref = rdrCtx.newCleanIntArrayRef(INITIAL_BUCKET_ARRAY); // 64K
|
||||
edgeBucketCounts_ref = rdrCtx.newCleanIntArrayRef(INITIAL_BUCKET_ARRAY); // 64K
|
||||
|
||||
@ -541,8 +541,6 @@ final class Renderer implements PathConsumer2D, MarlinRenderer {
|
||||
alphaLine_ref = rdrCtx.newCleanIntArrayRef(INITIAL_AA_ARRAY); // 8K
|
||||
alphaLine = alphaLine_ref.initial;
|
||||
|
||||
this.cache = rdrCtx.cache;
|
||||
|
||||
crossings_ref = rdrCtx.newDirtyIntArrayRef(INITIAL_CROSSING_COUNT); // 2K
|
||||
aux_crossings_ref = rdrCtx.newDirtyIntArrayRef(INITIAL_CROSSING_COUNT); // 2K
|
||||
edgePtrs_ref = rdrCtx.newDirtyIntArrayRef(INITIAL_CROSSING_COUNT); // 2K
|
||||
@ -672,7 +670,7 @@ final class Renderer implements PathConsumer2D, MarlinRenderer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveTo(float pix_x0, float pix_y0) {
|
||||
public void moveTo(final float pix_x0, final float pix_y0) {
|
||||
closePath();
|
||||
final float sx = tosubpixx(pix_x0);
|
||||
final float sy = tosubpixy(pix_y0);
|
||||
@ -683,7 +681,7 @@ final class Renderer implements PathConsumer2D, MarlinRenderer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lineTo(float pix_x1, float pix_y1) {
|
||||
public void lineTo(final float pix_x1, final float pix_y1) {
|
||||
final float x1 = tosubpixx(pix_x1);
|
||||
final float y1 = tosubpixy(pix_y1);
|
||||
addLine(x0, y0, x1, y1);
|
||||
@ -692,24 +690,26 @@ final class Renderer implements PathConsumer2D, MarlinRenderer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void curveTo(float x1, float y1,
|
||||
float x2, float y2,
|
||||
float x3, float y3)
|
||||
public void curveTo(final float pix_x1, final float pix_y1,
|
||||
final float pix_x2, final float pix_y2,
|
||||
final float pix_x3, final float pix_y3)
|
||||
{
|
||||
final float xe = tosubpixx(x3);
|
||||
final float ye = tosubpixy(y3);
|
||||
curve.set(x0, y0, tosubpixx(x1), tosubpixy(y1),
|
||||
tosubpixx(x2), tosubpixy(y2), xe, ye);
|
||||
final float xe = tosubpixx(pix_x3);
|
||||
final float ye = tosubpixy(pix_y3);
|
||||
curve.set(x0, y0, tosubpixx(pix_x1), tosubpixy(pix_y1),
|
||||
tosubpixx(pix_x2), tosubpixy(pix_y2), xe, ye);
|
||||
curveBreakIntoLinesAndAdd(x0, y0, curve, xe, ye);
|
||||
x0 = xe;
|
||||
y0 = ye;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void quadTo(float x1, float y1, float x2, float y2) {
|
||||
final float xe = tosubpixx(x2);
|
||||
final float ye = tosubpixy(y2);
|
||||
curve.set(x0, y0, tosubpixx(x1), tosubpixy(y1), xe, ye);
|
||||
public void quadTo(final float pix_x1, final float pix_y1,
|
||||
final float pix_x2, final float pix_y2)
|
||||
{
|
||||
final float xe = tosubpixx(pix_x2);
|
||||
final float ye = tosubpixy(pix_y2);
|
||||
curve.set(x0, y0, tosubpixx(pix_x1), tosubpixy(pix_y1), xe, ye);
|
||||
quadBreakIntoLinesAndAdd(x0, y0, curve, xe, ye);
|
||||
x0 = xe;
|
||||
y0 = ye;
|
||||
@ -717,9 +717,11 @@ final class Renderer implements PathConsumer2D, MarlinRenderer {
|
||||
|
||||
@Override
|
||||
public void closePath() {
|
||||
addLine(x0, y0, sx0, sy0);
|
||||
x0 = sx0;
|
||||
y0 = sy0;
|
||||
if (x0 != sx0 || y0 != sy0) {
|
||||
addLine(x0, y0, sx0, sy0);
|
||||
x0 = sx0;
|
||||
y0 = sy0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -75,16 +75,22 @@ final class RendererContext extends ReentrantContext implements IRendererContext
|
||||
final MarlinCache cache;
|
||||
// flag indicating the shape is stroked (1) or filled (0)
|
||||
int stroking = 0;
|
||||
// flag indicating to clip the shape
|
||||
boolean doClip = false;
|
||||
// flag indicating if the path is closed or not (in advance) to handle properly caps
|
||||
boolean closedPath = false;
|
||||
// clip rectangle (ymin, ymax, xmin, xmax):
|
||||
final float[] clipRect = new float[4];
|
||||
|
||||
// Array caches:
|
||||
/* clean int[] cache (zero-filled) = 5 refs */
|
||||
private final IntArrayCache cleanIntCache = new IntArrayCache(true, 5);
|
||||
/* dirty int[] cache = 4 refs */
|
||||
private final IntArrayCache dirtyIntCache = new IntArrayCache(false, 4);
|
||||
/* dirty float[] cache = 3 refs */
|
||||
private final FloatArrayCache dirtyFloatCache = new FloatArrayCache(false, 3);
|
||||
/* dirty byte[] cache = 1 ref */
|
||||
private final ByteArrayCache dirtyByteCache = new ByteArrayCache(false, 1);
|
||||
/* dirty int[] cache = 5 refs */
|
||||
private final IntArrayCache dirtyIntCache = new IntArrayCache(false, 5);
|
||||
/* dirty float[] cache = 4 refs (2 polystack) */
|
||||
private final FloatArrayCache dirtyFloatCache = new FloatArrayCache(false, 4);
|
||||
/* dirty byte[] cache = 2 ref (2 polystack) */
|
||||
private final ByteArrayCache dirtyByteCache = new ByteArrayCache(false, 2);
|
||||
|
||||
// RendererContext statistics
|
||||
final RendererStats stats;
|
||||
@ -116,7 +122,7 @@ final class RendererContext extends ReentrantContext implements IRendererContext
|
||||
nPQPathIterator = new NormalizingPathIterator.NearestPixelQuarter(float6);
|
||||
|
||||
// MarlinRenderingEngine.TransformingPathConsumer2D
|
||||
transformerPC2D = new TransformingPathConsumer2D();
|
||||
transformerPC2D = new TransformingPathConsumer2D(this);
|
||||
|
||||
// Renderer:
|
||||
cache = new MarlinCache(this);
|
||||
@ -138,7 +144,10 @@ final class RendererContext extends ReentrantContext implements IRendererContext
|
||||
}
|
||||
stats.totalOffHeap = 0L;
|
||||
}
|
||||
stroking = 0;
|
||||
stroking = 0;
|
||||
doClip = false;
|
||||
closedPath = false;
|
||||
|
||||
// if context is maked as DIRTY:
|
||||
if (dirty) {
|
||||
// may happen if an exception if thrown in the pipeline processing:
|
||||
@ -159,12 +168,11 @@ final class RendererContext extends ReentrantContext implements IRendererContext
|
||||
|
||||
Path2D.Float getPath2D() {
|
||||
// resolve reference:
|
||||
Path2D.Float p2d
|
||||
= (refPath2D != null) ? refPath2D.get() : null;
|
||||
Path2D.Float p2d = (refPath2D != null) ? refPath2D.get() : null;
|
||||
|
||||
// create a new Path2D ?
|
||||
if (p2d == null) {
|
||||
p2d = new Path2D.Float(Path2D.WIND_NON_ZERO, INITIAL_EDGES_COUNT); // 32K
|
||||
p2d = new Path2D.Float(WIND_NON_ZERO, INITIAL_EDGES_COUNT); // 32K
|
||||
|
||||
// update weak reference:
|
||||
refPath2D = new WeakReference<Path2D.Float>(p2d);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2015, 2017, 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
|
||||
@ -66,10 +66,6 @@ public final class RendererStats implements MarlinConst {
|
||||
= new StatLong("cache.rowAAChunk");
|
||||
final StatLong stat_cache_tiles
|
||||
= new StatLong("cache.tiles");
|
||||
final StatLong stat_rdr_poly_stack_curves
|
||||
= new StatLong("renderer.poly.stack.curves");
|
||||
final StatLong stat_rdr_poly_stack_types
|
||||
= new StatLong("renderer.poly.stack.types");
|
||||
final StatLong stat_rdr_addLine
|
||||
= new StatLong("renderer.addLine");
|
||||
final StatLong stat_rdr_addLine_skip
|
||||
@ -106,15 +102,21 @@ public final class RendererStats implements MarlinConst {
|
||||
= new StatLong("renderer.crossings.bsearch");
|
||||
final StatLong stat_rdr_crossings_msorts
|
||||
= new StatLong("renderer.crossings.msorts");
|
||||
final StatLong stat_str_polystack_curves
|
||||
= new StatLong("stroker.polystack.curves");
|
||||
final StatLong stat_str_polystack_types
|
||||
= new StatLong("stroker.polystack.types");
|
||||
final StatLong stat_cpd_polystack_curves
|
||||
= new StatLong("closedPathDetector.polystack.curves");
|
||||
final StatLong stat_cpd_polystack_types
|
||||
= new StatLong("closedPathDetector.polystack.types");
|
||||
final StatLong stat_pcf_idxstack_indices
|
||||
= new StatLong("pathClipFilter.stack.indices");
|
||||
// growable arrays
|
||||
final StatLong stat_array_dasher_dasher
|
||||
= new StatLong("array.dasher.dasher.d_float");
|
||||
final StatLong stat_array_dasher_firstSegmentsBuffer
|
||||
= new StatLong("array.dasher.firstSegmentsBuffer.d_float");
|
||||
final StatLong stat_array_stroker_polystack_curves
|
||||
= new StatLong("array.stroker.polystack.curves.d_float");
|
||||
final StatLong stat_array_stroker_polystack_curveTypes
|
||||
= new StatLong("array.stroker.polystack.curveTypes.d_byte");
|
||||
final StatLong stat_array_marlincache_rowAAChunk
|
||||
= new StatLong("array.marlincache.rowAAChunk.resize");
|
||||
final StatLong stat_array_marlincache_touchedTile
|
||||
@ -133,11 +135,19 @@ public final class RendererStats implements MarlinConst {
|
||||
= new StatLong("array.renderer.edgePtrs.int");
|
||||
final StatLong stat_array_renderer_aux_edgePtrs
|
||||
= new StatLong("array.renderer.aux_edgePtrs.int");
|
||||
final StatLong stat_array_str_polystack_curves
|
||||
= new StatLong("array.stroker.polystack.curves.d_float");
|
||||
final StatLong stat_array_str_polystack_types
|
||||
= new StatLong("array.stroker.polystack.curveTypes.d_byte");
|
||||
final StatLong stat_array_cpd_polystack_curves
|
||||
= new StatLong("array.closedPathDetector.polystack.curves.d_float");
|
||||
final StatLong stat_array_cpd_polystack_types
|
||||
= new StatLong("array.closedPathDetector.polystack.curveTypes.d_byte");
|
||||
final StatLong stat_array_pcf_idxstack_indices
|
||||
= new StatLong("array.pathClipFilter.stack.indices.d_int");
|
||||
// histograms
|
||||
final Histogram hist_rdr_edges_count
|
||||
= new Histogram("renderer.edges.count");
|
||||
final Histogram hist_rdr_poly_stack_curves
|
||||
= new Histogram("renderer.polystack.curves");
|
||||
final Histogram hist_rdr_crossings
|
||||
= new Histogram("renderer.crossings");
|
||||
final Histogram hist_rdr_crossings_ratio
|
||||
@ -148,6 +158,8 @@ public final class RendererStats implements MarlinConst {
|
||||
= new Histogram("renderer.crossings.msorts");
|
||||
final Histogram hist_rdr_crossings_msorts_adds
|
||||
= new Histogram("renderer.crossings.msorts.adds");
|
||||
final Histogram hist_str_polystack_curves
|
||||
= new Histogram("stroker.polystack.curves");
|
||||
final Histogram hist_tile_generator_alpha
|
||||
= new Histogram("tile_generator.alpha");
|
||||
final Histogram hist_tile_generator_encoding
|
||||
@ -158,13 +170,15 @@ public final class RendererStats implements MarlinConst {
|
||||
= new Histogram("tile_generator.encoding.ratio");
|
||||
final Histogram hist_tile_generator_encoding_runLen
|
||||
= new Histogram("tile_generator.encoding.runLen");
|
||||
final Histogram hist_cpd_polystack_curves
|
||||
= new Histogram("closedPathDetector.polystack.curves");
|
||||
final Histogram hist_pcf_idxstack_indices
|
||||
= new Histogram("pathClipFilter.stack.indices");
|
||||
// all stats
|
||||
final StatLong[] statistics = new StatLong[]{
|
||||
stat_cache_rowAA,
|
||||
stat_cache_rowAAChunk,
|
||||
stat_cache_tiles,
|
||||
stat_rdr_poly_stack_types,
|
||||
stat_rdr_poly_stack_curves,
|
||||
stat_rdr_addLine,
|
||||
stat_rdr_addLine_skip,
|
||||
stat_rdr_curveBreak,
|
||||
@ -183,8 +197,12 @@ public final class RendererStats implements MarlinConst {
|
||||
stat_rdr_crossings_sorts,
|
||||
stat_rdr_crossings_bsearch,
|
||||
stat_rdr_crossings_msorts,
|
||||
stat_str_polystack_types,
|
||||
stat_str_polystack_curves,
|
||||
stat_cpd_polystack_curves,
|
||||
stat_cpd_polystack_types,
|
||||
stat_pcf_idxstack_indices,
|
||||
hist_rdr_edges_count,
|
||||
hist_rdr_poly_stack_curves,
|
||||
hist_rdr_crossings,
|
||||
hist_rdr_crossings_ratio,
|
||||
hist_rdr_crossings_adds,
|
||||
@ -195,10 +213,11 @@ public final class RendererStats implements MarlinConst {
|
||||
hist_tile_generator_encoding_dist,
|
||||
hist_tile_generator_encoding_ratio,
|
||||
hist_tile_generator_encoding_runLen,
|
||||
hist_str_polystack_curves,
|
||||
hist_cpd_polystack_curves,
|
||||
hist_pcf_idxstack_indices,
|
||||
stat_array_dasher_dasher,
|
||||
stat_array_dasher_firstSegmentsBuffer,
|
||||
stat_array_stroker_polystack_curves,
|
||||
stat_array_stroker_polystack_curveTypes,
|
||||
stat_array_marlincache_rowAAChunk,
|
||||
stat_array_marlincache_touchedTile,
|
||||
stat_array_renderer_alphaline,
|
||||
@ -207,7 +226,12 @@ public final class RendererStats implements MarlinConst {
|
||||
stat_array_renderer_edgeBuckets,
|
||||
stat_array_renderer_edgeBucketCounts,
|
||||
stat_array_renderer_edgePtrs,
|
||||
stat_array_renderer_aux_edgePtrs
|
||||
stat_array_renderer_aux_edgePtrs,
|
||||
stat_array_str_polystack_curves,
|
||||
stat_array_str_polystack_types,
|
||||
stat_array_cpd_polystack_curves,
|
||||
stat_array_cpd_polystack_types,
|
||||
stat_array_pcf_idxstack_indices
|
||||
};
|
||||
// monitors
|
||||
final Monitor mon_pre_getAATileGenerator
|
||||
|
@ -28,6 +28,7 @@ package sun.java2d.marlin;
|
||||
import java.util.Arrays;
|
||||
|
||||
import sun.awt.geom.PathConsumer2D;
|
||||
import sun.java2d.marlin.Helpers.PolyStack;
|
||||
|
||||
// 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
|
||||
@ -38,42 +39,16 @@ final class Stroker implements PathConsumer2D, MarlinConst {
|
||||
private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad
|
||||
private static final int CLOSE = 2;
|
||||
|
||||
/**
|
||||
* Constant value for join style.
|
||||
*/
|
||||
public static final int JOIN_MITER = 0;
|
||||
|
||||
/**
|
||||
* Constant value for join style.
|
||||
*/
|
||||
public static final int JOIN_ROUND = 1;
|
||||
|
||||
/**
|
||||
* Constant value for join style.
|
||||
*/
|
||||
public static final int JOIN_BEVEL = 2;
|
||||
|
||||
/**
|
||||
* Constant value for end cap style.
|
||||
*/
|
||||
public static final int CAP_BUTT = 0;
|
||||
|
||||
/**
|
||||
* Constant value for end cap style.
|
||||
*/
|
||||
public static final int CAP_ROUND = 1;
|
||||
|
||||
/**
|
||||
* Constant value for end cap style.
|
||||
*/
|
||||
public static final int CAP_SQUARE = 2;
|
||||
|
||||
// pisces used to use fixed point arithmetic with 16 decimal digits. I
|
||||
// didn't want to change the values of the constant below when I converted
|
||||
// it to floating point, so that's why the divisions by 2^16 are there.
|
||||
private static final float ROUND_JOIN_THRESHOLD = 1000.0f/65536.0f;
|
||||
|
||||
private static final float C = 0.5522847498307933f;
|
||||
// kappa = (4/3) * (SQRT(2) - 1)
|
||||
private static final float C = (float)(4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d);
|
||||
|
||||
// SQRT(2)
|
||||
private static final float SQRT_2 = (float)Math.sqrt(2.0d);
|
||||
|
||||
private static final int MAX_N_CURVES = 11;
|
||||
|
||||
@ -120,6 +95,20 @@ final class Stroker implements PathConsumer2D, MarlinConst {
|
||||
// dirty curve
|
||||
final Curve curve;
|
||||
|
||||
// Bounds of the drawing region, at pixel precision.
|
||||
private float[] clipRect;
|
||||
|
||||
// the outcode of the current point
|
||||
private int cOutCode = 0;
|
||||
|
||||
// the outcode of the starting point
|
||||
private int sOutCode = 0;
|
||||
|
||||
// flag indicating if the path is opened (clipped)
|
||||
private boolean opened = false;
|
||||
// flag indicating if the starting point's cap is done
|
||||
private boolean capStart = false;
|
||||
|
||||
/**
|
||||
* Constructs a <code>Stroker</code>.
|
||||
* @param rdrCtx per-thread renderer context
|
||||
@ -127,7 +116,15 @@ final class Stroker implements PathConsumer2D, MarlinConst {
|
||||
Stroker(final RendererContext rdrCtx) {
|
||||
this.rdrCtx = rdrCtx;
|
||||
|
||||
this.reverse = new PolyStack(rdrCtx);
|
||||
this.reverse = (rdrCtx.stats != null) ?
|
||||
new PolyStack(rdrCtx,
|
||||
rdrCtx.stats.stat_str_polystack_types,
|
||||
rdrCtx.stats.stat_str_polystack_curves,
|
||||
rdrCtx.stats.hist_str_polystack_curves,
|
||||
rdrCtx.stats.stat_array_str_polystack_curves,
|
||||
rdrCtx.stats.stat_array_str_polystack_types)
|
||||
: new PolyStack(rdrCtx);
|
||||
|
||||
this.curve = rdrCtx.curve;
|
||||
}
|
||||
|
||||
@ -143,13 +140,15 @@ final class Stroker implements PathConsumer2D, MarlinConst {
|
||||
* <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or
|
||||
* <code>JOIN_BEVEL</code>.
|
||||
* @param miterLimit the desired miter limit
|
||||
* @param scale scaling factor applied to clip boundaries
|
||||
* @return this instance
|
||||
*/
|
||||
Stroker init(PathConsumer2D pc2d,
|
||||
float lineWidth,
|
||||
int capStyle,
|
||||
int joinStyle,
|
||||
float miterLimit)
|
||||
Stroker init(final PathConsumer2D pc2d,
|
||||
final float lineWidth,
|
||||
final int capStyle,
|
||||
final int joinStyle,
|
||||
final float miterLimit,
|
||||
final float scale)
|
||||
{
|
||||
this.out = pc2d;
|
||||
|
||||
@ -158,13 +157,45 @@ final class Stroker implements PathConsumer2D, MarlinConst {
|
||||
this.capStyle = capStyle;
|
||||
this.joinStyle = joinStyle;
|
||||
|
||||
float limit = miterLimit * lineWidth2;
|
||||
final float limit = miterLimit * lineWidth2;
|
||||
this.miterLimitSq = limit * limit;
|
||||
|
||||
this.prev = CLOSE;
|
||||
|
||||
rdrCtx.stroking = 1;
|
||||
|
||||
if (rdrCtx.doClip) {
|
||||
// Adjust the clipping rectangle with the stroker margin (miter limit, width)
|
||||
float rdrOffX = 0.0f, rdrOffY = 0.0f;
|
||||
float margin = lineWidth2;
|
||||
|
||||
if (capStyle == CAP_SQUARE) {
|
||||
margin *= SQRT_2;
|
||||
}
|
||||
if ((joinStyle == JOIN_MITER) && (margin < limit)) {
|
||||
margin = limit;
|
||||
}
|
||||
if (scale != 1.0f) {
|
||||
margin *= scale;
|
||||
rdrOffX = scale * Renderer.RDR_OFFSET_X;
|
||||
rdrOffY = scale * Renderer.RDR_OFFSET_Y;
|
||||
}
|
||||
// add a small rounding error:
|
||||
margin += 1e-3f;
|
||||
|
||||
// bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY
|
||||
// adjust clip rectangle (ymin, ymax, xmin, xmax):
|
||||
final float[] _clipRect = rdrCtx.clipRect;
|
||||
_clipRect[0] -= margin - rdrOffY;
|
||||
_clipRect[1] += margin + rdrOffY;
|
||||
_clipRect[2] -= margin - rdrOffX;
|
||||
_clipRect[3] += margin + rdrOffX;
|
||||
this.clipRect = _clipRect;
|
||||
} else {
|
||||
this.clipRect = null;
|
||||
this.cOutCode = 0;
|
||||
this.sOutCode = 0;
|
||||
}
|
||||
return this; // fluent API
|
||||
}
|
||||
|
||||
@ -175,6 +206,9 @@ final class Stroker implements PathConsumer2D, MarlinConst {
|
||||
void dispose() {
|
||||
reverse.dispose();
|
||||
|
||||
opened = false;
|
||||
capStart = false;
|
||||
|
||||
if (DO_CLEAN_DIRTY) {
|
||||
// Force zero-fill dirty arrays:
|
||||
Arrays.fill(offset0, 0.0f);
|
||||
@ -445,19 +479,62 @@ final class Stroker implements PathConsumer2D, MarlinConst {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveTo(float x0, float y0) {
|
||||
if (prev == DRAWING_OP_TO) {
|
||||
finish();
|
||||
public void moveTo(final float x0, final float y0) {
|
||||
moveTo(x0, y0, cOutCode);
|
||||
// update starting point:
|
||||
this.sx0 = x0;
|
||||
this.sy0 = y0;
|
||||
this.sdx = 1.0f;
|
||||
this.sdy = 0.0f;
|
||||
this.opened = false;
|
||||
this.capStart = false;
|
||||
|
||||
if (clipRect != null) {
|
||||
final int outcode = Helpers.outcode(x0, y0, clipRect);
|
||||
this.cOutCode = outcode;
|
||||
this.sOutCode = outcode;
|
||||
}
|
||||
}
|
||||
|
||||
private void moveTo(final float x0, final float y0,
|
||||
final int outcode)
|
||||
{
|
||||
if (prev == MOVE_TO) {
|
||||
this.cx0 = x0;
|
||||
this.cy0 = y0;
|
||||
} else {
|
||||
if (prev == DRAWING_OP_TO) {
|
||||
finish(outcode);
|
||||
}
|
||||
this.prev = MOVE_TO;
|
||||
this.cx0 = x0;
|
||||
this.cy0 = y0;
|
||||
this.cdx = 1.0f;
|
||||
this.cdy = 0.0f;
|
||||
}
|
||||
this.sx0 = this.cx0 = x0;
|
||||
this.sy0 = this.cy0 = y0;
|
||||
this.cdx = this.sdx = 1.0f;
|
||||
this.cdy = this.sdy = 0.0f;
|
||||
this.prev = MOVE_TO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lineTo(float x1, float y1) {
|
||||
public void lineTo(final float x1, final float y1) {
|
||||
lineTo(x1, y1, false);
|
||||
}
|
||||
|
||||
private void lineTo(final float x1, final float y1,
|
||||
final boolean force)
|
||||
{
|
||||
final int outcode0 = this.cOutCode;
|
||||
if (!force && clipRect != null) {
|
||||
final int outcode1 = Helpers.outcode(x1, y1, clipRect);
|
||||
this.cOutCode = outcode1;
|
||||
|
||||
// basic rejection criteria
|
||||
if ((outcode0 & outcode1) != 0) {
|
||||
moveTo(x1, y1, outcode0);
|
||||
opened = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
float dx = x1 - cx0;
|
||||
float dy = y1 - cy0;
|
||||
if (dx == 0.0f && dy == 0.0f) {
|
||||
@ -467,7 +544,7 @@ final class Stroker implements PathConsumer2D, MarlinConst {
|
||||
final float mx = offset0[0];
|
||||
final float my = offset0[1];
|
||||
|
||||
drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my);
|
||||
drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my, outcode0);
|
||||
|
||||
emitLineTo(cx0 + mx, cy0 + my);
|
||||
emitLineTo( x1 + mx, y1 + my);
|
||||
@ -475,43 +552,65 @@ final class Stroker implements PathConsumer2D, MarlinConst {
|
||||
emitLineToRev(cx0 - mx, cy0 - my);
|
||||
emitLineToRev( x1 - mx, y1 - my);
|
||||
|
||||
this.cmx = mx;
|
||||
this.cmy = my;
|
||||
this.cdx = dx;
|
||||
this.cdy = dy;
|
||||
this.prev = DRAWING_OP_TO;
|
||||
this.cx0 = x1;
|
||||
this.cy0 = y1;
|
||||
this.prev = DRAWING_OP_TO;
|
||||
this.cdx = dx;
|
||||
this.cdy = dy;
|
||||
this.cmx = mx;
|
||||
this.cmy = my;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closePath() {
|
||||
if (prev != DRAWING_OP_TO) {
|
||||
// distinguish empty path at all vs opened path ?
|
||||
if (prev != DRAWING_OP_TO && !opened) {
|
||||
if (prev == CLOSE) {
|
||||
return;
|
||||
}
|
||||
emitMoveTo(cx0, cy0 - lineWidth2);
|
||||
this.cmx = this.smx = 0.0f;
|
||||
this.cmy = this.smy = -lineWidth2;
|
||||
this.cdx = this.sdx = 1.0f;
|
||||
this.cdy = this.sdy = 0.0f;
|
||||
finish();
|
||||
|
||||
this.sdx = 1.0f;
|
||||
this.sdy = 0.0f;
|
||||
this.cdx = 1.0f;
|
||||
this.cdy = 0.0f;
|
||||
|
||||
this.smx = 0.0f;
|
||||
this.smy = -lineWidth2;
|
||||
this.cmx = 0.0f;
|
||||
this.cmy = -lineWidth2;
|
||||
|
||||
finish(cOutCode);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cx0 != sx0 || cy0 != sy0) {
|
||||
lineTo(sx0, sy0);
|
||||
// basic acceptance criteria
|
||||
if ((sOutCode & cOutCode) == 0) {
|
||||
if (cx0 != sx0 || cy0 != sy0) {
|
||||
lineTo(sx0, sy0, true);
|
||||
}
|
||||
|
||||
drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy, sOutCode);
|
||||
|
||||
emitLineTo(sx0 + smx, sy0 + smy);
|
||||
|
||||
if (opened) {
|
||||
emitLineTo(sx0 - smx, sy0 - smy);
|
||||
} else {
|
||||
emitMoveTo(sx0 - smx, sy0 - smy);
|
||||
}
|
||||
}
|
||||
|
||||
drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy);
|
||||
|
||||
emitLineTo(sx0 + smx, sy0 + smy);
|
||||
|
||||
emitMoveTo(sx0 - smx, sy0 - smy);
|
||||
// Ignore caps like finish(false)
|
||||
emitReverse();
|
||||
|
||||
this.prev = CLOSE;
|
||||
emitClose();
|
||||
|
||||
if (opened) {
|
||||
// do not emit close
|
||||
opened = false;
|
||||
} else {
|
||||
emitClose();
|
||||
}
|
||||
}
|
||||
|
||||
private void emitReverse() {
|
||||
@ -521,7 +620,7 @@ final class Stroker implements PathConsumer2D, MarlinConst {
|
||||
@Override
|
||||
public void pathDone() {
|
||||
if (prev == DRAWING_OP_TO) {
|
||||
finish();
|
||||
finish(cOutCode);
|
||||
}
|
||||
|
||||
out.pathDone();
|
||||
@ -534,23 +633,39 @@ final class Stroker implements PathConsumer2D, MarlinConst {
|
||||
dispose();
|
||||
}
|
||||
|
||||
private void finish() {
|
||||
if (capStyle == CAP_ROUND) {
|
||||
drawRoundCap(cx0, cy0, cmx, cmy);
|
||||
} else if (capStyle == CAP_SQUARE) {
|
||||
emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy);
|
||||
emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy);
|
||||
private void finish(final int outcode) {
|
||||
// Problem: impossible to guess if the path will be closed in advance
|
||||
// i.e. if caps must be drawn or not ?
|
||||
// Solution: use the ClosedPathDetector before Stroker to determine
|
||||
// if the path is a closed path or not
|
||||
if (!rdrCtx.closedPath) {
|
||||
if (outcode == 0) {
|
||||
// current point = end's cap:
|
||||
if (capStyle == CAP_ROUND) {
|
||||
drawRoundCap(cx0, cy0, cmx, cmy);
|
||||
} else if (capStyle == CAP_SQUARE) {
|
||||
emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy);
|
||||
emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy);
|
||||
}
|
||||
}
|
||||
emitReverse();
|
||||
|
||||
if (!capStart) {
|
||||
capStart = true;
|
||||
|
||||
if (sOutCode == 0) {
|
||||
// starting point = initial cap:
|
||||
if (capStyle == CAP_ROUND) {
|
||||
drawRoundCap(sx0, sy0, -smx, -smy);
|
||||
} else if (capStyle == CAP_SQUARE) {
|
||||
emitLineTo(sx0 + smy - smx, sy0 - smx - smy);
|
||||
emitLineTo(sx0 + smy + smx, sy0 - smx + smy);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
emitReverse();
|
||||
}
|
||||
|
||||
emitReverse();
|
||||
|
||||
if (capStyle == CAP_ROUND) {
|
||||
drawRoundCap(sx0, sy0, -smx, -smy);
|
||||
} else if (capStyle == CAP_SQUARE) {
|
||||
emitLineTo(sx0 + smy - smx, sy0 - smx - smy);
|
||||
emitLineTo(sx0 + smy + smx, sy0 - smx + smy);
|
||||
}
|
||||
|
||||
emitClose();
|
||||
}
|
||||
|
||||
@ -622,23 +737,28 @@ final class Stroker implements PathConsumer2D, MarlinConst {
|
||||
float x0, float y0,
|
||||
float dx, float dy,
|
||||
float omx, float omy,
|
||||
float mx, float my)
|
||||
float mx, float my,
|
||||
final int outcode)
|
||||
{
|
||||
if (prev != DRAWING_OP_TO) {
|
||||
emitMoveTo(x0 + mx, y0 + my);
|
||||
this.sdx = dx;
|
||||
this.sdy = dy;
|
||||
this.smx = mx;
|
||||
this.smy = my;
|
||||
if (!opened) {
|
||||
this.sdx = dx;
|
||||
this.sdy = dy;
|
||||
this.smx = mx;
|
||||
this.smy = my;
|
||||
}
|
||||
} else {
|
||||
boolean cw = isCW(pdx, pdy, dx, dy);
|
||||
if (joinStyle == JOIN_MITER) {
|
||||
drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
|
||||
} else if (joinStyle == JOIN_ROUND) {
|
||||
drawRoundJoin(x0, y0,
|
||||
omx, omy,
|
||||
mx, my, cw,
|
||||
ROUND_JOIN_THRESHOLD);
|
||||
final boolean cw = isCW(pdx, pdy, dx, dy);
|
||||
if (outcode == 0) {
|
||||
if (joinStyle == JOIN_MITER) {
|
||||
drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
|
||||
} else if (joinStyle == JOIN_ROUND) {
|
||||
drawRoundJoin(x0, y0,
|
||||
omx, omy,
|
||||
mx, my, cw,
|
||||
ROUND_JOIN_THRESHOLD);
|
||||
}
|
||||
}
|
||||
emitLineTo(x0, y0, !cw);
|
||||
}
|
||||
@ -943,10 +1063,29 @@ final class Stroker implements PathConsumer2D, MarlinConst {
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override public void curveTo(float x1, float y1,
|
||||
float x2, float y2,
|
||||
float x3, float y3)
|
||||
@Override
|
||||
public void curveTo(final float x1, final float y1,
|
||||
final float x2, final float y2,
|
||||
final float x3, final float y3)
|
||||
{
|
||||
final int outcode0 = this.cOutCode;
|
||||
if (clipRect != null) {
|
||||
final int outcode3 = Helpers.outcode(x3, y3, clipRect);
|
||||
this.cOutCode = outcode3;
|
||||
|
||||
if ((outcode0 & outcode3) != 0) {
|
||||
final int outcode1 = Helpers.outcode(x1, y1, clipRect);
|
||||
final int outcode2 = Helpers.outcode(x2, y2, clipRect);
|
||||
|
||||
// basic rejection criteria
|
||||
if ((outcode0 & outcode1 & outcode2 & outcode3) != 0) {
|
||||
moveTo(x3, y3, outcode0);
|
||||
opened = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final float[] mid = middle;
|
||||
|
||||
mid[0] = cx0; mid[1] = cy0;
|
||||
@ -955,7 +1094,7 @@ final class Stroker implements PathConsumer2D, MarlinConst {
|
||||
mid[6] = x3; mid[7] = y3;
|
||||
|
||||
// need these so we can update the state at the end of this method
|
||||
final float xf = mid[6], yf = mid[7];
|
||||
final float xf = x3, yf = y3;
|
||||
float dxs = mid[2] - mid[0];
|
||||
float dys = mid[3] - mid[1];
|
||||
float dxf = mid[6] - mid[4];
|
||||
@ -981,6 +1120,10 @@ final class Stroker implements PathConsumer2D, MarlinConst {
|
||||
}
|
||||
if (dxs == 0.0f && dys == 0.0f) {
|
||||
// this happens if the "curve" is just a point
|
||||
// fix outcode0 for lineTo() call:
|
||||
if (clipRect != null) {
|
||||
this.cOutCode = outcode0;
|
||||
}
|
||||
lineTo(mid[0], mid[1]);
|
||||
return;
|
||||
}
|
||||
@ -999,7 +1142,7 @@ final class Stroker implements PathConsumer2D, MarlinConst {
|
||||
}
|
||||
|
||||
computeOffset(dxs, dys, lineWidth2, offset0);
|
||||
drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]);
|
||||
drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
|
||||
|
||||
final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2);
|
||||
|
||||
@ -1034,16 +1177,36 @@ final class Stroker implements PathConsumer2D, MarlinConst {
|
||||
emitLineToRev(r[kind - 2], r[kind - 1]);
|
||||
}
|
||||
|
||||
this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
|
||||
this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f;
|
||||
this.cdx = dxf;
|
||||
this.cdy = dyf;
|
||||
this.prev = DRAWING_OP_TO;
|
||||
this.cx0 = xf;
|
||||
this.cy0 = yf;
|
||||
this.prev = DRAWING_OP_TO;
|
||||
this.cdx = dxf;
|
||||
this.cdy = dyf;
|
||||
this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
|
||||
this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f;
|
||||
}
|
||||
|
||||
@Override public void quadTo(float x1, float y1, float x2, float y2) {
|
||||
@Override
|
||||
public void quadTo(final float x1, final float y1,
|
||||
final float x2, final float y2)
|
||||
{
|
||||
final int outcode0 = this.cOutCode;
|
||||
if (clipRect != null) {
|
||||
final int outcode2 = Helpers.outcode(x2, y2, clipRect);
|
||||
this.cOutCode = outcode2;
|
||||
|
||||
if ((outcode0 & outcode2) != 0) {
|
||||
final int outcode1 = Helpers.outcode(x1, y1, clipRect);
|
||||
|
||||
// basic rejection criteria
|
||||
if ((outcode0 & outcode1 & outcode2) != 0) {
|
||||
moveTo(x2, y2, outcode0);
|
||||
opened = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final float[] mid = middle;
|
||||
|
||||
mid[0] = cx0; mid[1] = cy0;
|
||||
@ -1051,7 +1214,7 @@ final class Stroker implements PathConsumer2D, MarlinConst {
|
||||
mid[4] = x2; mid[5] = y2;
|
||||
|
||||
// need these so we can update the state at the end of this method
|
||||
final float xf = mid[4], yf = mid[5];
|
||||
final float xf = x2, yf = y2;
|
||||
float dxs = mid[2] - mid[0];
|
||||
float dys = mid[3] - mid[1];
|
||||
float dxf = mid[4] - mid[2];
|
||||
@ -1062,6 +1225,10 @@ final class Stroker implements PathConsumer2D, MarlinConst {
|
||||
}
|
||||
if (dxs == 0.0f && dys == 0.0f) {
|
||||
// this happens if the "curve" is just a point
|
||||
// fix outcode0 for lineTo() call:
|
||||
if (clipRect != null) {
|
||||
this.cOutCode = outcode0;
|
||||
}
|
||||
lineTo(mid[0], mid[1]);
|
||||
return;
|
||||
}
|
||||
@ -1079,7 +1246,7 @@ final class Stroker implements PathConsumer2D, MarlinConst {
|
||||
}
|
||||
|
||||
computeOffset(dxs, dys, lineWidth2, offset0);
|
||||
drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]);
|
||||
drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
|
||||
|
||||
int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2);
|
||||
|
||||
@ -1114,214 +1281,16 @@ final class Stroker implements PathConsumer2D, MarlinConst {
|
||||
emitLineToRev(r[kind - 2], r[kind - 1]);
|
||||
}
|
||||
|
||||
this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
|
||||
this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f;
|
||||
this.cdx = dxf;
|
||||
this.cdy = dyf;
|
||||
this.prev = DRAWING_OP_TO;
|
||||
this.cx0 = xf;
|
||||
this.cy0 = yf;
|
||||
this.prev = DRAWING_OP_TO;
|
||||
this.cdx = dxf;
|
||||
this.cdy = dyf;
|
||||
this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
|
||||
this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0f;
|
||||
}
|
||||
|
||||
@Override public long getNativeConsumer() {
|
||||
throw new InternalError("Stroker doesn't use a native consumer");
|
||||
}
|
||||
|
||||
// a stack of polynomial curves where each curve shares endpoints with
|
||||
// adjacent ones.
|
||||
static final class PolyStack {
|
||||
private static final byte TYPE_LINETO = (byte) 0;
|
||||
private static final byte TYPE_QUADTO = (byte) 1;
|
||||
private static final byte TYPE_CUBICTO = (byte) 2;
|
||||
|
||||
// curves capacity = edges count (8192) = edges x 2 (coords)
|
||||
private static final int INITIAL_CURVES_COUNT = INITIAL_EDGES_COUNT << 1;
|
||||
|
||||
// types capacity = edges count (4096)
|
||||
private static final int INITIAL_TYPES_COUNT = INITIAL_EDGES_COUNT;
|
||||
|
||||
float[] curves;
|
||||
int end;
|
||||
byte[] curveTypes;
|
||||
int numCurves;
|
||||
|
||||
// per-thread renderer context
|
||||
final RendererContext rdrCtx;
|
||||
|
||||
// curves ref (dirty)
|
||||
final FloatArrayCache.Reference curves_ref;
|
||||
// curveTypes ref (dirty)
|
||||
final ByteArrayCache.Reference curveTypes_ref;
|
||||
|
||||
// used marks (stats only)
|
||||
int curveTypesUseMark;
|
||||
int curvesUseMark;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param rdrCtx per-thread renderer context
|
||||
*/
|
||||
PolyStack(final RendererContext rdrCtx) {
|
||||
this.rdrCtx = rdrCtx;
|
||||
|
||||
curves_ref = rdrCtx.newDirtyFloatArrayRef(INITIAL_CURVES_COUNT); // 32K
|
||||
curves = curves_ref.initial;
|
||||
|
||||
curveTypes_ref = rdrCtx.newDirtyByteArrayRef(INITIAL_TYPES_COUNT); // 4K
|
||||
curveTypes = curveTypes_ref.initial;
|
||||
numCurves = 0;
|
||||
end = 0;
|
||||
|
||||
if (DO_STATS) {
|
||||
curveTypesUseMark = 0;
|
||||
curvesUseMark = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes this PolyStack:
|
||||
* clean up before reusing this instance
|
||||
*/
|
||||
void dispose() {
|
||||
end = 0;
|
||||
numCurves = 0;
|
||||
|
||||
if (DO_STATS) {
|
||||
rdrCtx.stats.stat_rdr_poly_stack_types.add(curveTypesUseMark);
|
||||
rdrCtx.stats.stat_rdr_poly_stack_curves.add(curvesUseMark);
|
||||
rdrCtx.stats.hist_rdr_poly_stack_curves.add(curvesUseMark);
|
||||
|
||||
// reset marks
|
||||
curveTypesUseMark = 0;
|
||||
curvesUseMark = 0;
|
||||
}
|
||||
|
||||
// Return arrays:
|
||||
// curves and curveTypes are kept dirty
|
||||
curves = curves_ref.putArray(curves);
|
||||
curveTypes = curveTypes_ref.putArray(curveTypes);
|
||||
}
|
||||
|
||||
private void ensureSpace(final int n) {
|
||||
// use substraction to avoid integer overflow:
|
||||
if (curves.length - end < n) {
|
||||
if (DO_STATS) {
|
||||
rdrCtx.stats.stat_array_stroker_polystack_curves
|
||||
.add(end + n);
|
||||
}
|
||||
curves = curves_ref.widenArray(curves, end, end + n);
|
||||
}
|
||||
if (curveTypes.length <= numCurves) {
|
||||
if (DO_STATS) {
|
||||
rdrCtx.stats.stat_array_stroker_polystack_curveTypes
|
||||
.add(numCurves + 1);
|
||||
}
|
||||
curveTypes = curveTypes_ref.widenArray(curveTypes,
|
||||
numCurves,
|
||||
numCurves + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void pushCubic(float x0, float y0,
|
||||
float x1, float y1,
|
||||
float x2, float y2)
|
||||
{
|
||||
ensureSpace(6);
|
||||
curveTypes[numCurves++] = TYPE_CUBICTO;
|
||||
// we reverse the coordinate order to make popping easier
|
||||
final float[] _curves = curves;
|
||||
int e = end;
|
||||
_curves[e++] = x2; _curves[e++] = y2;
|
||||
_curves[e++] = x1; _curves[e++] = y1;
|
||||
_curves[e++] = x0; _curves[e++] = y0;
|
||||
end = e;
|
||||
}
|
||||
|
||||
void pushQuad(float x0, float y0,
|
||||
float x1, float y1)
|
||||
{
|
||||
ensureSpace(4);
|
||||
curveTypes[numCurves++] = TYPE_QUADTO;
|
||||
final float[] _curves = curves;
|
||||
int e = end;
|
||||
_curves[e++] = x1; _curves[e++] = y1;
|
||||
_curves[e++] = x0; _curves[e++] = y0;
|
||||
end = e;
|
||||
}
|
||||
|
||||
void pushLine(float x, float y) {
|
||||
ensureSpace(2);
|
||||
curveTypes[numCurves++] = TYPE_LINETO;
|
||||
curves[end++] = x; curves[end++] = y;
|
||||
}
|
||||
|
||||
void popAll(PathConsumer2D io) {
|
||||
if (DO_STATS) {
|
||||
// update used marks:
|
||||
if (numCurves > curveTypesUseMark) {
|
||||
curveTypesUseMark = numCurves;
|
||||
}
|
||||
if (end > curvesUseMark) {
|
||||
curvesUseMark = end;
|
||||
}
|
||||
}
|
||||
final byte[] _curveTypes = curveTypes;
|
||||
final float[] _curves = curves;
|
||||
int nc = numCurves;
|
||||
int e = end;
|
||||
|
||||
while (nc != 0) {
|
||||
switch(_curveTypes[--nc]) {
|
||||
case TYPE_LINETO:
|
||||
e -= 2;
|
||||
io.lineTo(_curves[e], _curves[e+1]);
|
||||
continue;
|
||||
case TYPE_QUADTO:
|
||||
e -= 4;
|
||||
io.quadTo(_curves[e+0], _curves[e+1],
|
||||
_curves[e+2], _curves[e+3]);
|
||||
continue;
|
||||
case TYPE_CUBICTO:
|
||||
e -= 6;
|
||||
io.curveTo(_curves[e+0], _curves[e+1],
|
||||
_curves[e+2], _curves[e+3],
|
||||
_curves[e+4], _curves[e+5]);
|
||||
continue;
|
||||
default:
|
||||
}
|
||||
}
|
||||
numCurves = 0;
|
||||
end = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String ret = "";
|
||||
int nc = numCurves;
|
||||
int last = end;
|
||||
int len;
|
||||
while (nc != 0) {
|
||||
switch(curveTypes[--nc]) {
|
||||
case TYPE_LINETO:
|
||||
len = 2;
|
||||
ret += "line: ";
|
||||
break;
|
||||
case TYPE_QUADTO:
|
||||
len = 4;
|
||||
ret += "quad: ";
|
||||
break;
|
||||
case TYPE_CUBICTO:
|
||||
len = 6;
|
||||
ret += "cubic: ";
|
||||
break;
|
||||
default:
|
||||
len = 0;
|
||||
}
|
||||
last -= len;
|
||||
ret += Arrays.toString(Arrays.copyOfRange(curves, last, last+len))
|
||||
+ "\n";
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,50 +28,169 @@ package sun.java2d.marlin;
|
||||
import sun.awt.geom.PathConsumer2D;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Path2D;
|
||||
import sun.java2d.marlin.Helpers.IndexStack;
|
||||
import sun.java2d.marlin.Helpers.PolyStack;
|
||||
|
||||
final class TransformingPathConsumer2D {
|
||||
|
||||
TransformingPathConsumer2D() {
|
||||
// used by RendererContext
|
||||
}
|
||||
private final RendererContext rdrCtx;
|
||||
|
||||
// recycled PathConsumer2D instance from wrapPath2d()
|
||||
// recycled ClosedPathDetector instance from detectClosedPath()
|
||||
private final ClosedPathDetector cpDetector;
|
||||
|
||||
// recycled PathClipFilter instance from pathClipper()
|
||||
private final PathClipFilter pathClipper;
|
||||
|
||||
// recycled PathConsumer2D instance from wrapPath2D()
|
||||
private final Path2DWrapper wp_Path2DWrapper = new Path2DWrapper();
|
||||
|
||||
PathConsumer2D wrapPath2d(Path2D.Float p2d)
|
||||
{
|
||||
return wp_Path2DWrapper.init(p2d);
|
||||
}
|
||||
|
||||
// recycled PathConsumer2D instances from deltaTransformConsumer()
|
||||
private final DeltaScaleFilter dt_DeltaScaleFilter = new DeltaScaleFilter();
|
||||
private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter();
|
||||
|
||||
// recycled PathConsumer2D instances from inverseDeltaTransformConsumer()
|
||||
private final DeltaScaleFilter iv_DeltaScaleFilter = new DeltaScaleFilter();
|
||||
private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter();
|
||||
|
||||
// recycled PathTracer instances from tracer...() methods
|
||||
private final PathTracer tracerInput = new PathTracer("[Input]");
|
||||
private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector");
|
||||
private final PathTracer tracerFiller = new PathTracer("Filler");
|
||||
private final PathTracer tracerStroker = new PathTracer("Stroker");
|
||||
|
||||
TransformingPathConsumer2D(final RendererContext rdrCtx) {
|
||||
// used by RendererContext
|
||||
this.rdrCtx = rdrCtx;
|
||||
this.cpDetector = new ClosedPathDetector(rdrCtx);
|
||||
this.pathClipper = new PathClipFilter(rdrCtx);
|
||||
}
|
||||
|
||||
PathConsumer2D wrapPath2D(Path2D.Float p2d) {
|
||||
return wp_Path2DWrapper.init(p2d);
|
||||
}
|
||||
|
||||
PathConsumer2D traceInput(PathConsumer2D out) {
|
||||
return tracerInput.init(out);
|
||||
}
|
||||
|
||||
PathConsumer2D traceClosedPathDetector(PathConsumer2D out) {
|
||||
return tracerCPDetector.init(out);
|
||||
}
|
||||
|
||||
PathConsumer2D traceFiller(PathConsumer2D out) {
|
||||
return tracerFiller.init(out);
|
||||
}
|
||||
|
||||
PathConsumer2D traceStroker(PathConsumer2D out) {
|
||||
return tracerStroker.init(out);
|
||||
}
|
||||
|
||||
PathConsumer2D detectClosedPath(PathConsumer2D out) {
|
||||
return cpDetector.init(out);
|
||||
}
|
||||
|
||||
PathConsumer2D pathClipper(PathConsumer2D out) {
|
||||
return pathClipper.init(out);
|
||||
}
|
||||
|
||||
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();
|
||||
final float mxx = (float) at.getScaleX();
|
||||
final float mxy = (float) at.getShearX();
|
||||
final float myx = (float) at.getShearY();
|
||||
final float myy = (float) at.getScaleY();
|
||||
|
||||
if (mxy == 0.0f && myx == 0.0f) {
|
||||
if (mxx == 1.0f && myy == 1.0f) {
|
||||
return out;
|
||||
} else {
|
||||
// Scale only
|
||||
if (rdrCtx.doClip) {
|
||||
// adjust clip rectangle (ymin, ymax, xmin, xmax):
|
||||
adjustClipScale(rdrCtx.clipRect, mxx, myy);
|
||||
}
|
||||
return dt_DeltaScaleFilter.init(out, mxx, myy);
|
||||
}
|
||||
} else {
|
||||
if (rdrCtx.doClip) {
|
||||
// adjust clip rectangle (ymin, ymax, xmin, xmax):
|
||||
adjustClipInverseDelta(rdrCtx.clipRect, mxx, mxy, myx, myy);
|
||||
}
|
||||
return dt_DeltaTransformFilter.init(out, mxx, mxy, myx, myy);
|
||||
}
|
||||
}
|
||||
|
||||
// recycled PathConsumer2D instances from inverseDeltaTransformConsumer()
|
||||
private final DeltaScaleFilter iv_DeltaScaleFilter = new DeltaScaleFilter();
|
||||
private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter();
|
||||
private static void adjustClipOffset(final float[] clipRect) {
|
||||
clipRect[0] += Renderer.RDR_OFFSET_Y;
|
||||
clipRect[1] += Renderer.RDR_OFFSET_Y;
|
||||
clipRect[2] += Renderer.RDR_OFFSET_X;
|
||||
clipRect[3] += Renderer.RDR_OFFSET_X;
|
||||
}
|
||||
|
||||
private static void adjustClipScale(final float[] clipRect,
|
||||
final float mxx, final float myy)
|
||||
{
|
||||
adjustClipOffset(clipRect);
|
||||
|
||||
// Adjust the clipping rectangle (iv_DeltaScaleFilter):
|
||||
clipRect[0] /= myy;
|
||||
clipRect[1] /= myy;
|
||||
clipRect[2] /= mxx;
|
||||
clipRect[3] /= mxx;
|
||||
}
|
||||
|
||||
private static void adjustClipInverseDelta(final float[] clipRect,
|
||||
final float mxx, final float mxy,
|
||||
final float myx, final float myy)
|
||||
{
|
||||
adjustClipOffset(clipRect);
|
||||
|
||||
// Adjust the clipping rectangle (iv_DeltaTransformFilter):
|
||||
final float det = mxx * myy - mxy * myx;
|
||||
final float imxx = myy / det;
|
||||
final float imxy = -mxy / det;
|
||||
final float imyx = -myx / det;
|
||||
final float imyy = mxx / det;
|
||||
|
||||
float xmin, xmax, ymin, ymax;
|
||||
float x, y;
|
||||
// xmin, ymin:
|
||||
x = clipRect[2] * imxx + clipRect[0] * imxy;
|
||||
y = clipRect[2] * imyx + clipRect[0] * imyy;
|
||||
|
||||
xmin = xmax = x;
|
||||
ymin = ymax = y;
|
||||
|
||||
// xmax, ymin:
|
||||
x = clipRect[3] * imxx + clipRect[0] * imxy;
|
||||
y = clipRect[3] * imyx + clipRect[0] * imyy;
|
||||
|
||||
if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; }
|
||||
if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; }
|
||||
|
||||
// xmin, ymax:
|
||||
x = clipRect[2] * imxx + clipRect[1] * imxy;
|
||||
y = clipRect[2] * imyx + clipRect[1] * imyy;
|
||||
|
||||
if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; }
|
||||
if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; }
|
||||
|
||||
// xmax, ymax:
|
||||
x = clipRect[3] * imxx + clipRect[1] * imxy;
|
||||
y = clipRect[3] * imyx + clipRect[1] * imyy;
|
||||
|
||||
if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; }
|
||||
if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; }
|
||||
|
||||
clipRect[0] = ymin;
|
||||
clipRect[1] = ymax;
|
||||
clipRect[2] = xmin;
|
||||
clipRect[3] = xmax;
|
||||
}
|
||||
|
||||
PathConsumer2D inverseDeltaTransformConsumer(PathConsumer2D out,
|
||||
AffineTransform at)
|
||||
@ -91,7 +210,7 @@ final class TransformingPathConsumer2D {
|
||||
return iv_DeltaScaleFilter.init(out, 1.0f/mxx, 1.0f/myy);
|
||||
}
|
||||
} else {
|
||||
float det = mxx * myy - mxy * myx;
|
||||
final float det = mxx * myy - mxy * myx;
|
||||
return iv_DeltaTransformFilter.init(out,
|
||||
myy / det,
|
||||
-mxy / det,
|
||||
@ -100,7 +219,6 @@ final class TransformingPathConsumer2D {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static final class DeltaScaleFilter implements PathConsumer2D {
|
||||
private PathConsumer2D out;
|
||||
private float sx, sy;
|
||||
@ -275,4 +393,427 @@ final class TransformingPathConsumer2D {
|
||||
throw new InternalError("Not using a native peer");
|
||||
}
|
||||
}
|
||||
|
||||
static final class ClosedPathDetector implements PathConsumer2D {
|
||||
|
||||
private final RendererContext rdrCtx;
|
||||
private final PolyStack stack;
|
||||
|
||||
private PathConsumer2D out;
|
||||
|
||||
ClosedPathDetector(final RendererContext rdrCtx) {
|
||||
this.rdrCtx = rdrCtx;
|
||||
this.stack = (rdrCtx.stats != null) ?
|
||||
new PolyStack(rdrCtx,
|
||||
rdrCtx.stats.stat_cpd_polystack_types,
|
||||
rdrCtx.stats.stat_cpd_polystack_curves,
|
||||
rdrCtx.stats.hist_cpd_polystack_curves,
|
||||
rdrCtx.stats.stat_array_cpd_polystack_curves,
|
||||
rdrCtx.stats.stat_array_cpd_polystack_types)
|
||||
: new PolyStack(rdrCtx);
|
||||
}
|
||||
|
||||
ClosedPathDetector init(PathConsumer2D out) {
|
||||
this.out = out;
|
||||
return this; // fluent API
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes this instance:
|
||||
* clean up before reusing this instance
|
||||
*/
|
||||
void dispose() {
|
||||
stack.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pathDone() {
|
||||
// previous path is not closed:
|
||||
finish(false);
|
||||
out.pathDone();
|
||||
|
||||
// TODO: fix possible leak if exception happened
|
||||
// Dispose this instance:
|
||||
dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closePath() {
|
||||
// path is closed
|
||||
finish(true);
|
||||
out.closePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveTo(float x0, float y0) {
|
||||
// previous path is not closed:
|
||||
finish(false);
|
||||
out.moveTo(x0, y0);
|
||||
}
|
||||
|
||||
private void finish(final boolean closed) {
|
||||
rdrCtx.closedPath = closed;
|
||||
stack.pullAll(out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lineTo(float x1, float y1) {
|
||||
stack.pushLine(x1, y1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void curveTo(float x3, float y3,
|
||||
float x2, float y2,
|
||||
float x1, float y1)
|
||||
{
|
||||
stack.pushCubic(x1, y1, x2, y2, x3, y3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void quadTo(float x2, float y2, float x1, float y1) {
|
||||
stack.pushQuad(x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNativeConsumer() {
|
||||
throw new InternalError("Not using a native peer");
|
||||
}
|
||||
}
|
||||
|
||||
static final class PathClipFilter implements PathConsumer2D {
|
||||
|
||||
private PathConsumer2D out;
|
||||
|
||||
// Bounds of the drawing region, at pixel precision.
|
||||
private final float[] clipRect;
|
||||
|
||||
private final float[] corners = new float[8];
|
||||
private boolean init_corners = false;
|
||||
|
||||
private final IndexStack stack;
|
||||
|
||||
// the current outcode of the current sub path
|
||||
private int cOutCode = 0;
|
||||
|
||||
// the cumulated (and) outcode of the complete path
|
||||
private int gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
|
||||
|
||||
private boolean outside = false;
|
||||
|
||||
// The current point OUTSIDE
|
||||
private float cx0, cy0;
|
||||
|
||||
PathClipFilter(final RendererContext rdrCtx) {
|
||||
this.clipRect = rdrCtx.clipRect;
|
||||
this.stack = (rdrCtx.stats != null) ?
|
||||
new IndexStack(rdrCtx,
|
||||
rdrCtx.stats.stat_pcf_idxstack_indices,
|
||||
rdrCtx.stats.hist_pcf_idxstack_indices,
|
||||
rdrCtx.stats.stat_array_pcf_idxstack_indices)
|
||||
: new IndexStack(rdrCtx);
|
||||
}
|
||||
|
||||
PathClipFilter init(final PathConsumer2D out) {
|
||||
this.out = out;
|
||||
|
||||
// Adjust the clipping rectangle with the renderer offsets
|
||||
final float rdrOffX = Renderer.RDR_OFFSET_X;
|
||||
final float rdrOffY = Renderer.RDR_OFFSET_Y;
|
||||
|
||||
// add a small rounding error:
|
||||
final float margin = 1e-3f;
|
||||
|
||||
final float[] _clipRect = this.clipRect;
|
||||
_clipRect[0] -= margin - rdrOffY;
|
||||
_clipRect[1] += margin + rdrOffY;
|
||||
_clipRect[2] -= margin - rdrOffX;
|
||||
_clipRect[3] += margin + rdrOffX;
|
||||
|
||||
this.init_corners = true;
|
||||
this.gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
|
||||
|
||||
return this; // fluent API
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes this instance:
|
||||
* clean up before reusing this instance
|
||||
*/
|
||||
void dispose() {
|
||||
stack.dispose();
|
||||
}
|
||||
|
||||
private void finishPath() {
|
||||
if (outside) {
|
||||
// criteria: inside or totally outside ?
|
||||
if (gOutCode == 0) {
|
||||
finish();
|
||||
} else {
|
||||
this.outside = false;
|
||||
stack.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void finish() {
|
||||
this.outside = false;
|
||||
|
||||
if (!stack.isEmpty()) {
|
||||
if (init_corners) {
|
||||
init_corners = false;
|
||||
|
||||
final float[] _corners = corners;
|
||||
final float[] _clipRect = clipRect;
|
||||
// Top Left (0):
|
||||
_corners[0] = _clipRect[2];
|
||||
_corners[1] = _clipRect[0];
|
||||
// Bottom Left (1):
|
||||
_corners[2] = _clipRect[2];
|
||||
_corners[3] = _clipRect[1];
|
||||
// Top right (2):
|
||||
_corners[4] = _clipRect[3];
|
||||
_corners[5] = _clipRect[0];
|
||||
// Bottom Right (3):
|
||||
_corners[6] = _clipRect[3];
|
||||
_corners[7] = _clipRect[1];
|
||||
}
|
||||
stack.pullAll(corners, out);
|
||||
}
|
||||
out.lineTo(cx0, cy0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pathDone() {
|
||||
finishPath();
|
||||
|
||||
out.pathDone();
|
||||
|
||||
// TODO: fix possible leak if exception happened
|
||||
// Dispose this instance:
|
||||
dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closePath() {
|
||||
finishPath();
|
||||
|
||||
out.closePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveTo(final float x0, final float y0) {
|
||||
finishPath();
|
||||
|
||||
final int outcode = Helpers.outcode(x0, y0, clipRect);
|
||||
this.cOutCode = outcode;
|
||||
this.outside = false;
|
||||
out.moveTo(x0, y0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lineTo(final float xe, final float ye) {
|
||||
final int outcode0 = this.cOutCode;
|
||||
final int outcode1 = Helpers.outcode(xe, ye, clipRect);
|
||||
this.cOutCode = outcode1;
|
||||
|
||||
final int sideCode = (outcode0 & outcode1);
|
||||
|
||||
// basic rejection criteria:
|
||||
if (sideCode == 0) {
|
||||
this.gOutCode = 0;
|
||||
} else {
|
||||
this.gOutCode &= sideCode;
|
||||
// keep last point coordinate before entering the clip again:
|
||||
this.outside = true;
|
||||
this.cx0 = xe;
|
||||
this.cy0 = ye;
|
||||
|
||||
clip(sideCode, outcode0, outcode1);
|
||||
return;
|
||||
}
|
||||
if (outside) {
|
||||
finish();
|
||||
}
|
||||
// clipping disabled:
|
||||
out.lineTo(xe, ye);
|
||||
}
|
||||
|
||||
private void clip(final int sideCode,
|
||||
final int outcode0,
|
||||
final int outcode1)
|
||||
{
|
||||
// corner or cross-boundary on left or right side:
|
||||
if ((outcode0 != outcode1)
|
||||
&& ((sideCode & MarlinConst.OUTCODE_MASK_L_R) != 0))
|
||||
{
|
||||
// combine outcodes:
|
||||
final int mergeCode = (outcode0 | outcode1);
|
||||
final int tbCode = mergeCode & MarlinConst.OUTCODE_MASK_T_B;
|
||||
final int lrCode = mergeCode & MarlinConst.OUTCODE_MASK_L_R;
|
||||
final int off = (lrCode == MarlinConst.OUTCODE_LEFT) ? 0 : 2;
|
||||
|
||||
// add corners to outside stack:
|
||||
switch (tbCode) {
|
||||
case MarlinConst.OUTCODE_TOP:
|
||||
// System.out.println("TOP "+ ((off == 0) ? "LEFT" : "RIGHT"));
|
||||
stack.push(off); // top
|
||||
return;
|
||||
case MarlinConst.OUTCODE_BOTTOM:
|
||||
// System.out.println("BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT"));
|
||||
stack.push(off + 1); // bottom
|
||||
return;
|
||||
default:
|
||||
// both TOP / BOTTOM:
|
||||
if ((outcode0 & MarlinConst.OUTCODE_TOP) != 0) {
|
||||
// System.out.println("TOP + BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT"));
|
||||
// top to bottom
|
||||
stack.push(off); // top
|
||||
stack.push(off + 1); // bottom
|
||||
} else {
|
||||
// System.out.println("BOTTOM + TOP "+ ((off == 0) ? "LEFT" : "RIGHT"));
|
||||
// bottom to top
|
||||
stack.push(off + 1); // bottom
|
||||
stack.push(off); // top
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void curveTo(final float x1, final float y1,
|
||||
final float x2, final float y2,
|
||||
final float xe, final float ye)
|
||||
{
|
||||
final int outcode0 = this.cOutCode;
|
||||
final int outcode3 = Helpers.outcode(xe, ye, clipRect);
|
||||
this.cOutCode = outcode3;
|
||||
|
||||
int sideCode = outcode0 & outcode3;
|
||||
|
||||
if (sideCode == 0) {
|
||||
this.gOutCode = 0;
|
||||
} else {
|
||||
sideCode &= Helpers.outcode(x1, y1, clipRect);
|
||||
sideCode &= Helpers.outcode(x2, y2, clipRect);
|
||||
this.gOutCode &= sideCode;
|
||||
|
||||
// basic rejection criteria:
|
||||
if (sideCode != 0) {
|
||||
// keep last point coordinate before entering the clip again:
|
||||
this.outside = true;
|
||||
this.cx0 = xe;
|
||||
this.cy0 = ye;
|
||||
|
||||
clip(sideCode, outcode0, outcode3);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (outside) {
|
||||
finish();
|
||||
}
|
||||
// clipping disabled:
|
||||
out.curveTo(x1, y1, x2, y2, xe, ye);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void quadTo(final float x1, final float y1,
|
||||
final float xe, final float ye)
|
||||
{
|
||||
final int outcode0 = this.cOutCode;
|
||||
final int outcode2 = Helpers.outcode(xe, ye, clipRect);
|
||||
this.cOutCode = outcode2;
|
||||
|
||||
int sideCode = outcode0 & outcode2;
|
||||
|
||||
if (sideCode == 0) {
|
||||
this.gOutCode = 0;
|
||||
} else {
|
||||
sideCode &= Helpers.outcode(x1, y1, clipRect);
|
||||
this.gOutCode &= sideCode;
|
||||
|
||||
// basic rejection criteria:
|
||||
if (sideCode != 0) {
|
||||
// keep last point coordinate before entering the clip again:
|
||||
this.outside = true;
|
||||
this.cx0 = xe;
|
||||
this.cy0 = ye;
|
||||
|
||||
clip(sideCode, outcode0, outcode2);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (outside) {
|
||||
finish();
|
||||
}
|
||||
// clipping disabled:
|
||||
out.quadTo(x1, y1, xe, ye);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNativeConsumer() {
|
||||
throw new InternalError("Not using a native peer");
|
||||
}
|
||||
}
|
||||
|
||||
static final class PathTracer implements PathConsumer2D {
|
||||
private final String prefix;
|
||||
private PathConsumer2D out;
|
||||
|
||||
PathTracer(String name) {
|
||||
this.prefix = name + ": ";
|
||||
}
|
||||
|
||||
PathTracer init(PathConsumer2D out) {
|
||||
this.out = out;
|
||||
return this; // fluent API
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveTo(float x0, float y0) {
|
||||
log("moveTo (" + x0 + ", " + y0 + ')');
|
||||
out.moveTo(x0, y0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lineTo(float x1, float y1) {
|
||||
log("lineTo (" + x1 + ", " + y1 + ')');
|
||||
out.lineTo(x1, y1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void curveTo(float x1, float y1,
|
||||
float x2, float y2,
|
||||
float x3, float y3)
|
||||
{
|
||||
log("curveTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ')');
|
||||
out.curveTo(x1, y1, x2, y2, x3, y3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void quadTo(float x1, float y1, float x2, float y2) {
|
||||
log("quadTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ')');
|
||||
out.quadTo(x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closePath() {
|
||||
log("closePath");
|
||||
out.closePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pathDone() {
|
||||
log("pathDone");
|
||||
out.pathDone();
|
||||
}
|
||||
|
||||
private void log(final String message) {
|
||||
System.out.println(prefix + message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNativeConsumer() {
|
||||
throw new InternalError("Not using a native peer");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ package sun.java2d.marlin;
|
||||
|
||||
public final class Version {
|
||||
|
||||
private static final String VERSION = "marlin-0.7.5-Unsafe-OpenJDK";
|
||||
private static final String VERSION = "marlin-0.8.2-Unsafe-OpenJDK";
|
||||
|
||||
public static String getVersion() {
|
||||
return VERSION;
|
||||
|
865
test/jdk/sun/java2d/marlin/ClipShapeTest.java
Normal file
865
test/jdk/sun/java2d/marlin/ClipShapeTest.java
Normal file
@ -0,0 +1,865 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.Shape;
|
||||
import java.awt.Stroke;
|
||||
import java.awt.geom.Ellipse2D;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.awt.geom.PathIterator;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBufferInt;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.LogRecord;
|
||||
import java.util.logging.Logger;
|
||||
import javax.imageio.IIOImage;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageWriteParam;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8191814
|
||||
* @summary Verifies that Marlin rendering generates the same
|
||||
* images with and without clipping optimization with all possible
|
||||
* stroke (cap/join) and fill modes (EO rules)
|
||||
* Note: Use the argument -slow to run more intensive tests (too much time)
|
||||
* @run main/othervm/timeout=120 -Dsun.java2d.renderer=sun.java2d.marlin.MarlinRenderingEngine ClipShapeTest
|
||||
* @run main/othervm/timeout=120 -Dsun.java2d.renderer=sun.java2d.marlin.DMarlinRenderingEngine ClipShapeTest
|
||||
*/
|
||||
public final class ClipShapeTest {
|
||||
|
||||
static final boolean TEST_STROKER = true;
|
||||
static final boolean TEST_FILLER = true;
|
||||
|
||||
// complementary tests in slow mode:
|
||||
static boolean USE_DASHES = false;
|
||||
static boolean USE_VAR_STROKE = false;
|
||||
|
||||
static int NUM_TESTS = 5000;
|
||||
static final int TESTW = 100;
|
||||
static final int TESTH = 100;
|
||||
|
||||
// shape settings:
|
||||
static final ShapeMode SHAPE_MODE = ShapeMode.NINE_LINE_POLYS;
|
||||
static final boolean SHAPE_REPEAT = true;
|
||||
|
||||
// dump path on console:
|
||||
static final boolean DUMP_SHAPE = true;
|
||||
|
||||
static final boolean SHOW_DETAILS = true;
|
||||
static final boolean SHOW_OUTLINE = true;
|
||||
static final boolean SHOW_POINTS = true;
|
||||
static final boolean SHOW_INFO = false;
|
||||
|
||||
static final int MAX_SHOW_FRAMES = 10;
|
||||
|
||||
// use fixed seed to reproduce always same polygons between tests
|
||||
static final boolean FIXED_SEED = false;
|
||||
static final double RAND_SCALE = 3.0;
|
||||
static final double RANDW = TESTW * RAND_SCALE;
|
||||
static final double OFFW = (TESTW - RANDW) / 2.0;
|
||||
static final double RANDH = TESTH * RAND_SCALE;
|
||||
static final double OFFH = (TESTH - RANDH) / 2.0;
|
||||
|
||||
static enum ShapeMode {
|
||||
TWO_CUBICS,
|
||||
FOUR_QUADS,
|
||||
FIVE_LINE_POLYS,
|
||||
NINE_LINE_POLYS,
|
||||
FIFTY_LINE_POLYS,
|
||||
MIXED
|
||||
}
|
||||
|
||||
static final long SEED = 1666133789L;
|
||||
// Fixed seed to avoid any difference between runs:
|
||||
static final Random RANDOM = new Random(SEED);
|
||||
|
||||
static final File OUTPUT_DIR = new File(".");
|
||||
|
||||
/**
|
||||
* Test
|
||||
* @param args
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
boolean runSlowTests = (args.length != 0 && "-slow".equals(args[0]));
|
||||
|
||||
if (runSlowTests) {
|
||||
NUM_TESTS = 20000; // or 100000 (very slow)
|
||||
USE_DASHES = true;
|
||||
USE_VAR_STROKE = true;
|
||||
}
|
||||
|
||||
Locale.setDefault(Locale.US);
|
||||
|
||||
// Get Marlin runtime state from its log:
|
||||
final AtomicBoolean isMarlin = new AtomicBoolean();
|
||||
final AtomicBoolean isClipRuntime = new AtomicBoolean();
|
||||
|
||||
// initialize j.u.l Looger:
|
||||
final Logger log = Logger.getLogger("sun.java2d.marlin");
|
||||
log.addHandler(new Handler() {
|
||||
@Override
|
||||
public void publish(LogRecord record) {
|
||||
final String msg = record.getMessage();
|
||||
if (msg != null) {
|
||||
// last space to avoid matching other settings:
|
||||
if (msg.startsWith("sun.java2d.renderer ")) {
|
||||
isMarlin.set(msg.contains("MarlinRenderingEngine"));
|
||||
}
|
||||
if (msg.startsWith("sun.java2d.renderer.clip.runtime.enable")) {
|
||||
isClipRuntime.set(msg.contains("true"));
|
||||
}
|
||||
}
|
||||
|
||||
final Throwable th = record.getThrown();
|
||||
// detect any Throwable:
|
||||
if (th != null) {
|
||||
System.out.println("Test failed:\n" + record.getMessage());
|
||||
th.printStackTrace(System.out);
|
||||
|
||||
throw new RuntimeException("Test failed: ", th);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws SecurityException {
|
||||
}
|
||||
});
|
||||
|
||||
// enable Marlin logging & internal checks:
|
||||
System.setProperty("sun.java2d.renderer.log", "true");
|
||||
System.setProperty("sun.java2d.renderer.useLogger", "true");
|
||||
|
||||
// disable static clipping setting:
|
||||
System.setProperty("sun.java2d.renderer.clip", "false");
|
||||
System.setProperty("sun.java2d.renderer.clip.runtime.enable", "true");
|
||||
|
||||
System.out.println("ClipShapeTests: image = " + TESTW + " x " + TESTH);
|
||||
|
||||
int failures = 0;
|
||||
final long start = System.nanoTime();
|
||||
try {
|
||||
// TODO: test affine transforms ?
|
||||
|
||||
if (TEST_STROKER) {
|
||||
final float[][] dashArrays = (USE_DASHES)
|
||||
? new float[][]{null, new float[]{1f, 2f}}
|
||||
: new float[][]{null};
|
||||
|
||||
System.out.println("dashes: " + Arrays.toString(dashArrays));
|
||||
|
||||
final float[] strokeWidths = (USE_VAR_STROKE)
|
||||
? new float[5] : new float[]{8f};
|
||||
|
||||
int nsw = 0;
|
||||
if (USE_VAR_STROKE) {
|
||||
for (float width = 0.1f; width < 110f; width *= 5f) {
|
||||
strokeWidths[nsw++] = width;
|
||||
}
|
||||
} else {
|
||||
nsw = 1;
|
||||
}
|
||||
|
||||
System.out.println("stroke widths: " + Arrays.toString(strokeWidths));
|
||||
|
||||
// Stroker tests:
|
||||
for (int w = 0; w < nsw; w++) {
|
||||
final float width = strokeWidths[w];
|
||||
|
||||
for (float[] dashes : dashArrays) {
|
||||
|
||||
for (int cap = 0; cap <= 2; cap++) {
|
||||
|
||||
for (int join = 0; join <= 2; join++) {
|
||||
|
||||
failures += paintPaths(new TestSetup(SHAPE_MODE, false, width, cap, join, dashes));
|
||||
failures += paintPaths(new TestSetup(SHAPE_MODE, true, width, cap, join, dashes));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (TEST_FILLER) {
|
||||
// Filler tests:
|
||||
failures += paintPaths(new TestSetup(SHAPE_MODE, false, Path2D.WIND_NON_ZERO));
|
||||
failures += paintPaths(new TestSetup(SHAPE_MODE, true, Path2D.WIND_NON_ZERO));
|
||||
|
||||
failures += paintPaths(new TestSetup(SHAPE_MODE, false, Path2D.WIND_EVEN_ODD));
|
||||
failures += paintPaths(new TestSetup(SHAPE_MODE, true, Path2D.WIND_EVEN_ODD));
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
}
|
||||
System.out.println("main: duration= " + (1e-6 * (System.nanoTime() - start)) + " ms.");
|
||||
|
||||
if (!isMarlin.get()) {
|
||||
throw new RuntimeException("Marlin renderer not used at runtime !");
|
||||
}
|
||||
if (!isClipRuntime.get()) {
|
||||
throw new RuntimeException("Marlin clipping not enabled at runtime !");
|
||||
}
|
||||
if (failures != 0) {
|
||||
throw new RuntimeException("Clip test failures : " + failures);
|
||||
}
|
||||
}
|
||||
|
||||
static int paintPaths(final TestSetup ts) throws IOException {
|
||||
final long start = System.nanoTime();
|
||||
|
||||
if (FIXED_SEED) {
|
||||
// Reset seed for random numbers:
|
||||
RANDOM.setSeed(SEED);
|
||||
}
|
||||
|
||||
System.out.println("paintPaths: " + NUM_TESTS
|
||||
+ " paths (" + SHAPE_MODE + ") - setup: " + ts);
|
||||
|
||||
final boolean fill = !ts.isStroke();
|
||||
final Path2D p2d = new Path2D.Double(ts.windingRule);
|
||||
|
||||
final BufferedImage imgOn = newImage(TESTW, TESTH);
|
||||
final Graphics2D g2dOn = initialize(imgOn, ts);
|
||||
|
||||
final BufferedImage imgOff = newImage(TESTW, TESTH);
|
||||
final Graphics2D g2dOff = initialize(imgOff, ts);
|
||||
|
||||
final BufferedImage imgDiff = newImage(TESTW, TESTH);
|
||||
|
||||
final DiffContext globalCtx = new DiffContext("All tests");
|
||||
|
||||
int nd = 0;
|
||||
try {
|
||||
final DiffContext testCtx = new DiffContext("Test");
|
||||
BufferedImage diffImage;
|
||||
|
||||
for (int n = 0; n < NUM_TESTS; n++) {
|
||||
genShape(p2d, ts);
|
||||
|
||||
// Runtime clip setting OFF:
|
||||
paintShape(p2d, g2dOff, fill, false);
|
||||
|
||||
// Runtime clip setting ON:
|
||||
paintShape(p2d, g2dOn, fill, true);
|
||||
|
||||
/* compute image difference if possible */
|
||||
diffImage = computeDiffImage(testCtx, imgOn, imgOff, imgDiff, globalCtx);
|
||||
|
||||
final String testName = "Setup_" + ts.id + "_test_" + n;
|
||||
|
||||
if (diffImage != null) {
|
||||
nd++;
|
||||
|
||||
final double ratio = (100.0 * testCtx.histPix.count) / testCtx.histAll.count;
|
||||
System.out.println("Diff ratio: " + testName + " = " + trimTo3Digits(ratio) + " %");
|
||||
|
||||
if (false) {
|
||||
saveImage(diffImage, OUTPUT_DIR, testName + "-diff.png");
|
||||
}
|
||||
|
||||
if (DUMP_SHAPE) {
|
||||
dumpShape(p2d);
|
||||
}
|
||||
if (nd < MAX_SHOW_FRAMES) {
|
||||
if (SHOW_DETAILS) {
|
||||
paintShapeDetails(g2dOff, p2d);
|
||||
paintShapeDetails(g2dOn, p2d);
|
||||
}
|
||||
|
||||
saveImage(imgOff, OUTPUT_DIR, testName + "-off.png");
|
||||
saveImage(imgOn, OUTPUT_DIR, testName + "-on.png");
|
||||
saveImage(diffImage, OUTPUT_DIR, testName + "-diff.png");
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
g2dOff.dispose();
|
||||
g2dOn.dispose();
|
||||
|
||||
if (nd != 0) {
|
||||
System.out.println("paintPaths: " + NUM_TESTS + " paths - "
|
||||
+ "Number of differences = " + nd
|
||||
+ " ratio = " + (100f * nd) / NUM_TESTS + " %");
|
||||
}
|
||||
|
||||
globalCtx.dump();
|
||||
}
|
||||
System.out.println("paintPaths: duration= " + (1e-6 * (System.nanoTime() - start)) + " ms.");
|
||||
return nd;
|
||||
}
|
||||
|
||||
private static void paintShape(final Path2D p2d, final Graphics2D g2d,
|
||||
final boolean fill, final boolean clip) {
|
||||
reset(g2d);
|
||||
|
||||
setClip(g2d, clip);
|
||||
|
||||
if (fill) {
|
||||
g2d.fill(p2d);
|
||||
} else {
|
||||
g2d.draw(p2d);
|
||||
}
|
||||
}
|
||||
|
||||
private static Graphics2D initialize(final BufferedImage img,
|
||||
final TestSetup ts) {
|
||||
final Graphics2D g2d = (Graphics2D) img.getGraphics();
|
||||
g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
|
||||
RenderingHints.VALUE_RENDER_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
|
||||
RenderingHints.VALUE_STROKE_PURE);
|
||||
|
||||
if (ts.isStroke()) {
|
||||
g2d.setStroke(createStroke(ts));
|
||||
}
|
||||
g2d.setColor(Color.GRAY);
|
||||
|
||||
return g2d;
|
||||
}
|
||||
|
||||
private static void reset(final Graphics2D g2d) {
|
||||
// Disable antialiasing:
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
|
||||
RenderingHints.VALUE_ANTIALIAS_OFF);
|
||||
g2d.setBackground(Color.WHITE);
|
||||
g2d.clearRect(0, 0, TESTW, TESTH);
|
||||
}
|
||||
|
||||
private static void setClip(final Graphics2D g2d, final boolean clip) {
|
||||
// Enable antialiasing:
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
|
||||
RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
// Enable or Disable clipping:
|
||||
System.setProperty("sun.java2d.renderer.clip.runtime", (clip) ? "true" : "false");
|
||||
}
|
||||
|
||||
static void genShape(final Path2D p2d, final TestSetup ts) {
|
||||
p2d.reset();
|
||||
|
||||
final int end = (SHAPE_REPEAT) ? 2 : 1;
|
||||
|
||||
for (int p = 0; p < end; p++) {
|
||||
p2d.moveTo(randX(), randY());
|
||||
|
||||
switch (ts.shapeMode) {
|
||||
case MIXED:
|
||||
case FIFTY_LINE_POLYS:
|
||||
case NINE_LINE_POLYS:
|
||||
case FIVE_LINE_POLYS:
|
||||
p2d.lineTo(randX(), randY());
|
||||
p2d.lineTo(randX(), randY());
|
||||
p2d.lineTo(randX(), randY());
|
||||
p2d.lineTo(randX(), randY());
|
||||
if (ts.shapeMode == ShapeMode.FIVE_LINE_POLYS) {
|
||||
// And an implicit close makes 5 lines
|
||||
break;
|
||||
}
|
||||
p2d.lineTo(randX(), randY());
|
||||
p2d.lineTo(randX(), randY());
|
||||
p2d.lineTo(randX(), randY());
|
||||
p2d.lineTo(randX(), randY());
|
||||
if (ts.shapeMode == ShapeMode.NINE_LINE_POLYS) {
|
||||
// And an implicit close makes 9 lines
|
||||
break;
|
||||
}
|
||||
if (ts.shapeMode == ShapeMode.FIFTY_LINE_POLYS) {
|
||||
for (int i = 0; i < 41; i++) {
|
||||
p2d.lineTo(randX(), randY());
|
||||
}
|
||||
// And an implicit close makes 50 lines
|
||||
break;
|
||||
}
|
||||
case TWO_CUBICS:
|
||||
p2d.curveTo(randX(), randY(), randX(), randY(), randX(), randY());
|
||||
p2d.curveTo(randX(), randY(), randX(), randY(), randX(), randY());
|
||||
if (ts.shapeMode == ShapeMode.TWO_CUBICS) {
|
||||
break;
|
||||
}
|
||||
case FOUR_QUADS:
|
||||
p2d.quadTo(randX(), randY(), randX(), randY());
|
||||
p2d.quadTo(randX(), randY(), randX(), randY());
|
||||
p2d.quadTo(randX(), randY(), randX(), randY());
|
||||
p2d.quadTo(randX(), randY(), randX(), randY());
|
||||
if (ts.shapeMode == ShapeMode.FOUR_QUADS) {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
if (ts.closed) {
|
||||
p2d.closePath();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static final float POINT_RADIUS = 2f;
|
||||
static final float LINE_WIDTH = 1f;
|
||||
|
||||
static final Stroke OUTLINE_STROKE = new BasicStroke(LINE_WIDTH);
|
||||
static final int COLOR_ALPHA = 128;
|
||||
static final Color COLOR_MOVETO = new Color(255, 0, 0, COLOR_ALPHA);
|
||||
static final Color COLOR_LINETO_ODD = new Color(0, 0, 255, COLOR_ALPHA);
|
||||
static final Color COLOR_LINETO_EVEN = new Color(0, 255, 0, COLOR_ALPHA);
|
||||
|
||||
static final Ellipse2D.Float ELL_POINT = new Ellipse2D.Float();
|
||||
|
||||
private static void paintShapeDetails(final Graphics2D g2d, final Shape shape) {
|
||||
|
||||
final Stroke oldStroke = g2d.getStroke();
|
||||
final Color oldColor = g2d.getColor();
|
||||
|
||||
setClip(g2d, false);
|
||||
|
||||
if (SHOW_OUTLINE) {
|
||||
g2d.setStroke(OUTLINE_STROKE);
|
||||
g2d.setColor(COLOR_LINETO_ODD);
|
||||
g2d.draw(shape);
|
||||
}
|
||||
|
||||
final float[] coords = new float[6];
|
||||
float px, py;
|
||||
|
||||
int nMove = 0;
|
||||
int nLine = 0;
|
||||
int n = 0;
|
||||
|
||||
for (final PathIterator it = shape.getPathIterator(null); !it.isDone(); it.next()) {
|
||||
int type = it.currentSegment(coords);
|
||||
switch (type) {
|
||||
case PathIterator.SEG_MOVETO:
|
||||
if (SHOW_POINTS) {
|
||||
g2d.setColor(COLOR_MOVETO);
|
||||
}
|
||||
break;
|
||||
case PathIterator.SEG_LINETO:
|
||||
if (SHOW_POINTS) {
|
||||
g2d.setColor((nLine % 2 == 0) ? COLOR_LINETO_ODD : COLOR_LINETO_EVEN);
|
||||
}
|
||||
nLine++;
|
||||
break;
|
||||
case PathIterator.SEG_CLOSE:
|
||||
continue;
|
||||
default:
|
||||
System.out.println("unsupported segment type= " + type);
|
||||
continue;
|
||||
}
|
||||
px = coords[0];
|
||||
py = coords[1];
|
||||
|
||||
if (SHOW_INFO) {
|
||||
System.out.println("point[" + (n++) + "|seg=" + type + "]: " + px + " " + py);
|
||||
}
|
||||
|
||||
if (SHOW_POINTS) {
|
||||
ELL_POINT.setFrame(px - POINT_RADIUS, py - POINT_RADIUS,
|
||||
POINT_RADIUS * 2f, POINT_RADIUS * 2f);
|
||||
g2d.fill(ELL_POINT);
|
||||
}
|
||||
}
|
||||
if (SHOW_INFO) {
|
||||
System.out.println("Path moveTo=" + nMove + ", lineTo=" + nLine);
|
||||
System.out.println("--------------------------------------------------");
|
||||
}
|
||||
|
||||
g2d.setStroke(oldStroke);
|
||||
g2d.setColor(oldColor);
|
||||
}
|
||||
|
||||
private static void dumpShape(final Shape shape) {
|
||||
final float[] coords = new float[6];
|
||||
|
||||
for (final PathIterator it = shape.getPathIterator(null); !it.isDone(); it.next()) {
|
||||
final int type = it.currentSegment(coords);
|
||||
switch (type) {
|
||||
case PathIterator.SEG_MOVETO:
|
||||
System.out.println("p2d.moveTo(" + coords[0] + ", " + coords[1] + ");");
|
||||
break;
|
||||
case PathIterator.SEG_LINETO:
|
||||
System.out.println("p2d.lineTo(" + coords[0] + ", " + coords[1] + ");");
|
||||
break;
|
||||
case PathIterator.SEG_CLOSE:
|
||||
System.out.println("p2d.closePath();");
|
||||
break;
|
||||
default:
|
||||
System.out.println("// Unsupported segment type= " + type);
|
||||
}
|
||||
}
|
||||
System.out.println("--------------------------------------------------");
|
||||
}
|
||||
|
||||
static double randX() {
|
||||
return RANDOM.nextDouble() * RANDW + OFFW;
|
||||
}
|
||||
|
||||
static double randY() {
|
||||
return RANDOM.nextDouble() * RANDH + OFFH;
|
||||
}
|
||||
|
||||
private static BasicStroke createStroke(final TestSetup ts) {
|
||||
return new BasicStroke(ts.strokeWidth, ts.strokeCap, ts.strokeJoin, 10.0f, ts.dashes, 0.0f);
|
||||
}
|
||||
|
||||
private final static class TestSetup {
|
||||
|
||||
static final AtomicInteger COUNT = new AtomicInteger();
|
||||
|
||||
final int id;
|
||||
final ShapeMode shapeMode;
|
||||
final boolean closed;
|
||||
// stroke
|
||||
final float strokeWidth;
|
||||
final int strokeCap;
|
||||
final int strokeJoin;
|
||||
final float[] dashes;
|
||||
// fill
|
||||
final int windingRule;
|
||||
|
||||
TestSetup(ShapeMode shapeMode, final boolean closed,
|
||||
final float strokeWidth, final int strokeCap, final int strokeJoin, final float[] dashes) {
|
||||
this.id = COUNT.incrementAndGet();
|
||||
this.shapeMode = shapeMode;
|
||||
this.closed = closed;
|
||||
this.strokeWidth = strokeWidth;
|
||||
this.strokeCap = strokeCap;
|
||||
this.strokeJoin = strokeJoin;
|
||||
this.dashes = dashes;
|
||||
this.windingRule = Path2D.WIND_NON_ZERO;
|
||||
}
|
||||
|
||||
TestSetup(ShapeMode shapeMode, final boolean closed, final int windingRule) {
|
||||
this.id = COUNT.incrementAndGet();
|
||||
this.shapeMode = shapeMode;
|
||||
this.closed = closed;
|
||||
this.strokeWidth = 0f;
|
||||
this.strokeCap = this.strokeJoin = -1; // invalid
|
||||
this.dashes = null;
|
||||
this.windingRule = windingRule;
|
||||
}
|
||||
|
||||
boolean isStroke() {
|
||||
return this.strokeWidth > 0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TestSetup{id=" + id + ", shapeMode=" + shapeMode + ", closed=" + closed
|
||||
+ ", strokeWidth=" + strokeWidth + ", strokeCap=" + strokeCap + ", strokeJoin=" + strokeJoin
|
||||
+ ((dashes != null) ? ", dashes: " + Arrays.toString(dashes) : "")
|
||||
+ ", windingRule=" + windingRule + '}';
|
||||
}
|
||||
}
|
||||
|
||||
// --- utilities ---
|
||||
private static final int DCM_ALPHA_MASK = 0xff000000;
|
||||
|
||||
public static BufferedImage newImage(final int w, final int h) {
|
||||
return new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB_PRE);
|
||||
}
|
||||
|
||||
public static BufferedImage computeDiffImage(final DiffContext localCtx,
|
||||
final BufferedImage tstImage,
|
||||
final BufferedImage refImage,
|
||||
final BufferedImage diffImage,
|
||||
final DiffContext globalCtx) {
|
||||
|
||||
final int[] aRefPix = ((DataBufferInt) refImage.getRaster().getDataBuffer()).getData();
|
||||
final int[] aTstPix = ((DataBufferInt) tstImage.getRaster().getDataBuffer()).getData();
|
||||
final int[] aDifPix = ((DataBufferInt) diffImage.getRaster().getDataBuffer()).getData();
|
||||
|
||||
// reset local diff context:
|
||||
localCtx.reset();
|
||||
|
||||
int ref, tst, dg, v;
|
||||
for (int i = 0, len = aRefPix.length; i < len; i++) {
|
||||
ref = aRefPix[i];
|
||||
tst = aTstPix[i];
|
||||
|
||||
// grayscale diff:
|
||||
dg = (r(ref) + g(ref) + b(ref)) - (r(tst) + g(tst) + b(tst));
|
||||
|
||||
// max difference on grayscale values:
|
||||
v = (int) Math.ceil(Math.abs(dg / 3.0));
|
||||
|
||||
aDifPix[i] = toInt(v, v, v);
|
||||
|
||||
localCtx.add(v);
|
||||
globalCtx.add(v);
|
||||
}
|
||||
|
||||
if (!localCtx.isDiff()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return diffImage;
|
||||
}
|
||||
|
||||
static void saveImage(final BufferedImage image, final File resDirectory, final String imageFileName) throws IOException {
|
||||
final Iterator<ImageWriter> itWriters = ImageIO.getImageWritersByFormatName("PNG");
|
||||
if (itWriters.hasNext()) {
|
||||
final ImageWriter writer = itWriters.next();
|
||||
|
||||
final ImageWriteParam writerParams = writer.getDefaultWriteParam();
|
||||
writerParams.setProgressiveMode(ImageWriteParam.MODE_DISABLED);
|
||||
|
||||
final File imgFile = new File(resDirectory, imageFileName);
|
||||
|
||||
if (!imgFile.exists() || imgFile.canWrite()) {
|
||||
System.out.println("saveImage: saving image as PNG [" + imgFile + "]...");
|
||||
imgFile.delete();
|
||||
|
||||
// disable cache in temporary files:
|
||||
ImageIO.setUseCache(false);
|
||||
|
||||
final long start = System.nanoTime();
|
||||
|
||||
// PNG uses already buffering:
|
||||
final ImageOutputStream imgOutStream = ImageIO.createImageOutputStream(new FileOutputStream(imgFile));
|
||||
|
||||
writer.setOutput(imgOutStream);
|
||||
try {
|
||||
writer.write(null, new IIOImage(image, null, null), writerParams);
|
||||
} finally {
|
||||
imgOutStream.close();
|
||||
|
||||
final long time = System.nanoTime() - start;
|
||||
System.out.println("saveImage: duration= " + (time / 1000000l) + " ms.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int r(final int v) {
|
||||
return (v >> 16 & 0xff);
|
||||
}
|
||||
|
||||
static int g(final int v) {
|
||||
return (v >> 8 & 0xff);
|
||||
}
|
||||
|
||||
static int b(final int v) {
|
||||
return (v & 0xff);
|
||||
}
|
||||
|
||||
static int clamp127(final int v) {
|
||||
return (v < 128) ? (v > -127 ? (v + 127) : 0) : 255;
|
||||
}
|
||||
|
||||
static int toInt(final int r, final int g, final int b) {
|
||||
return DCM_ALPHA_MASK | (r << 16) | (g << 8) | b;
|
||||
}
|
||||
|
||||
/* stats */
|
||||
static class StatInteger {
|
||||
|
||||
public final String name;
|
||||
public long count = 0l;
|
||||
public long sum = 0l;
|
||||
public long min = Integer.MAX_VALUE;
|
||||
public long max = Integer.MIN_VALUE;
|
||||
|
||||
StatInteger(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
count = 0l;
|
||||
sum = 0l;
|
||||
min = Integer.MAX_VALUE;
|
||||
max = Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
void add(int val) {
|
||||
count++;
|
||||
sum += val;
|
||||
if (val < min) {
|
||||
min = val;
|
||||
}
|
||||
if (val > max) {
|
||||
max = val;
|
||||
}
|
||||
}
|
||||
|
||||
void add(long val) {
|
||||
count++;
|
||||
sum += val;
|
||||
if (val < min) {
|
||||
min = val;
|
||||
}
|
||||
if (val > max) {
|
||||
max = val;
|
||||
}
|
||||
}
|
||||
|
||||
public final double average() {
|
||||
return ((double) sum) / count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder(128);
|
||||
toString(sb);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public final StringBuilder toString(final StringBuilder sb) {
|
||||
sb.append(name).append("[n: ").append(count);
|
||||
sb.append("] sum: ").append(sum).append(" avg: ").append(trimTo3Digits(average()));
|
||||
sb.append(" [").append(min).append(" | ").append(max).append("]");
|
||||
return sb;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
final static class Histogram extends StatInteger {
|
||||
|
||||
static final int BUCKET = 2;
|
||||
static final int MAX = 20;
|
||||
static final int LAST = MAX - 1;
|
||||
static final int[] STEPS = new int[MAX];
|
||||
|
||||
static {
|
||||
STEPS[0] = 0;
|
||||
STEPS[1] = 1;
|
||||
|
||||
for (int i = 2; i < MAX; i++) {
|
||||
STEPS[i] = STEPS[i - 1] * BUCKET;
|
||||
}
|
||||
// System.out.println("Histogram.STEPS = " + Arrays.toString(STEPS));
|
||||
}
|
||||
|
||||
static int bucket(int val) {
|
||||
for (int i = 1; i < MAX; i++) {
|
||||
if (val < STEPS[i]) {
|
||||
return i - 1;
|
||||
}
|
||||
}
|
||||
return LAST;
|
||||
}
|
||||
|
||||
private final StatInteger[] stats = new StatInteger[MAX];
|
||||
|
||||
public Histogram(String name) {
|
||||
super(name);
|
||||
for (int i = 0; i < MAX; i++) {
|
||||
stats[i] = new StatInteger(String.format("%5s .. %5s", STEPS[i], ((i + 1 < MAX) ? STEPS[i + 1] : "~")));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
final void reset() {
|
||||
super.reset();
|
||||
for (int i = 0; i < MAX; i++) {
|
||||
stats[i].reset();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
final void add(int val) {
|
||||
super.add(val);
|
||||
stats[bucket(val)].add(val);
|
||||
}
|
||||
|
||||
@Override
|
||||
final void add(long val) {
|
||||
add((int) val);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
final StringBuilder sb = new StringBuilder(2048);
|
||||
super.toString(sb).append(" { ");
|
||||
|
||||
for (int i = 0; i < MAX; i++) {
|
||||
if (stats[i].count != 0l) {
|
||||
sb.append("\n ").append(stats[i].toString());
|
||||
}
|
||||
}
|
||||
|
||||
return sb.append(" }").toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the given double value to keep only 3 decimal digits
|
||||
* @param value value to adjust
|
||||
* @return double value with only 3 decimal digits
|
||||
*/
|
||||
static double trimTo3Digits(final double value) {
|
||||
return ((long) (1e3d * value)) / 1e3d;
|
||||
}
|
||||
|
||||
static final class DiffContext {
|
||||
|
||||
public final Histogram histAll;
|
||||
public final Histogram histPix;
|
||||
|
||||
DiffContext(String name) {
|
||||
histAll = new Histogram("All Pixels [" + name + "]");
|
||||
histPix = new Histogram("Diff Pixels [" + name + "]");
|
||||
}
|
||||
|
||||
void reset() {
|
||||
histAll.reset();
|
||||
histPix.reset();
|
||||
}
|
||||
|
||||
void dump() {
|
||||
if (isDiff()) {
|
||||
System.out.println("Differences [" + histAll.name + "]:");
|
||||
System.out.println("Total [all pixels]:\n" + histAll.toString());
|
||||
System.out.println("Total [different pixels]:\n" + histPix.toString());
|
||||
} else {
|
||||
System.out.println("No difference for [" + histAll.name + "].");
|
||||
}
|
||||
}
|
||||
|
||||
void add(int val) {
|
||||
histAll.add(val);
|
||||
if (val != 0) {
|
||||
histPix.add(val);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isDiff() {
|
||||
return histAll.sum != 0l;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user