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:
Laurent Bourgès 2017-12-11 21:14:43 +01:00
parent ee9c644643
commit 5f530a331b
22 changed files with 4026 additions and 1071 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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