8191814: Marlin rasterizer spends time computing geometry for stroked segments that do not intersect the clip
Summary: upgrade to Marlin 0.8.2 providing efficient path clipping (Stroker and Filler)
Reviewed-by: prr, serb
--- a/src/java.desktop/share/classes/sun/java2d/marlin/Curve.java Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/Curve.java Mon Dec 11 21:14:43 2017 +0100
@@ -56,12 +56,16 @@
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 @@
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;
--- a/src/java.desktop/share/classes/sun/java2d/marlin/DCurve.java Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/DCurve.java Mon Dec 11 21:14:43 2017 +0100
@@ -56,12 +56,16 @@
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 @@
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;
--- a/src/java.desktop/share/classes/sun/java2d/marlin/DDasher.java Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/DDasher.java Mon Dec 11 21:14:43 2017 +0100
@@ -137,7 +137,7 @@
dashOn = !dashOn;
}
}
- } else if (phase > 0) {
+ } else if (phase > 0.0d) {
if (cycles >= MAX_CYCLES) {
phase = 0.0d;
} else {
@@ -157,12 +157,13 @@
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 @@
}
@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 @@
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 @@
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 @@
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) {
+ needsMoveTo = false;
out.moveTo(x0, y0);
- needsMoveTo = false;
}
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 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 @@
// 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 @@
// 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 @@
// 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 @@
}
@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 @@
}
@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 @@
@Override
public void closePath() {
lineTo(sx, sy);
- if (firstSegidx > 0) {
+ if (firstSegidx != 0) {
if (!dashOn || needsMoveTo) {
out.moveTo(sx, sy);
}
@@ -728,7 +774,7 @@
@Override
public void pathDone() {
- if (firstSegidx > 0) {
+ if (firstSegidx != 0) {
out.moveTo(sx, sy);
emitFirstSegments();
}
--- a/src/java.desktop/share/classes/sun/java2d/marlin/DHelpers.java Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/DHelpers.java Mon Dec 11 21:14:43 2017 +0100
@@ -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 @@
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 @@
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 @@
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;
+ }
+ }
}
--- a/src/java.desktop/share/classes/sun/java2d/marlin/DMarlinRenderingEngine.java Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/DMarlinRenderingEngine.java Mon Dec 11 21:14:43 2017 +0100
@@ -84,6 +84,13 @@
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 @@
miterlimit,
dashes,
dashphase,
- rdrCtx.transformerPC2D.wrapPath2d(p2d)
+ rdrCtx.transformerPC2D.wrapPath2D(p2d)
);
// Use Path2D copy constructor (trim)
@@ -195,14 +202,14 @@
}
}
- 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 @@
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 @@
int dashLen = -1;
boolean recycleDashes = false;
+ double scale = 1.0d;
double[] dashesD = null;
// Ensure converting dashes to double precision:
@@ -364,7 +372,7 @@
// 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 @@
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 @@
}
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 @@
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 @@
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 @@
}
@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 @@
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 @@
}
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 @@
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);
--- a/src/java.desktop/share/classes/sun/java2d/marlin/DRenderer.java Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/DRenderer.java Mon Dec 11 21:14:43 2017 +0100
@@ -46,6 +46,9 @@
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 @@
// 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 @@
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 @@
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 @@
}
@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 @@
}
@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 @@
}
@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 @@
@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
--- a/src/java.desktop/share/classes/sun/java2d/marlin/DRendererContext.java Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/DRendererContext.java Mon Dec 11 21:14:43 2017 +0100
@@ -75,16 +75,22 @@
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 @@
nPQPathIterator = new NormalizingPathIterator.NearestPixelQuarter(double6);
// MarlinRenderingEngine.TransformingPathConsumer2D
- transformerPC2D = new DTransformingPathConsumer2D();
+ transformerPC2D = new DTransformingPathConsumer2D(this);
// Renderer:
cache = new MarlinCache(this);
@@ -141,7 +147,10 @@
}
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 @@
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);
--- a/src/java.desktop/share/classes/sun/java2d/marlin/DStroker.java Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/DStroker.java Mon Dec 11 21:14:43 2017 +0100
@@ -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 @@
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 @@
// 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 @@
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 @@
* <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 @@
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 @@
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 @@
}
@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;
}
- 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;
+ }
+
+ 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;
+ }
}
@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 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 @@
emitLineToRev(cx0 - mx, cy0 - my);
emitLineToRev( x1 - mx, y1 - my);
- this.cmx = mx;
- this.cmy = my;
+ this.prev = DRAWING_OP_TO;
+ this.cx0 = x1;
+ this.cy0 = y1;
this.cdx = dx;
this.cdy = dy;
- this.cx0 = x1;
- this.cy0 = y1;
- this.prev = DRAWING_OP_TO;
+ 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);
- drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy);
+ emitLineTo(sx0 + smx, sy0 + smy);
- emitLineTo(sx0 + smx, sy0 + smy);
-
- emitMoveTo(sx0 - smx, sy0 - smy);
+ if (opened) {
+ emitLineTo(sx0 - smx, sy0 - smy);
+ } else {
+ 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 @@
@Override
public void pathDone() {
if (prev == DRAWING_OP_TO) {
- finish();
+ finish(cOutCode);
}
out.pathDone();
@@ -532,23 +631,39 @@
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();
- emitReverse();
+ if (!capStart) {
+ capStart = true;
- 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);
+ 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();
}
-
emitClose();
}
@@ -620,23 +735,28 @@
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 @@
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 @@
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 @@
}
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 @@
}
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 @@
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.prev = DRAWING_OP_TO;
+ this.cx0 = xf;
+ this.cy0 = yf;
this.cdx = dxf;
this.cdy = dyf;
- this.cx0 = xf;
- this.cy0 = yf;
- this.prev = DRAWING_OP_TO;
+ 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 @@
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 @@
}
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 @@
}
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 @@
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.prev = DRAWING_OP_TO;
+ this.cx0 = xf;
+ this.cy0 = yf;
this.cdx = dxf;
this.cdy = dyf;
- this.cx0 = xf;
- this.cy0 = yf;
- this.prev = DRAWING_OP_TO;
+ 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;
- }
- }
}
--- a/src/java.desktop/share/classes/sun/java2d/marlin/DTransformingPathConsumer2D.java Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/DTransformingPathConsumer2D.java Mon Dec 11 21:14:43 2017 +0100
@@ -27,50 +27,169 @@
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 ClosedPathDetector instance from detectClosedPath()
+ private final ClosedPathDetector cpDetector;
- // recycled DPathConsumer2D instance from wrapPath2d()
+ // 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 @@
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 @@
}
}
-
static final class DeltaScaleFilter implements DPathConsumer2D {
private DPathConsumer2D out;
private double sx, sy;
@@ -274,4 +392,427 @@
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");
+ }
+ }
}
--- a/src/java.desktop/share/classes/sun/java2d/marlin/Dasher.java Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/Dasher.java Mon Dec 11 21:14:43 2017 +0100
@@ -138,7 +138,7 @@
dashOn = !dashOn;
}
}
- } else if (phase > 0) {
+ } else if (phase > 0.0f) {
if (cycles >= MAX_CYCLES) {
phase = 0.0f;
} else {
@@ -158,12 +158,13 @@
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 @@
}
@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 @@
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 @@
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 @@
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) {
+ needsMoveTo = false;
out.moveTo(x0, y0);
- needsMoveTo = false;
}
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 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 @@
// 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 @@
// 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 @@
// 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 @@
}
@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 @@
}
@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 @@
@Override
public void closePath() {
lineTo(sx, sy);
- if (firstSegidx > 0) {
+ if (firstSegidx != 0) {
if (!dashOn || needsMoveTo) {
out.moveTo(sx, sy);
}
@@ -729,7 +775,7 @@
@Override
public void pathDone() {
- if (firstSegidx > 0) {
+ if (firstSegidx != 0) {
out.moveTo(sx, sy);
emitFirstSegments();
}
--- a/src/java.desktop/share/classes/sun/java2d/marlin/Helpers.java Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/Helpers.java Mon Dec 11 21:14:43 2017 +0100
@@ -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 @@
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 @@
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 @@
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;
+ }
+ }
}
--- a/src/java.desktop/share/classes/sun/java2d/marlin/MarlinCache.java Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/MarlinCache.java Mon Dec 11 21:14:43 2017 +0100
@@ -139,11 +139,7 @@
// 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;
--- a/src/java.desktop/share/classes/sun/java2d/marlin/MarlinConst.java Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/MarlinConst.java Mon Dec 11 21:14:43 2017 +0100
@@ -128,4 +128,47 @@
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;
}
--- a/src/java.desktop/share/classes/sun/java2d/marlin/MarlinProperties.java Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/MarlinProperties.java Mon Dec 11 21:14:43 2017 +0100
@@ -145,6 +145,18 @@
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() {
--- a/src/java.desktop/share/classes/sun/java2d/marlin/MarlinRenderingEngine.java Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/MarlinRenderingEngine.java Mon Dec 11 21:14:43 2017 +0100
@@ -85,6 +85,13 @@
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 @@
miterlimit,
dashes,
dashphase,
- rdrCtx.transformerPC2D.wrapPath2d(p2d)
+ rdrCtx.transformerPC2D.wrapPath2D(p2d)
);
// Use Path2D copy constructor (trim)
@@ -195,14 +202,14 @@
}
}
- 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 @@
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 @@
int dashLen = -1;
boolean recycleDashes = false;
+ float scale = 1.0f;
if (at != null && !at.isIdentity()) {
final double a = at.getScaleX();
@@ -356,7 +364,7 @@
// 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 @@
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 @@
}
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 @@
}
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 @@
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 @@
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 @@
}
@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 @@
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 @@
}
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 @@
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);
--- a/src/java.desktop/share/classes/sun/java2d/marlin/Renderer.java Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/Renderer.java Mon Dec 11 21:14:43 2017 +0100
@@ -47,6 +47,9 @@
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 @@
// 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 @@
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 @@
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 @@
}
@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 @@
}
@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 @@
}
@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 @@
@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
--- a/src/java.desktop/share/classes/sun/java2d/marlin/RendererContext.java Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/RendererContext.java Mon Dec 11 21:14:43 2017 +0100
@@ -75,16 +75,22 @@
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 @@
nPQPathIterator = new NormalizingPathIterator.NearestPixelQuarter(float6);
// MarlinRenderingEngine.TransformingPathConsumer2D
- transformerPC2D = new TransformingPathConsumer2D();
+ transformerPC2D = new TransformingPathConsumer2D(this);
// Renderer:
cache = new MarlinCache(this);
@@ -138,7 +144,10 @@
}
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 @@
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);
--- a/src/java.desktop/share/classes/sun/java2d/marlin/RendererStats.java Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/RendererStats.java Mon Dec 11 21:14:43 2017 +0100
@@ -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 @@
= 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 @@
= 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 @@
= 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 @@
= 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 @@
= 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 @@
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 @@
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 @@
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
--- a/src/java.desktop/share/classes/sun/java2d/marlin/Stroker.java Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/Stroker.java Mon Dec 11 21:14:43 2017 +0100
@@ -28,6 +28,7 @@
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 @@
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 @@
// 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 @@
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 @@
* <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 @@
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 @@
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 @@
}
@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;
}
- 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;
+ }
+
+ 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;
+ }
}
@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 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 @@
emitLineToRev(cx0 - mx, cy0 - my);
emitLineToRev( x1 - mx, y1 - my);
- this.cmx = mx;
- this.cmy = my;
+ this.prev = DRAWING_OP_TO;
+ this.cx0 = x1;
+ this.cy0 = y1;
this.cdx = dx;
this.cdy = dy;
- this.cx0 = x1;
- this.cy0 = y1;
- this.prev = DRAWING_OP_TO;
+ 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);
- drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy);
+ emitLineTo(sx0 + smx, sy0 + smy);
- emitLineTo(sx0 + smx, sy0 + smy);
-
- emitMoveTo(sx0 - smx, sy0 - smy);
+ if (opened) {
+ emitLineTo(sx0 - smx, sy0 - smy);
+ } else {
+ 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 @@
@Override
public void pathDone() {
if (prev == DRAWING_OP_TO) {
- finish();
+ finish(cOutCode);
}
out.pathDone();
@@ -534,23 +633,39 @@
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();
- emitReverse();
+ if (!capStart) {
+ capStart = true;
- 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);
+ 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();
}
-
emitClose();
}
@@ -622,23 +737,28 @@
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 @@
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 @@
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 @@
}
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 @@
}
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 @@
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.prev = DRAWING_OP_TO;
+ this.cx0 = xf;
+ this.cy0 = yf;
this.cdx = dxf;
this.cdy = dyf;
- this.cx0 = xf;
- this.cy0 = yf;
- this.prev = DRAWING_OP_TO;
+ 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 @@
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 @@
}
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 @@
}
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 @@
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.prev = DRAWING_OP_TO;
+ this.cx0 = xf;
+ this.cy0 = yf;
this.cdx = dxf;
this.cdy = dyf;
- this.cx0 = xf;
- this.cy0 = yf;
- this.prev = DRAWING_OP_TO;
+ 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;
- }
- }
}
--- a/src/java.desktop/share/classes/sun/java2d/marlin/TransformingPathConsumer2D.java Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/TransformingPathConsumer2D.java Mon Dec 11 21:14:43 2017 +0100
@@ -28,50 +28,169 @@
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 ClosedPathDetector instance from detectClosedPath()
+ private final ClosedPathDetector cpDetector;
- // recycled PathConsumer2D instance from wrapPath2d()
+ // 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 @@
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 @@
}
}
-
static final class DeltaScaleFilter implements PathConsumer2D {
private PathConsumer2D out;
private float sx, sy;
@@ -275,4 +393,427 @@
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");
+ }
+ }
}
--- a/src/java.desktop/share/classes/sun/java2d/marlin/Version.java Mon Dec 11 10:08:51 2017 -0800
+++ b/src/java.desktop/share/classes/sun/java2d/marlin/Version.java Mon Dec 11 21:14:43 2017 +0100
@@ -27,7 +27,7 @@
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;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/sun/java2d/marlin/ClipShapeTest.java Mon Dec 11 21:14:43 2017 +0100
@@ -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;
+ }
+ }
+}