8228711: Path rendered incorrectly when it goes outside the clipping region
Fixed closePath() to preserve last position and its outcode in Stroker and TransformingPathConsumer2D.PathClipFilter Reviewed-by: prr, kcr
This commit is contained in:
parent
c4b6dfbad1
commit
383e7dfb30
@ -47,6 +47,8 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
|
||||
static final double CURVE_LEN_ERR = MarlinProperties.getCurveLengthError(); // 0.01 initial
|
||||
static final double MIN_T_INC = 1.0d / (1 << REC_LIMIT);
|
||||
|
||||
static final double EPS = 1e-6d;
|
||||
|
||||
// More than 24 bits of mantissa means we can no longer accurately
|
||||
// measure the number of times cycled through the dash array so we
|
||||
// punt and override the phase to just be 0 past that point.
|
||||
@ -269,6 +271,9 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
|
||||
|
||||
private void emitSeg(double[] buf, int off, int type) {
|
||||
switch (type) {
|
||||
case 4:
|
||||
out.lineTo(buf[off], buf[off + 1]);
|
||||
return;
|
||||
case 8:
|
||||
out.curveTo(buf[off ], buf[off + 1],
|
||||
buf[off + 2], buf[off + 3],
|
||||
@ -278,9 +283,6 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
|
||||
out.quadTo(buf[off ], buf[off + 1],
|
||||
buf[off + 2], buf[off + 3]);
|
||||
return;
|
||||
case 4:
|
||||
out.lineTo(buf[off], buf[off + 1]);
|
||||
return;
|
||||
default:
|
||||
}
|
||||
}
|
||||
@ -361,7 +363,7 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
|
||||
|
||||
// basic rejection criteria:
|
||||
if (sideCode == 0) {
|
||||
// ovelap clip:
|
||||
// overlap clip:
|
||||
if (subdivide) {
|
||||
// avoid reentrance
|
||||
subdivide = false;
|
||||
@ -416,13 +418,13 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
|
||||
boolean _dashOn = dashOn;
|
||||
double _phase = phase;
|
||||
|
||||
double leftInThisDashSegment, d;
|
||||
double leftInThisDashSegment, rem;
|
||||
|
||||
while (true) {
|
||||
d = _dash[_idx];
|
||||
leftInThisDashSegment = d - _phase;
|
||||
leftInThisDashSegment = _dash[_idx] - _phase;
|
||||
rem = len - leftInThisDashSegment;
|
||||
|
||||
if (len <= leftInThisDashSegment) {
|
||||
if (rem <= EPS) {
|
||||
_curCurvepts[0] = x1;
|
||||
_curCurvepts[1] = y1;
|
||||
|
||||
@ -431,8 +433,8 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
|
||||
// Advance phase within current dash segment
|
||||
_phase += len;
|
||||
|
||||
// TODO: compare double values using epsilon:
|
||||
if (len == leftInThisDashSegment) {
|
||||
// compare values using epsilon:
|
||||
if (Math.abs(rem) <= EPS) {
|
||||
_phase = 0.0d;
|
||||
_idx = (_idx + 1) % _dashLen;
|
||||
_dashOn = !_dashOn;
|
||||
@ -440,17 +442,12 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
|
||||
break;
|
||||
}
|
||||
|
||||
if (_phase == 0.0d) {
|
||||
_curCurvepts[0] = cx0 + d * cx;
|
||||
_curCurvepts[1] = cy0 + d * cy;
|
||||
} else {
|
||||
_curCurvepts[0] = cx0 + leftInThisDashSegment * cx;
|
||||
_curCurvepts[1] = cy0 + leftInThisDashSegment * cy;
|
||||
}
|
||||
_curCurvepts[0] = cx0 + leftInThisDashSegment * cx;
|
||||
_curCurvepts[1] = cy0 + leftInThisDashSegment * cy;
|
||||
|
||||
goTo(_curCurvepts, 0, 4, _dashOn);
|
||||
|
||||
len -= leftInThisDashSegment;
|
||||
len = rem;
|
||||
// Advance to next dash segment
|
||||
_idx = (_idx + 1) % _dashLen;
|
||||
_dashOn = !_dashOn;
|
||||
@ -506,18 +503,18 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
|
||||
_dashOn = (iterations + (_dashOn ? 1L : 0L) & 1L) == 1L;
|
||||
}
|
||||
|
||||
double leftInThisDashSegment, d;
|
||||
double leftInThisDashSegment, rem;
|
||||
|
||||
while (true) {
|
||||
d = _dash[_idx];
|
||||
leftInThisDashSegment = d - _phase;
|
||||
leftInThisDashSegment = _dash[_idx] - _phase;
|
||||
rem = len - leftInThisDashSegment;
|
||||
|
||||
if (len <= leftInThisDashSegment) {
|
||||
if (rem <= EPS) {
|
||||
// Advance phase within current dash segment
|
||||
_phase += len;
|
||||
|
||||
// TODO: compare double values using epsilon:
|
||||
if (len == leftInThisDashSegment) {
|
||||
// compare values using epsilon:
|
||||
if (Math.abs(rem) <= EPS) {
|
||||
_phase = 0.0d;
|
||||
_idx = (_idx + 1) % _dashLen;
|
||||
_dashOn = !_dashOn;
|
||||
@ -525,7 +522,7 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
|
||||
break;
|
||||
}
|
||||
|
||||
len -= leftInThisDashSegment;
|
||||
len = rem;
|
||||
// Advance to next dash segment
|
||||
_idx = (_idx + 1) % _dashLen;
|
||||
_dashOn = !_dashOn;
|
||||
@ -579,7 +576,9 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
|
||||
goTo(_curCurvepts, curCurveoff + 2, type, _dashOn);
|
||||
|
||||
_phase += _li.lastSegLen();
|
||||
if (_phase >= _dash[_idx]) {
|
||||
|
||||
// compare values using epsilon:
|
||||
if (_phase + EPS >= _dash[_idx]) {
|
||||
_phase = 0.0d;
|
||||
_idx = (_idx + 1) % _dashLen;
|
||||
_dashOn = !_dashOn;
|
||||
@ -938,7 +937,7 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
|
||||
|
||||
// basic rejection criteria:
|
||||
if (sideCode == 0) {
|
||||
// ovelap clip:
|
||||
// overlap clip:
|
||||
if (subdivide) {
|
||||
// avoid reentrance
|
||||
subdivide = false;
|
||||
@ -1024,7 +1023,7 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
|
||||
|
||||
// basic rejection criteria:
|
||||
if (sideCode == 0) {
|
||||
// ovelap clip:
|
||||
// overlap clip:
|
||||
if (subdivide) {
|
||||
// avoid reentrance
|
||||
subdivide = false;
|
||||
|
@ -243,7 +243,7 @@ final class DHelpers implements MarlinConst {
|
||||
final double y12 = pts[3] - pts[1];
|
||||
// if the curve is already parallel to either axis we gain nothing
|
||||
// from rotating it.
|
||||
if ((y12 != 0.0d && x12 != 0.0d)) {
|
||||
if ((y12 != 0.0d) && (x12 != 0.0d)) {
|
||||
// we rotate it so that the first vector in the control polygon is
|
||||
// parallel to the x-axis. This will ensure that rotated quarter
|
||||
// circles won't be subdivided.
|
||||
@ -764,17 +764,17 @@ final class DHelpers implements MarlinConst {
|
||||
io.lineTo(_curves[e], _curves[e+1]);
|
||||
e += 2;
|
||||
continue;
|
||||
case TYPE_QUADTO:
|
||||
io.quadTo(_curves[e], _curves[e+1],
|
||||
_curves[e+2], _curves[e+3]);
|
||||
e += 4;
|
||||
continue;
|
||||
case TYPE_CUBICTO:
|
||||
io.curveTo(_curves[e], _curves[e+1],
|
||||
_curves[e+2], _curves[e+3],
|
||||
_curves[e+4], _curves[e+5]);
|
||||
e += 6;
|
||||
continue;
|
||||
case TYPE_QUADTO:
|
||||
io.quadTo(_curves[e], _curves[e+1],
|
||||
_curves[e+2], _curves[e+3]);
|
||||
e += 4;
|
||||
continue;
|
||||
default:
|
||||
}
|
||||
}
|
||||
@ -806,17 +806,17 @@ final class DHelpers implements MarlinConst {
|
||||
e -= 2;
|
||||
io.lineTo(_curves[e], _curves[e+1]);
|
||||
continue;
|
||||
case TYPE_QUADTO:
|
||||
e -= 4;
|
||||
io.quadTo(_curves[e], _curves[e+1],
|
||||
_curves[e+2], _curves[e+3]);
|
||||
continue;
|
||||
case TYPE_CUBICTO:
|
||||
e -= 6;
|
||||
io.curveTo(_curves[e], _curves[e+1],
|
||||
_curves[e+2], _curves[e+3],
|
||||
_curves[e+4], _curves[e+5]);
|
||||
continue;
|
||||
case TYPE_QUADTO:
|
||||
e -= 4;
|
||||
io.quadTo(_curves[e], _curves[e+1],
|
||||
_curves[e+2], _curves[e+3]);
|
||||
continue;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
@ -540,7 +540,7 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
|
||||
|
||||
// basic rejection criteria:
|
||||
if (sideCode == 0) {
|
||||
// ovelap clip:
|
||||
// overlap clip:
|
||||
if (subdivide) {
|
||||
// avoid reentrance
|
||||
subdivide = false;
|
||||
@ -634,6 +634,9 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
|
||||
emitReverse();
|
||||
|
||||
this.prev = CLOSE;
|
||||
this.cx0 = sx0;
|
||||
this.cy0 = sy0;
|
||||
this.cOutCode = sOutCode;
|
||||
|
||||
if (opened) {
|
||||
// do not emit close
|
||||
@ -668,7 +671,9 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
|
||||
// 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 (rdrCtx.closedPath) {
|
||||
emitReverse();
|
||||
} else {
|
||||
if (outcode == 0) {
|
||||
// current point = end's cap:
|
||||
if (capStyle == CAP_ROUND) {
|
||||
@ -693,8 +698,6 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
emitReverse();
|
||||
}
|
||||
emitClose();
|
||||
}
|
||||
@ -1058,7 +1061,7 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
|
||||
|
||||
// basic rejection criteria:
|
||||
if (sideCode == 0) {
|
||||
// ovelap clip:
|
||||
// overlap clip:
|
||||
if (subdivide) {
|
||||
// avoid reentrance
|
||||
subdivide = false;
|
||||
@ -1206,7 +1209,7 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
|
||||
|
||||
// basic rejection criteria:
|
||||
if (sideCode == 0) {
|
||||
// ovelap clip:
|
||||
// overlap clip:
|
||||
if (subdivide) {
|
||||
// avoid reentrance
|
||||
subdivide = false;
|
||||
|
@ -530,6 +530,9 @@ final class DTransformingPathConsumer2D {
|
||||
|
||||
private boolean outside = false;
|
||||
|
||||
// The starting point of the path
|
||||
private double sx0, sy0;
|
||||
|
||||
// The current point (TODO stupid repeated info)
|
||||
private double cx0, cy0;
|
||||
|
||||
@ -630,17 +633,26 @@ final class DTransformingPathConsumer2D {
|
||||
finishPath();
|
||||
|
||||
out.closePath();
|
||||
|
||||
// back to starting point:
|
||||
this.cOutCode = DHelpers.outcode(sx0, sy0, clipRect);
|
||||
this.cx0 = sx0;
|
||||
this.cy0 = sy0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveTo(final double x0, final double y0) {
|
||||
finishPath();
|
||||
|
||||
this.cOutCode = DHelpers.outcode(x0, y0, clipRect);
|
||||
this.outside = false;
|
||||
out.moveTo(x0, y0);
|
||||
|
||||
// update starting point:
|
||||
this.cOutCode = DHelpers.outcode(x0, y0, clipRect);
|
||||
this.cx0 = x0;
|
||||
this.cy0 = y0;
|
||||
|
||||
this.sx0 = x0;
|
||||
this.sy0 = y0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -655,7 +667,7 @@ final class DTransformingPathConsumer2D {
|
||||
|
||||
// basic rejection criteria:
|
||||
if (sideCode == 0) {
|
||||
// ovelap clip:
|
||||
// overlap clip:
|
||||
if (subdivide) {
|
||||
// avoid reentrance
|
||||
subdivide = false;
|
||||
@ -754,7 +766,7 @@ final class DTransformingPathConsumer2D {
|
||||
|
||||
// basic rejection criteria:
|
||||
if (sideCode == 0) {
|
||||
// ovelap clip:
|
||||
// overlap clip:
|
||||
if (subdivide) {
|
||||
// avoid reentrance
|
||||
subdivide = false;
|
||||
@ -816,7 +828,7 @@ final class DTransformingPathConsumer2D {
|
||||
|
||||
// basic rejection criteria:
|
||||
if (sideCode == 0) {
|
||||
// ovelap clip:
|
||||
// overlap clip:
|
||||
if (subdivide) {
|
||||
// avoid reentrance
|
||||
subdivide = false;
|
||||
@ -1153,13 +1165,13 @@ final class DTransformingPathConsumer2D {
|
||||
|
||||
@Override
|
||||
public void moveTo(double x0, double y0) {
|
||||
log("moveTo (" + x0 + ", " + y0 + ')');
|
||||
log("p.moveTo(" + x0 + ", " + y0 + ");");
|
||||
out.moveTo(x0, y0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lineTo(double x1, double y1) {
|
||||
log("lineTo (" + x1 + ", " + y1 + ')');
|
||||
log("p.lineTo(" + x1 + ", " + y1 + ");");
|
||||
out.lineTo(x1, y1);
|
||||
}
|
||||
|
||||
@ -1168,25 +1180,26 @@ final class DTransformingPathConsumer2D {
|
||||
double x2, double y2,
|
||||
double x3, double y3)
|
||||
{
|
||||
log("curveTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ')');
|
||||
log("p.curveTo(" + x1 + ", " + y1 + ", " + x2 + ", " + y2 + ", " + 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 + ')');
|
||||
public void quadTo(double x1, double y1,
|
||||
double x2, double y2) {
|
||||
log("p.quadTo(" + x1 + ", " + y1 + ", " + x2 + ", " + y2 + ");");
|
||||
out.quadTo(x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closePath() {
|
||||
log("closePath");
|
||||
log("p.closePath();");
|
||||
out.closePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pathDone() {
|
||||
log("pathDone");
|
||||
log("p.pathDone();");
|
||||
out.pathDone();
|
||||
}
|
||||
|
||||
|
@ -48,6 +48,8 @@ final class Dasher implements PathConsumer2D, MarlinConst {
|
||||
static final float CURVE_LEN_ERR = MarlinProperties.getCurveLengthError(); // 0.01
|
||||
static final float MIN_T_INC = 1.0f / (1 << REC_LIMIT);
|
||||
|
||||
static final float EPS = 1e-6f;
|
||||
|
||||
// More than 24 bits of mantissa means we can no longer accurately
|
||||
// measure the number of times cycled through the dash array so we
|
||||
// punt and override the phase to just be 0 past that point.
|
||||
@ -270,6 +272,9 @@ final class Dasher implements PathConsumer2D, MarlinConst {
|
||||
|
||||
private void emitSeg(float[] buf, int off, int type) {
|
||||
switch (type) {
|
||||
case 4:
|
||||
out.lineTo(buf[off], buf[off + 1]);
|
||||
return;
|
||||
case 8:
|
||||
out.curveTo(buf[off ], buf[off + 1],
|
||||
buf[off + 2], buf[off + 3],
|
||||
@ -279,9 +284,6 @@ final class Dasher implements PathConsumer2D, MarlinConst {
|
||||
out.quadTo(buf[off ], buf[off + 1],
|
||||
buf[off + 2], buf[off + 3]);
|
||||
return;
|
||||
case 4:
|
||||
out.lineTo(buf[off], buf[off + 1]);
|
||||
return;
|
||||
default:
|
||||
}
|
||||
}
|
||||
@ -362,7 +364,7 @@ final class Dasher implements PathConsumer2D, MarlinConst {
|
||||
|
||||
// basic rejection criteria:
|
||||
if (sideCode == 0) {
|
||||
// ovelap clip:
|
||||
// overlap clip:
|
||||
if (subdivide) {
|
||||
// avoid reentrance
|
||||
subdivide = false;
|
||||
@ -417,13 +419,13 @@ final class Dasher implements PathConsumer2D, MarlinConst {
|
||||
boolean _dashOn = dashOn;
|
||||
float _phase = phase;
|
||||
|
||||
float leftInThisDashSegment, d;
|
||||
float leftInThisDashSegment, rem;
|
||||
|
||||
while (true) {
|
||||
d = _dash[_idx];
|
||||
leftInThisDashSegment = d - _phase;
|
||||
leftInThisDashSegment = _dash[_idx] - _phase;
|
||||
rem = len - leftInThisDashSegment;
|
||||
|
||||
if (len <= leftInThisDashSegment) {
|
||||
if (rem <= EPS) {
|
||||
_curCurvepts[0] = x1;
|
||||
_curCurvepts[1] = y1;
|
||||
|
||||
@ -432,8 +434,8 @@ final class Dasher implements PathConsumer2D, MarlinConst {
|
||||
// Advance phase within current dash segment
|
||||
_phase += len;
|
||||
|
||||
// TODO: compare float values using epsilon:
|
||||
if (len == leftInThisDashSegment) {
|
||||
// compare values using epsilon:
|
||||
if (Math.abs(rem) <= EPS) {
|
||||
_phase = 0.0f;
|
||||
_idx = (_idx + 1) % _dashLen;
|
||||
_dashOn = !_dashOn;
|
||||
@ -441,17 +443,12 @@ final class Dasher implements PathConsumer2D, MarlinConst {
|
||||
break;
|
||||
}
|
||||
|
||||
if (_phase == 0.0f) {
|
||||
_curCurvepts[0] = cx0 + d * cx;
|
||||
_curCurvepts[1] = cy0 + d * cy;
|
||||
} else {
|
||||
_curCurvepts[0] = cx0 + leftInThisDashSegment * cx;
|
||||
_curCurvepts[1] = cy0 + leftInThisDashSegment * cy;
|
||||
}
|
||||
_curCurvepts[0] = cx0 + leftInThisDashSegment * cx;
|
||||
_curCurvepts[1] = cy0 + leftInThisDashSegment * cy;
|
||||
|
||||
goTo(_curCurvepts, 0, 4, _dashOn);
|
||||
|
||||
len -= leftInThisDashSegment;
|
||||
len = rem;
|
||||
// Advance to next dash segment
|
||||
_idx = (_idx + 1) % _dashLen;
|
||||
_dashOn = !_dashOn;
|
||||
@ -507,18 +504,18 @@ final class Dasher implements PathConsumer2D, MarlinConst {
|
||||
_dashOn = (iterations + (_dashOn ? 1L : 0L) & 1L) == 1L;
|
||||
}
|
||||
|
||||
float leftInThisDashSegment, d;
|
||||
float leftInThisDashSegment, rem;
|
||||
|
||||
while (true) {
|
||||
d = _dash[_idx];
|
||||
leftInThisDashSegment = d - _phase;
|
||||
leftInThisDashSegment = _dash[_idx] - _phase;
|
||||
rem = len - leftInThisDashSegment;
|
||||
|
||||
if (len <= leftInThisDashSegment) {
|
||||
if (rem <= EPS) {
|
||||
// Advance phase within current dash segment
|
||||
_phase += len;
|
||||
|
||||
// TODO: compare float values using epsilon:
|
||||
if (len == leftInThisDashSegment) {
|
||||
// compare values using epsilon:
|
||||
if (Math.abs(rem) <= EPS) {
|
||||
_phase = 0.0f;
|
||||
_idx = (_idx + 1) % _dashLen;
|
||||
_dashOn = !_dashOn;
|
||||
@ -526,7 +523,7 @@ final class Dasher implements PathConsumer2D, MarlinConst {
|
||||
break;
|
||||
}
|
||||
|
||||
len -= leftInThisDashSegment;
|
||||
len = rem;
|
||||
// Advance to next dash segment
|
||||
_idx = (_idx + 1) % _dashLen;
|
||||
_dashOn = !_dashOn;
|
||||
@ -580,7 +577,9 @@ final class Dasher implements PathConsumer2D, MarlinConst {
|
||||
goTo(_curCurvepts, curCurveoff + 2, type, _dashOn);
|
||||
|
||||
_phase += _li.lastSegLen();
|
||||
if (_phase >= _dash[_idx]) {
|
||||
|
||||
// compare values using epsilon:
|
||||
if (_phase + EPS >= _dash[_idx]) {
|
||||
_phase = 0.0f;
|
||||
_idx = (_idx + 1) % _dashLen;
|
||||
_dashOn = !_dashOn;
|
||||
@ -939,7 +938,7 @@ final class Dasher implements PathConsumer2D, MarlinConst {
|
||||
|
||||
// basic rejection criteria:
|
||||
if (sideCode == 0) {
|
||||
// ovelap clip:
|
||||
// overlap clip:
|
||||
if (subdivide) {
|
||||
// avoid reentrance
|
||||
subdivide = false;
|
||||
@ -1025,7 +1024,7 @@ final class Dasher implements PathConsumer2D, MarlinConst {
|
||||
|
||||
// basic rejection criteria:
|
||||
if (sideCode == 0) {
|
||||
// ovelap clip:
|
||||
// overlap clip:
|
||||
if (subdivide) {
|
||||
// avoid reentrance
|
||||
subdivide = false;
|
||||
|
@ -251,7 +251,7 @@ final class Helpers implements MarlinConst {
|
||||
final float y12 = pts[3] - pts[1];
|
||||
// if the curve is already parallel to either axis we gain nothing
|
||||
// from rotating it.
|
||||
if ((y12 != 0.0f && x12 != 0.0f)) {
|
||||
if ((y12 != 0.0f) && (x12 != 0.0f)) {
|
||||
// we rotate it so that the first vector in the control polygon is
|
||||
// parallel to the x-axis. This will ensure that rotated quarter
|
||||
// circles won't be subdivided.
|
||||
@ -772,17 +772,17 @@ final class Helpers implements MarlinConst {
|
||||
io.lineTo(_curves[e], _curves[e+1]);
|
||||
e += 2;
|
||||
continue;
|
||||
case TYPE_QUADTO:
|
||||
io.quadTo(_curves[e], _curves[e+1],
|
||||
_curves[e+2], _curves[e+3]);
|
||||
e += 4;
|
||||
continue;
|
||||
case TYPE_CUBICTO:
|
||||
io.curveTo(_curves[e], _curves[e+1],
|
||||
_curves[e+2], _curves[e+3],
|
||||
_curves[e+4], _curves[e+5]);
|
||||
e += 6;
|
||||
continue;
|
||||
case TYPE_QUADTO:
|
||||
io.quadTo(_curves[e], _curves[e+1],
|
||||
_curves[e+2], _curves[e+3]);
|
||||
e += 4;
|
||||
continue;
|
||||
default:
|
||||
}
|
||||
}
|
||||
@ -814,17 +814,17 @@ final class Helpers implements MarlinConst {
|
||||
e -= 2;
|
||||
io.lineTo(_curves[e], _curves[e+1]);
|
||||
continue;
|
||||
case TYPE_QUADTO:
|
||||
e -= 4;
|
||||
io.quadTo(_curves[e], _curves[e+1],
|
||||
_curves[e+2], _curves[e+3]);
|
||||
continue;
|
||||
case TYPE_CUBICTO:
|
||||
e -= 6;
|
||||
io.curveTo(_curves[e], _curves[e+1],
|
||||
_curves[e+2], _curves[e+3],
|
||||
_curves[e+4], _curves[e+5]);
|
||||
continue;
|
||||
case TYPE_QUADTO:
|
||||
e -= 4;
|
||||
io.quadTo(_curves[e], _curves[e+1],
|
||||
_curves[e+2], _curves[e+3]);
|
||||
continue;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
@ -542,7 +542,7 @@ final class Stroker implements PathConsumer2D, MarlinConst {
|
||||
|
||||
// basic rejection criteria:
|
||||
if (sideCode == 0) {
|
||||
// ovelap clip:
|
||||
// overlap clip:
|
||||
if (subdivide) {
|
||||
// avoid reentrance
|
||||
subdivide = false;
|
||||
@ -636,6 +636,9 @@ final class Stroker implements PathConsumer2D, MarlinConst {
|
||||
emitReverse();
|
||||
|
||||
this.prev = CLOSE;
|
||||
this.cx0 = sx0;
|
||||
this.cy0 = sy0;
|
||||
this.cOutCode = sOutCode;
|
||||
|
||||
if (opened) {
|
||||
// do not emit close
|
||||
@ -670,7 +673,9 @@ final class Stroker implements PathConsumer2D, MarlinConst {
|
||||
// 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 (rdrCtx.closedPath) {
|
||||
emitReverse();
|
||||
} else {
|
||||
if (outcode == 0) {
|
||||
// current point = end's cap:
|
||||
if (capStyle == CAP_ROUND) {
|
||||
@ -695,8 +700,6 @@ final class Stroker implements PathConsumer2D, MarlinConst {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
emitReverse();
|
||||
}
|
||||
emitClose();
|
||||
}
|
||||
@ -1060,7 +1063,7 @@ final class Stroker implements PathConsumer2D, MarlinConst {
|
||||
|
||||
// basic rejection criteria:
|
||||
if (sideCode == 0) {
|
||||
// ovelap clip:
|
||||
// overlap clip:
|
||||
if (subdivide) {
|
||||
// avoid reentrance
|
||||
subdivide = false;
|
||||
@ -1208,7 +1211,7 @@ final class Stroker implements PathConsumer2D, MarlinConst {
|
||||
|
||||
// basic rejection criteria:
|
||||
if (sideCode == 0) {
|
||||
// ovelap clip:
|
||||
// overlap clip:
|
||||
if (subdivide) {
|
||||
// avoid reentrance
|
||||
subdivide = false;
|
||||
|
@ -531,6 +531,9 @@ final class TransformingPathConsumer2D {
|
||||
|
||||
private boolean outside = false;
|
||||
|
||||
// The starting point of the path
|
||||
private float sx0, sy0;
|
||||
|
||||
// The current point (TODO stupid repeated info)
|
||||
private float cx0, cy0;
|
||||
|
||||
@ -631,17 +634,26 @@ final class TransformingPathConsumer2D {
|
||||
finishPath();
|
||||
|
||||
out.closePath();
|
||||
|
||||
// back to starting point:
|
||||
this.cOutCode = Helpers.outcode(sx0, sy0, clipRect);
|
||||
this.cx0 = sx0;
|
||||
this.cy0 = sy0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveTo(final float x0, final float y0) {
|
||||
finishPath();
|
||||
|
||||
this.cOutCode = Helpers.outcode(x0, y0, clipRect);
|
||||
this.outside = false;
|
||||
out.moveTo(x0, y0);
|
||||
|
||||
// update starting point:
|
||||
this.cOutCode = Helpers.outcode(x0, y0, clipRect);
|
||||
this.cx0 = x0;
|
||||
this.cy0 = y0;
|
||||
|
||||
this.sx0 = x0;
|
||||
this.sy0 = y0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -656,7 +668,7 @@ final class TransformingPathConsumer2D {
|
||||
|
||||
// basic rejection criteria:
|
||||
if (sideCode == 0) {
|
||||
// ovelap clip:
|
||||
// overlap clip:
|
||||
if (subdivide) {
|
||||
// avoid reentrance
|
||||
subdivide = false;
|
||||
@ -755,7 +767,7 @@ final class TransformingPathConsumer2D {
|
||||
|
||||
// basic rejection criteria:
|
||||
if (sideCode == 0) {
|
||||
// ovelap clip:
|
||||
// overlap clip:
|
||||
if (subdivide) {
|
||||
// avoid reentrance
|
||||
subdivide = false;
|
||||
@ -817,7 +829,7 @@ final class TransformingPathConsumer2D {
|
||||
|
||||
// basic rejection criteria:
|
||||
if (sideCode == 0) {
|
||||
// ovelap clip:
|
||||
// overlap clip:
|
||||
if (subdivide) {
|
||||
// avoid reentrance
|
||||
subdivide = false;
|
||||
@ -1154,13 +1166,13 @@ final class TransformingPathConsumer2D {
|
||||
|
||||
@Override
|
||||
public void moveTo(float x0, float y0) {
|
||||
log("moveTo (" + x0 + ", " + y0 + ')');
|
||||
log("p.moveTo(" + x0 + ", " + y0 + ");");
|
||||
out.moveTo(x0, y0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lineTo(float x1, float y1) {
|
||||
log("lineTo (" + x1 + ", " + y1 + ')');
|
||||
log("p.lineTo(" + x1 + ", " + y1 + ");");
|
||||
out.lineTo(x1, y1);
|
||||
}
|
||||
|
||||
@ -1169,25 +1181,26 @@ final class TransformingPathConsumer2D {
|
||||
float x2, float y2,
|
||||
float x3, float y3)
|
||||
{
|
||||
log("curveTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ')');
|
||||
log("p.curveTo(" + x1 + ", " + y1 + ", " + x2 + ", " + y2 + ", " + 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 + ')');
|
||||
public void quadTo(float x1, float y1,
|
||||
float x2, float y2) {
|
||||
log("p.quadTo(" + x1 + ", " + y1 + ", " + x2 + ", " + y2 + ");");
|
||||
out.quadTo(x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closePath() {
|
||||
log("closePath");
|
||||
log("p.closePath();");
|
||||
out.closePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pathDone() {
|
||||
log("pathDone");
|
||||
log("p.pathDone();");
|
||||
out.pathDone();
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ package sun.java2d.marlin;
|
||||
|
||||
public final class Version {
|
||||
|
||||
private static final String VERSION = "marlin-0.9.1.1-Unsafe-OpenJDK";
|
||||
private static final String VERSION = "marlin-0.9.1.2-Unsafe-OpenJDK";
|
||||
|
||||
public static String getVersion() {
|
||||
return VERSION;
|
||||
|
@ -24,11 +24,14 @@ 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.Shape;
|
||||
import java.awt.geom.CubicCurve2D;
|
||||
import java.awt.geom.Ellipse2D;
|
||||
import java.awt.geom.Line2D;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.awt.geom.PathIterator;
|
||||
import java.awt.geom.QuadCurve2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBufferInt;
|
||||
import java.io.File;
|
||||
@ -69,27 +72,30 @@ import javax.imageio.stream.ImageOutputStream;
|
||||
*/
|
||||
public final class ClipShapeTest {
|
||||
|
||||
static boolean TX_SCALE = false;
|
||||
static boolean TX_SHEAR = false;
|
||||
|
||||
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;
|
||||
// test options:
|
||||
static int NUM_TESTS;
|
||||
|
||||
// shape settings:
|
||||
static ShapeMode SHAPE_MODE = ShapeMode.NINE_LINE_POLYS;
|
||||
static ShapeMode SHAPE_MODE;
|
||||
|
||||
static boolean USE_DASHES;
|
||||
static boolean USE_VAR_STROKE;
|
||||
|
||||
static int THRESHOLD_DELTA;
|
||||
static long THRESHOLD_NBPIX;
|
||||
|
||||
static final boolean SHAPE_REPEAT = true;
|
||||
// constants:
|
||||
static final boolean DO_FAIL = Boolean.valueOf(System.getProperty("ClipShapeTest.fail", "true"));
|
||||
|
||||
static final boolean TEST_STROKER = true;
|
||||
static final boolean TEST_FILLER = true;
|
||||
|
||||
static final boolean SUBDIVIDE_CURVE = true;
|
||||
static final double SUBDIVIDE_LEN_TH = 50.0;
|
||||
static final boolean TRACE_SUBDIVIDE_CURVE = false;
|
||||
|
||||
static final int TESTW = 100;
|
||||
static final int TESTH = 100;
|
||||
|
||||
// dump path on console:
|
||||
static final boolean DUMP_SHAPE = true;
|
||||
@ -103,7 +109,8 @@ public final class ClipShapeTest {
|
||||
static final int MAX_SAVE_FRAMES = 100;
|
||||
|
||||
// use fixed seed to reproduce always same polygons between tests
|
||||
static final boolean FIXED_SEED = false;
|
||||
static final boolean FIXED_SEED = true;
|
||||
|
||||
static final double RAND_SCALE = 3.0;
|
||||
static final double RANDW = TESTW * RAND_SCALE;
|
||||
static final double OFFW = (TESTW - RANDW) / 2.0;
|
||||
@ -126,6 +133,7 @@ public final class ClipShapeTest {
|
||||
static final File OUTPUT_DIR = new File(".");
|
||||
|
||||
static final AtomicBoolean isMarlin = new AtomicBoolean();
|
||||
static final AtomicBoolean isMarlinFloat = new AtomicBoolean();
|
||||
static final AtomicBoolean isClipRuntime = new AtomicBoolean();
|
||||
|
||||
static {
|
||||
@ -143,6 +151,7 @@ public final class ClipShapeTest {
|
||||
// last space to avoid matching other settings:
|
||||
if (msg.startsWith("sun.java2d.renderer ")) {
|
||||
isMarlin.set(msg.contains("MarlinRenderingEngine"));
|
||||
isMarlinFloat.set(!msg.contains("DMarlinRenderingEngine"));
|
||||
}
|
||||
if (msg.startsWith("sun.java2d.renderer.clip.runtime.enable")) {
|
||||
isClipRuntime.set(msg.contains("true"));
|
||||
@ -186,12 +195,22 @@ public final class ClipShapeTest {
|
||||
// curve length max error:
|
||||
System.setProperty("sun.java2d.renderer.curve_len_err", "1e-4");
|
||||
|
||||
// quad max error:
|
||||
System.setProperty("sun.java2d.renderer.quad_dec_d2", "5e-4");
|
||||
|
||||
// cubic min/max error:
|
||||
System.setProperty("sun.java2d.renderer.cubic_dec_d2", "1e-3");
|
||||
System.setProperty("sun.java2d.renderer.cubic_inc_d1", "1e-4"); // or disabled ~ 1e-6
|
||||
System.setProperty("sun.java2d.renderer.cubic_inc_d1", "1e-4");
|
||||
|
||||
// quad max error:
|
||||
System.setProperty("sun.java2d.renderer.quad_dec_d2", "5e-4");
|
||||
}
|
||||
|
||||
private static void resetOptions() {
|
||||
NUM_TESTS = Integer.getInteger("ClipShapeTest.numTests", 5000);
|
||||
|
||||
// shape settings:
|
||||
SHAPE_MODE = ShapeMode.NINE_LINE_POLYS;
|
||||
|
||||
USE_DASHES = false;
|
||||
USE_VAR_STROKE = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -199,79 +218,119 @@ public final class ClipShapeTest {
|
||||
* @param args
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
{
|
||||
// Bootstrap: init Renderer now:
|
||||
final BufferedImage img = newImage(TESTW, TESTH);
|
||||
final Graphics2D g2d = initialize(img, null);
|
||||
|
||||
try {
|
||||
paintShape(new Line2D.Double(0,0,100,100), g2d, true, false);
|
||||
} finally {
|
||||
g2d.dispose();
|
||||
}
|
||||
|
||||
if (!isMarlin.get()) {
|
||||
throw new RuntimeException("Marlin renderer not used at runtime !");
|
||||
}
|
||||
if (!isClipRuntime.get()) {
|
||||
throw new RuntimeException("Marlin clipping not enabled at runtime !");
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("---------------------------------------");
|
||||
System.out.println("ClipShapeTest: image = " + TESTW + " x " + TESTH);
|
||||
|
||||
resetOptions();
|
||||
|
||||
boolean runSlowTests = false;
|
||||
|
||||
for (String arg : args) {
|
||||
if ("-slow".equals(arg)) {
|
||||
System.out.println("slow: enabled.");
|
||||
runSlowTests = true;
|
||||
} else if ("-doScale".equals(arg)) {
|
||||
System.out.println("doScale: enabled.");
|
||||
TX_SCALE = true;
|
||||
} else if ("-doShear".equals(arg)) {
|
||||
System.out.println("doShear: enabled.");
|
||||
TX_SHEAR = true;
|
||||
} else if ("-doDash".equals(arg)) {
|
||||
System.out.println("doDash: enabled.");
|
||||
USE_DASHES = true;
|
||||
} else if ("-doVarStroke".equals(arg)) {
|
||||
System.out.println("doVarStroke: enabled.");
|
||||
USE_VAR_STROKE = true;
|
||||
}
|
||||
// shape mode:
|
||||
else if (arg.equalsIgnoreCase("-poly")) {
|
||||
SHAPE_MODE = ShapeMode.NINE_LINE_POLYS;
|
||||
} else if (arg.equalsIgnoreCase("-bigpoly")) {
|
||||
SHAPE_MODE = ShapeMode.FIFTY_LINE_POLYS;
|
||||
} else if (arg.equalsIgnoreCase("-quad")) {
|
||||
SHAPE_MODE = ShapeMode.FOUR_QUADS;
|
||||
} else if (arg.equalsIgnoreCase("-cubic")) {
|
||||
SHAPE_MODE = ShapeMode.TWO_CUBICS;
|
||||
} else if (arg.equalsIgnoreCase("-mixed")) {
|
||||
SHAPE_MODE = ShapeMode.MIXED;
|
||||
} else {
|
||||
// shape mode:
|
||||
if (arg.equalsIgnoreCase("-poly")) {
|
||||
SHAPE_MODE = ShapeMode.NINE_LINE_POLYS;
|
||||
} else if (arg.equalsIgnoreCase("-bigpoly")) {
|
||||
SHAPE_MODE = ShapeMode.FIFTY_LINE_POLYS;
|
||||
} else if (arg.equalsIgnoreCase("-quad")) {
|
||||
SHAPE_MODE = ShapeMode.FOUR_QUADS;
|
||||
} else if (arg.equalsIgnoreCase("-cubic")) {
|
||||
SHAPE_MODE = ShapeMode.TWO_CUBICS;
|
||||
} else if (arg.equalsIgnoreCase("-mixed")) {
|
||||
SHAPE_MODE = ShapeMode.MIXED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("Shape mode: " + SHAPE_MODE);
|
||||
|
||||
// adjust image comparison thresholds:
|
||||
switch(SHAPE_MODE) {
|
||||
switch (SHAPE_MODE) {
|
||||
case TWO_CUBICS:
|
||||
// Define uncertainty for curves:
|
||||
THRESHOLD_DELTA = 32; // / 256
|
||||
THRESHOLD_NBPIX = 128; // / 10000
|
||||
THRESHOLD_DELTA = 32;
|
||||
THRESHOLD_NBPIX = (USE_DASHES) ? 50 : 200;
|
||||
if (SUBDIVIDE_CURVE) {
|
||||
THRESHOLD_NBPIX = 4;
|
||||
}
|
||||
break;
|
||||
case FOUR_QUADS:
|
||||
case MIXED:
|
||||
// Define uncertainty for quads:
|
||||
// curve subdivision causes curves to be smaller
|
||||
// then curve offsets are different (more accurate)
|
||||
THRESHOLD_DELTA = 64; // 64 / 256
|
||||
THRESHOLD_NBPIX = 256; // 256 / 10000
|
||||
THRESHOLD_DELTA = 64;
|
||||
THRESHOLD_NBPIX = (USE_DASHES) ? 40 : 420;
|
||||
if (SUBDIVIDE_CURVE) {
|
||||
THRESHOLD_NBPIX = 10;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Define uncertainty for lines:
|
||||
// float variant have higher uncertainty
|
||||
THRESHOLD_DELTA = 8;
|
||||
THRESHOLD_NBPIX = 8;
|
||||
THRESHOLD_DELTA = 2;
|
||||
THRESHOLD_NBPIX = (USE_DASHES) ?
|
||||
// float variant have higher uncertainty
|
||||
((isMarlinFloat.get()) ? 30 : 6) // low for double
|
||||
: (isMarlinFloat.get()) ? 10 : 0;
|
||||
}
|
||||
|
||||
System.out.println("THRESHOLD_DELTA: "+THRESHOLD_DELTA);
|
||||
System.out.println("THRESHOLD_NBPIX: "+THRESHOLD_NBPIX);
|
||||
// Visual inspection (low threshold):
|
||||
// THRESHOLD_NBPIX = 2;
|
||||
|
||||
System.out.println("THRESHOLD_DELTA: " + THRESHOLD_DELTA);
|
||||
System.out.println("THRESHOLD_NBPIX: " + THRESHOLD_NBPIX);
|
||||
|
||||
if (runSlowTests) {
|
||||
NUM_TESTS = 10000; // or 100000 (very slow)
|
||||
USE_DASHES = true;
|
||||
USE_VAR_STROKE = true;
|
||||
}
|
||||
|
||||
System.out.println("ClipShapeTests: image = " + TESTW + " x " + TESTH);
|
||||
System.out.println("NUM_TESTS: " + NUM_TESTS);
|
||||
|
||||
if (USE_DASHES) {
|
||||
System.out.println("USE_DASHES: enabled.");
|
||||
}
|
||||
if (USE_VAR_STROKE) {
|
||||
System.out.println("USE_VAR_STROKE: enabled.");
|
||||
}
|
||||
if (!DO_FAIL) {
|
||||
System.out.println("DO_FAIL: disabled.");
|
||||
}
|
||||
|
||||
System.out.println("---------------------------------------");
|
||||
|
||||
final DiffContext allCtx = new DiffContext("All Test setups");
|
||||
final DiffContext allWorstCtx = new DiffContext("Worst(All Test setups)");
|
||||
|
||||
int failures = 0;
|
||||
final long start = System.nanoTime();
|
||||
try {
|
||||
// TODO: test affine transforms ?
|
||||
|
||||
if (TEST_STROKER) {
|
||||
final float[][] dashArrays = (USE_DASHES) ?
|
||||
// small
|
||||
@ -291,7 +350,7 @@ public final class ClipShapeTest {
|
||||
|
||||
int nsw = 0;
|
||||
if (USE_VAR_STROKE) {
|
||||
for (float width = 0.1f; width < 110f; width *= 5f) {
|
||||
for (float width = 0.25f; width < 110f; width *= 5f) {
|
||||
strokeWidths[nsw++] = width;
|
||||
}
|
||||
} else {
|
||||
@ -310,8 +369,8 @@ public final class ClipShapeTest {
|
||||
|
||||
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));
|
||||
failures += paintPaths(allCtx, allWorstCtx, new TestSetup(SHAPE_MODE, false, width, cap, join, dashes));
|
||||
failures += paintPaths(allCtx, allWorstCtx, new TestSetup(SHAPE_MODE, true, width, cap, join, dashes));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -320,29 +379,26 @@ public final class ClipShapeTest {
|
||||
|
||||
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(allCtx, allWorstCtx, new TestSetup(SHAPE_MODE, false, Path2D.WIND_NON_ZERO));
|
||||
failures += paintPaths(allCtx, allWorstCtx, 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));
|
||||
failures += paintPaths(allCtx, allWorstCtx, new TestSetup(SHAPE_MODE, false, Path2D.WIND_EVEN_ODD));
|
||||
failures += paintPaths(allCtx, allWorstCtx, 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) {
|
||||
allWorstCtx.dump();
|
||||
allCtx.dump();
|
||||
|
||||
if (DO_FAIL && (failures != 0)) {
|
||||
throw new RuntimeException("Clip test failures : " + failures);
|
||||
}
|
||||
}
|
||||
|
||||
static int paintPaths(final TestSetup ts) throws IOException {
|
||||
static int paintPaths(final DiffContext allCtx, final DiffContext allWorstCtx, final TestSetup ts) throws IOException {
|
||||
final long start = System.nanoTime();
|
||||
|
||||
if (FIXED_SEED) {
|
||||
@ -356,19 +412,24 @@ public final class ClipShapeTest {
|
||||
final boolean fill = !ts.isStroke();
|
||||
final Path2D p2d = new Path2D.Double(ts.windingRule);
|
||||
|
||||
final Stroke stroke = (!fill) ? createStroke(ts) : null;
|
||||
|
||||
final BufferedImage imgOn = newImage(TESTW, TESTH);
|
||||
final Graphics2D g2dOn = initialize(imgOn, ts);
|
||||
final Graphics2D g2dOn = initialize(imgOn, stroke);
|
||||
|
||||
final BufferedImage imgOff = newImage(TESTW, TESTH);
|
||||
final Graphics2D g2dOff = initialize(imgOff, ts);
|
||||
final Graphics2D g2dOff = initialize(imgOff, stroke);
|
||||
|
||||
final BufferedImage imgDiff = newImage(TESTW, TESTH);
|
||||
|
||||
final DiffContext globalCtx = new DiffContext("All tests");
|
||||
final DiffContext testSetupCtx = new DiffContext("Test setup");
|
||||
final DiffContext testWorstCtx = new DiffContext("Worst");
|
||||
final DiffContext testWorstThCtx = new DiffContext("Worst(>threshold)");
|
||||
|
||||
int nd = 0;
|
||||
try {
|
||||
final DiffContext testCtx = new DiffContext("Test");
|
||||
final DiffContext testThCtx = new DiffContext("Test(>threshold)");
|
||||
BufferedImage diffImage;
|
||||
|
||||
for (int n = 0; n < NUM_TESTS; n++) {
|
||||
@ -381,15 +442,24 @@ public final class ClipShapeTest {
|
||||
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;
|
||||
diffImage = computeDiffImage(testCtx, testThCtx, imgOn, imgOff, imgDiff);
|
||||
|
||||
// Worst (total)
|
||||
if (testCtx.isDiff()) {
|
||||
if (testWorstCtx.isWorse(testCtx, false)) {
|
||||
testWorstCtx.set(testCtx);
|
||||
}
|
||||
if (testWorstThCtx.isWorse(testCtx, true)) {
|
||||
testWorstThCtx.set(testCtx);
|
||||
}
|
||||
// accumulate data:
|
||||
testSetupCtx.add(testCtx);
|
||||
}
|
||||
if (diffImage != null) {
|
||||
nd++;
|
||||
|
||||
final double ratio = (100.0 * testCtx.histPix.count) / testCtx.histAll.count;
|
||||
System.out.println("Diff ratio: " + testName + " = " + trimTo3Digits(ratio) + " %");
|
||||
testThCtx.dump();
|
||||
testCtx.dump();
|
||||
|
||||
if (nd < MAX_SHOW_FRAMES) {
|
||||
if (SHOW_DETAILS) {
|
||||
@ -401,9 +471,12 @@ public final class ClipShapeTest {
|
||||
if (DUMP_SHAPE) {
|
||||
dumpShape(p2d);
|
||||
}
|
||||
|
||||
final String testName = "Setup_" + ts.id + "_test_" + n;
|
||||
|
||||
saveImage(imgOff, OUTPUT_DIR, testName + "-off.png");
|
||||
saveImage(imgOn, OUTPUT_DIR, testName + "-on.png");
|
||||
saveImage(diffImage, OUTPUT_DIR, testName + "-diff.png");
|
||||
saveImage(imgDiff, OUTPUT_DIR, testName + "-diff.png");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -418,13 +491,25 @@ public final class ClipShapeTest {
|
||||
+ " ratio = " + (100f * nd) / NUM_TESTS + " %");
|
||||
}
|
||||
|
||||
globalCtx.dump();
|
||||
if (testWorstCtx.isDiff()) {
|
||||
testWorstCtx.dump();
|
||||
if (testWorstThCtx.isDiff() && testWorstThCtx.histPix.sum != testWorstCtx.histPix.sum) {
|
||||
testWorstThCtx.dump();
|
||||
}
|
||||
if (allWorstCtx.isWorse(testWorstThCtx, true)) {
|
||||
allWorstCtx.set(testWorstThCtx);
|
||||
}
|
||||
}
|
||||
testSetupCtx.dump();
|
||||
|
||||
// accumulate data:
|
||||
allCtx.add(testSetupCtx);
|
||||
}
|
||||
System.out.println("paintPaths: duration= " + (1e-6 * (System.nanoTime() - start)) + " ms.");
|
||||
return nd;
|
||||
}
|
||||
|
||||
private static void paintShape(final Path2D p2d, final Graphics2D g2d,
|
||||
private static void paintShape(final Shape p2d, final Graphics2D g2d,
|
||||
final boolean fill, final boolean clip) {
|
||||
reset(g2d);
|
||||
|
||||
@ -438,26 +523,20 @@ public final class ClipShapeTest {
|
||||
}
|
||||
|
||||
private static Graphics2D initialize(final BufferedImage img,
|
||||
final TestSetup ts) {
|
||||
final Stroke s) {
|
||||
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);
|
||||
// Test normalize:
|
||||
// RenderingHints.VALUE_STROKE_NORMALIZE
|
||||
RenderingHints.VALUE_STROKE_PURE
|
||||
);
|
||||
|
||||
if (ts.isStroke()) {
|
||||
g2d.setStroke(createStroke(ts));
|
||||
}
|
||||
g2d.setColor(Color.GRAY);
|
||||
|
||||
// Test scale
|
||||
if (TX_SCALE) {
|
||||
g2d.scale(1.2, 1.2);
|
||||
}
|
||||
// Test shear
|
||||
if (TX_SHEAR) {
|
||||
g2d.shear(0.1, 0.2);
|
||||
if (s != null) {
|
||||
g2d.setStroke(s);
|
||||
}
|
||||
g2d.setColor(Color.BLACK);
|
||||
|
||||
return g2d;
|
||||
}
|
||||
@ -482,20 +561,35 @@ public final class ClipShapeTest {
|
||||
static void genShape(final Path2D p2d, final TestSetup ts) {
|
||||
p2d.reset();
|
||||
|
||||
final int end = (SHAPE_REPEAT) ? 2 : 1;
|
||||
/*
|
||||
Test closed path:
|
||||
0: moveTo + (draw)To + closePath
|
||||
1: (draw)To + closePath (closePath + (draw)To sequence)
|
||||
*/
|
||||
final int end = (ts.closed) ? 2 : 1;
|
||||
|
||||
final double[] in = new double[8];
|
||||
|
||||
double sx0 = 0.0, sy0 = 0.0, x0 = 0.0, y0 = 0.0;
|
||||
|
||||
for (int p = 0; p < end; p++) {
|
||||
p2d.moveTo(randX(), randY());
|
||||
if (p <= 0) {
|
||||
x0 = randX(); y0 = randY();
|
||||
p2d.moveTo(x0, y0);
|
||||
sx0 = x0; sy0 = y0;
|
||||
}
|
||||
|
||||
switch (ts.shapeMode) {
|
||||
case MIXED:
|
||||
case FIFTY_LINE_POLYS:
|
||||
case NINE_LINE_POLYS:
|
||||
case FIVE_LINE_POLYS:
|
||||
case NINE_LINE_POLYS:
|
||||
case FIFTY_LINE_POLYS:
|
||||
p2d.lineTo(randX(), randY());
|
||||
p2d.lineTo(randX(), randY());
|
||||
p2d.lineTo(randX(), randY());
|
||||
p2d.lineTo(randX(), randY());
|
||||
x0 = randX(); y0 = randY();
|
||||
p2d.lineTo(x0, y0);
|
||||
if (ts.shapeMode == ShapeMode.FIVE_LINE_POLYS) {
|
||||
// And an implicit close makes 5 lines
|
||||
break;
|
||||
@ -503,29 +597,75 @@ public final class ClipShapeTest {
|
||||
p2d.lineTo(randX(), randY());
|
||||
p2d.lineTo(randX(), randY());
|
||||
p2d.lineTo(randX(), randY());
|
||||
p2d.lineTo(randX(), randY());
|
||||
x0 = randX(); y0 = randY();
|
||||
p2d.lineTo(x0, y0);
|
||||
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());
|
||||
x0 = randX(); y0 = randY();
|
||||
p2d.lineTo(x0, y0);
|
||||
}
|
||||
// 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 (SUBDIVIDE_CURVE) {
|
||||
in[0] = x0; in[1] = y0;
|
||||
in[2] = randX(); in[3] = randY();
|
||||
in[4] = randX(); in[5] = randY();
|
||||
x0 = randX(); y0 = randY();
|
||||
in[6] = x0; in[7] = y0;
|
||||
subdivide(p2d, 8, in);
|
||||
in[0] = x0; in[1] = y0;
|
||||
in[2] = randX(); in[3] = randY();
|
||||
in[4] = randX(); in[5] = randY();
|
||||
x0 = randX(); y0 = randY();
|
||||
in[6] = x0; in[7] = y0;
|
||||
subdivide(p2d, 8, in);
|
||||
} else {
|
||||
x0 = randX(); y0 = randY();
|
||||
p2d.curveTo(randX(), randY(), randX(), randY(), x0, y0);
|
||||
x0 = randX(); y0 = randY();
|
||||
p2d.curveTo(randX(), randY(), randX(), randY(), x0, y0);
|
||||
}
|
||||
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 (SUBDIVIDE_CURVE) {
|
||||
in[0] = x0; in[1] = y0;
|
||||
in[2] = randX(); in[3] = randY();
|
||||
x0 = randX(); y0 = randY();
|
||||
in[4] = x0; in[5] = y0;
|
||||
subdivide(p2d, 6, in);
|
||||
in[0] = x0; in[1] = y0;
|
||||
in[2] = randX(); in[3] = randY();
|
||||
x0 = randX(); y0 = randY();
|
||||
in[4] = x0; in[5] = y0;
|
||||
subdivide(p2d, 6, in);
|
||||
in[0] = x0; in[1] = y0;
|
||||
in[2] = randX(); in[3] = randY();
|
||||
x0 = randX(); y0 = randY();
|
||||
in[4] = x0; in[5] = y0;
|
||||
subdivide(p2d, 6, in);
|
||||
in[0] = x0; in[1] = y0;
|
||||
in[2] = randX(); in[3] = randY();
|
||||
x0 = randX(); y0 = randY();
|
||||
in[4] = x0; in[5] = y0;
|
||||
subdivide(p2d, 6, in);
|
||||
} else {
|
||||
x0 = randX(); y0 = randY();
|
||||
p2d.quadTo(randX(), randY(), x0, y0);
|
||||
x0 = randX(); y0 = randY();
|
||||
p2d.quadTo(randX(), randY(), x0, y0);
|
||||
x0 = randX(); y0 = randY();
|
||||
p2d.quadTo(randX(), randY(), x0, y0);
|
||||
x0 = randX(); y0 = randY();
|
||||
p2d.quadTo(randX(), randY(), x0, y0);
|
||||
}
|
||||
if (ts.shapeMode == ShapeMode.FOUR_QUADS) {
|
||||
break;
|
||||
}
|
||||
@ -534,6 +674,111 @@ public final class ClipShapeTest {
|
||||
|
||||
if (ts.closed) {
|
||||
p2d.closePath();
|
||||
x0 = sx0; y0 = sy0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static final int SUBDIVIDE_LIMIT = 5;
|
||||
static final double[][] SUBDIVIDE_CURVES = new double[SUBDIVIDE_LIMIT + 1][];
|
||||
|
||||
static {
|
||||
for (int i = 0, n = 1; i < SUBDIVIDE_LIMIT; i++, n *= 2) {
|
||||
SUBDIVIDE_CURVES[i] = new double[8 * n];
|
||||
}
|
||||
}
|
||||
|
||||
static void subdivide(final Path2D p2d, final int type, final double[] in) {
|
||||
if (TRACE_SUBDIVIDE_CURVE) {
|
||||
System.out.println("subdivide: " + Arrays.toString(Arrays.copyOf(in, type)));
|
||||
}
|
||||
|
||||
double curveLen = ((type == 8)
|
||||
? curvelen(in[0], in[1], in[2], in[3], in[4], in[5], in[6], in[7])
|
||||
: quadlen(in[0], in[1], in[2], in[3], in[4], in[5]));
|
||||
|
||||
if (curveLen > SUBDIVIDE_LEN_TH) {
|
||||
if (TRACE_SUBDIVIDE_CURVE) {
|
||||
System.out.println("curvelen: " + curveLen);
|
||||
}
|
||||
|
||||
System.arraycopy(in, 0, SUBDIVIDE_CURVES[0], 0, 8);
|
||||
|
||||
int level = 0;
|
||||
while (curveLen >= SUBDIVIDE_LEN_TH) {
|
||||
level++;
|
||||
curveLen /= 2.0;
|
||||
if (TRACE_SUBDIVIDE_CURVE) {
|
||||
System.out.println("curvelen: " + curveLen);
|
||||
}
|
||||
}
|
||||
|
||||
if (TRACE_SUBDIVIDE_CURVE) {
|
||||
System.out.println("level: " + level);
|
||||
}
|
||||
|
||||
if (level > SUBDIVIDE_LIMIT) {
|
||||
if (TRACE_SUBDIVIDE_CURVE) {
|
||||
System.out.println("max level reached : " + level);
|
||||
}
|
||||
level = SUBDIVIDE_LIMIT;
|
||||
}
|
||||
|
||||
for (int l = 0; l < level; l++) {
|
||||
if (TRACE_SUBDIVIDE_CURVE) {
|
||||
System.out.println("level: " + l);
|
||||
}
|
||||
|
||||
double[] src = SUBDIVIDE_CURVES[l];
|
||||
double[] dst = SUBDIVIDE_CURVES[l + 1];
|
||||
|
||||
for (int i = 0, j = 0; i < src.length; i += 8, j += 16) {
|
||||
if (TRACE_SUBDIVIDE_CURVE) {
|
||||
System.out.println("subdivide: " + Arrays.toString(Arrays.copyOfRange(src, i, i + type)));
|
||||
}
|
||||
if (type == 8) {
|
||||
CubicCurve2D.subdivide(src, i, dst, j, dst, j + 8);
|
||||
} else {
|
||||
QuadCurve2D.subdivide(src, i, dst, j, dst, j + 8);
|
||||
}
|
||||
if (TRACE_SUBDIVIDE_CURVE) {
|
||||
System.out.println("left: " + Arrays.toString(Arrays.copyOfRange(dst, j, j + type)));
|
||||
System.out.println("right: " + Arrays.toString(Arrays.copyOfRange(dst, j + 8, j + 8 + type)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Emit curves at last level:
|
||||
double[] src = SUBDIVIDE_CURVES[level];
|
||||
|
||||
double len = 0.0;
|
||||
|
||||
for (int i = 0; i < src.length; i += 8) {
|
||||
if (TRACE_SUBDIVIDE_CURVE) {
|
||||
System.out.println("curve: " + Arrays.toString(Arrays.copyOfRange(src, i, i + type)));
|
||||
}
|
||||
|
||||
if (type == 8) {
|
||||
if (TRACE_SUBDIVIDE_CURVE) {
|
||||
len += curvelen(src[i + 0], src[i + 1], src[i + 2], src[i + 3], src[i + 4], src[i + 5], src[i + 6], src[i + 7]);
|
||||
}
|
||||
p2d.curveTo(src[i + 2], src[i + 3], src[i + 4], src[i + 5], src[i + 6], src[i + 7]);
|
||||
} else {
|
||||
if (TRACE_SUBDIVIDE_CURVE) {
|
||||
len += quadlen(src[i + 0], src[i + 1], src[i + 2], src[i + 3], src[i + 4], src[i + 5]);
|
||||
}
|
||||
p2d.quadTo(src[i + 2], src[i + 3], src[i + 4], src[i + 5]);
|
||||
}
|
||||
}
|
||||
|
||||
if (TRACE_SUBDIVIDE_CURVE) {
|
||||
System.out.println("curveLen (final) = " + len);
|
||||
}
|
||||
} else {
|
||||
if (type == 8) {
|
||||
p2d.curveTo(in[2], in[3], in[4], in[5], in[6], in[7]);
|
||||
} else {
|
||||
p2d.quadTo(in[2], in[3], in[4], in[5]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -754,18 +999,19 @@ public final class ClipShapeTest {
|
||||
return new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB_PRE);
|
||||
}
|
||||
|
||||
public static BufferedImage computeDiffImage(final DiffContext localCtx,
|
||||
public static BufferedImage computeDiffImage(final DiffContext testCtx,
|
||||
final DiffContext testThCtx,
|
||||
final BufferedImage tstImage,
|
||||
final BufferedImage refImage,
|
||||
final BufferedImage diffImage,
|
||||
final DiffContext globalCtx) {
|
||||
final BufferedImage diffImage) {
|
||||
|
||||
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();
|
||||
// reset diff contexts:
|
||||
testCtx.reset();
|
||||
testThCtx.reset();
|
||||
|
||||
int ref, tst, dg, v;
|
||||
for (int i = 0, len = aRefPix.length; i < len; i++) {
|
||||
@ -777,24 +1023,24 @@ public final class ClipShapeTest {
|
||||
|
||||
// max difference on grayscale values:
|
||||
v = (int) Math.ceil(Math.abs(dg / 3.0));
|
||||
|
||||
// TODO: count warnings
|
||||
if (v <= THRESHOLD_DELTA) {
|
||||
aDifPix[i] = 0;
|
||||
} else {
|
||||
aDifPix[i] = toInt(v, v, v);
|
||||
|
||||
localCtx.add(v);
|
||||
testThCtx.add(v);
|
||||
}
|
||||
|
||||
if (v != 0) {
|
||||
testCtx.add(v);
|
||||
}
|
||||
globalCtx.add(v);
|
||||
}
|
||||
|
||||
if (!localCtx.isDiff() || (localCtx.histPix.count <= THRESHOLD_NBPIX)) {
|
||||
testCtx.addNbPix(testThCtx.histPix.count);
|
||||
|
||||
if (!testThCtx.isDiff() || (testThCtx.histPix.count <= THRESHOLD_NBPIX)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
localCtx.dump();
|
||||
|
||||
return diffImage;
|
||||
}
|
||||
|
||||
@ -895,6 +1141,17 @@ public final class ClipShapeTest {
|
||||
}
|
||||
}
|
||||
|
||||
void add(StatInteger stat) {
|
||||
count += stat.count;
|
||||
sum += stat.sum;
|
||||
if (stat.min < min) {
|
||||
min = stat.min;
|
||||
}
|
||||
if (stat.max > max) {
|
||||
max = stat.max;
|
||||
}
|
||||
}
|
||||
|
||||
public final double average() {
|
||||
return ((double) sum) / count;
|
||||
}
|
||||
@ -908,8 +1165,11 @@ public final class ClipShapeTest {
|
||||
|
||||
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("]");
|
||||
sb.append("] ");
|
||||
if (count != 0) {
|
||||
sb.append("sum: ").append(sum).append(" avg: ").append(trimTo3Digits(average()));
|
||||
sb.append(" [").append(min).append(" | ").append(max).append("]");
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
@ -921,6 +1181,7 @@ public final class ClipShapeTest {
|
||||
static final int MAX = 20;
|
||||
static final int LAST = MAX - 1;
|
||||
static final int[] STEPS = new int[MAX];
|
||||
static final int BUCKET_TH;
|
||||
|
||||
static {
|
||||
STEPS[0] = 0;
|
||||
@ -930,6 +1191,12 @@ public final class ClipShapeTest {
|
||||
STEPS[i] = STEPS[i - 1] * BUCKET;
|
||||
}
|
||||
// System.out.println("Histogram.STEPS = " + Arrays.toString(STEPS));
|
||||
|
||||
if (THRESHOLD_DELTA % 2 != 0) {
|
||||
throw new IllegalStateException("THRESHOLD_DELTA must be odd");
|
||||
}
|
||||
|
||||
BUCKET_TH = bucket(THRESHOLD_DELTA);
|
||||
}
|
||||
|
||||
static int bucket(int val) {
|
||||
@ -969,6 +1236,37 @@ public final class ClipShapeTest {
|
||||
add((int) val);
|
||||
}
|
||||
|
||||
void add(Histogram hist) {
|
||||
super.add(hist);
|
||||
for (int i = 0; i < MAX; i++) {
|
||||
stats[i].add(hist.stats[i]);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isWorse(Histogram hist, boolean useTh) {
|
||||
boolean worst = false;
|
||||
if (!useTh && (hist.sum > sum)) {
|
||||
worst = true;
|
||||
} else {
|
||||
long sumLoc = 0l;
|
||||
long sumHist = 0l;
|
||||
// use running sum:
|
||||
for (int i = MAX - 1; i >= BUCKET_TH; i--) {
|
||||
sumLoc += stats[i].sum;
|
||||
sumHist += hist.stats[i].sum;
|
||||
}
|
||||
if (sumHist > sumLoc) {
|
||||
worst = true;
|
||||
}
|
||||
}
|
||||
/*
|
||||
System.out.println("running sum worst:");
|
||||
System.out.println("this ? " + toString());
|
||||
System.out.println("worst ? " + hist.toString());
|
||||
*/
|
||||
return worst;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
final StringBuilder sb = new StringBuilder(2048);
|
||||
@ -995,38 +1293,88 @@ public final class ClipShapeTest {
|
||||
|
||||
static final class DiffContext {
|
||||
|
||||
public final Histogram histAll;
|
||||
public final Histogram histPix;
|
||||
|
||||
public final StatInteger nbPix;
|
||||
|
||||
DiffContext(String name) {
|
||||
histAll = new Histogram("All Pixels [" + name + "]");
|
||||
histPix = new Histogram("Diff Pixels [" + name + "]");
|
||||
nbPix = new StatInteger("NbPixels [" + name + "]");
|
||||
}
|
||||
|
||||
void reset() {
|
||||
histAll.reset();
|
||||
histPix.reset();
|
||||
nbPix.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());
|
||||
System.out.println("Differences [" + histPix.name + "]:\n"
|
||||
+ ((nbPix.count != 0) ? (nbPix.toString() + "\n") : "")
|
||||
+ histPix.toString()
|
||||
);
|
||||
} else {
|
||||
System.out.println("No difference for [" + histAll.name + "].");
|
||||
System.out.println("No difference for [" + histPix.name + "].");
|
||||
}
|
||||
}
|
||||
|
||||
void add(int val) {
|
||||
histAll.add(val);
|
||||
if (val != 0) {
|
||||
histPix.add(val);
|
||||
histPix.add(val);
|
||||
}
|
||||
|
||||
void add(DiffContext ctx) {
|
||||
histPix.add(ctx.histPix);
|
||||
if (ctx.nbPix.count != 0L) {
|
||||
nbPix.add(ctx.nbPix);
|
||||
}
|
||||
}
|
||||
|
||||
void addNbPix(long val) {
|
||||
if (val != 0L) {
|
||||
nbPix.add(val);
|
||||
}
|
||||
}
|
||||
|
||||
void set(DiffContext ctx) {
|
||||
reset();
|
||||
add(ctx);
|
||||
}
|
||||
|
||||
boolean isWorse(DiffContext ctx, boolean useTh) {
|
||||
return histPix.isWorse(ctx.histPix, useTh);
|
||||
}
|
||||
|
||||
boolean isDiff() {
|
||||
return histAll.sum != 0l;
|
||||
return histPix.sum != 0l;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static double linelen(final double x0, final double y0,
|
||||
final double x1, final double y1)
|
||||
{
|
||||
final double dx = x1 - x0;
|
||||
final double dy = y1 - y0;
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
static double quadlen(final double x0, final double y0,
|
||||
final double x1, final double y1,
|
||||
final double x2, final double y2)
|
||||
{
|
||||
return (linelen(x0, y0, x1, y1)
|
||||
+ linelen(x1, y1, x2, y2)
|
||||
+ linelen(x0, y0, x2, y2)) / 2.0d;
|
||||
}
|
||||
|
||||
static double curvelen(final double x0, final double y0,
|
||||
final double x1, final double y1,
|
||||
final double x2, final double y2,
|
||||
final double x3, final double y3)
|
||||
{
|
||||
return (linelen(x0, y0, x1, y1)
|
||||
+ linelen(x1, y1, x2, y2)
|
||||
+ linelen(x2, y2, x3, y3)
|
||||
+ linelen(x0, y0, x3, y3)) / 2.0d;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user