mirror of
https://github.com/openjdk/jdk.git
synced 2026-02-26 10:10:19 +00:00
8144938: Handle properly coordinate overflow in Marlin Renderer
Skip point coordinates with NaN/Infinity values Reviewed-by: flar, prr
This commit is contained in:
parent
d8f82c9477
commit
048ce8be87
@ -51,6 +51,9 @@ public class MarlinRenderingEngine extends RenderingEngine
|
||||
|
||||
private static final float MIN_PEN_SIZE = 1f / NORM_SUBPIXELS;
|
||||
|
||||
static final float UPPER_BND = Float.MAX_VALUE / 2.0f;
|
||||
static final float LOWER_BND = -UPPER_BND;
|
||||
|
||||
/**
|
||||
* Public constructor
|
||||
*/
|
||||
@ -279,7 +282,7 @@ public class MarlinRenderingEngine extends RenderingEngine
|
||||
float dashphase,
|
||||
PathConsumer2D pc2d)
|
||||
{
|
||||
// We use strokerat and outat so that in Stroker and Dasher we can work only
|
||||
// We use strokerat so that in Stroker and Dasher we can work only
|
||||
// with the pre-transformation coordinates. This will repeat a lot of
|
||||
// computations done in the path iterator, but the alternative is to
|
||||
// work with transformed paths and compute untransformed coordinates
|
||||
@ -289,15 +292,11 @@ public class MarlinRenderingEngine extends RenderingEngine
|
||||
// However, if a path's width is constant after a transformation,
|
||||
// we can skip all this untransforming.
|
||||
|
||||
// If normalization is off we save some transformations by not
|
||||
// transforming the input to pisces. Instead, we apply the
|
||||
// transformation after the path processing has been done.
|
||||
// We can't do this if normalization is on, because it isn't a good
|
||||
// idea to normalize before the transformation is applied.
|
||||
// As pathTo() will check transformed coordinates for invalid values
|
||||
// (NaN / Infinity) to ignore such points, it is necessary to apply the
|
||||
// transformation before the path processing.
|
||||
AffineTransform strokerat = null;
|
||||
AffineTransform outat = null;
|
||||
|
||||
PathIterator pi;
|
||||
int dashLen = -1;
|
||||
boolean recycleDashes = false;
|
||||
|
||||
@ -333,6 +332,7 @@ public class MarlinRenderingEngine extends RenderingEngine
|
||||
// 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);
|
||||
|
||||
if (dashes != null) {
|
||||
recycleDashes = true;
|
||||
dashLen = dashes.length;
|
||||
@ -349,48 +349,35 @@ public class MarlinRenderingEngine extends RenderingEngine
|
||||
System.arraycopy(dashes, 0, newDashes, 0, dashLen);
|
||||
dashes = newDashes;
|
||||
for (int i = 0; i < dashLen; i++) {
|
||||
dashes[i] = scale * dashes[i];
|
||||
dashes[i] *= scale;
|
||||
}
|
||||
dashphase = scale * dashphase;
|
||||
dashphase *= scale;
|
||||
}
|
||||
width = scale * width;
|
||||
pi = getNormalizingPathIterator(rdrCtx, normalize,
|
||||
src.getPathIterator(at));
|
||||
width *= scale;
|
||||
|
||||
// by now strokerat == null && outat == null. Input paths to
|
||||
// by now strokerat == null. Input paths to
|
||||
// stroker (and maybe dasher) will have the full transform at
|
||||
// applied to them and nothing will happen to the output paths.
|
||||
} else {
|
||||
if (normalize != NormMode.OFF) {
|
||||
strokerat = at;
|
||||
pi = getNormalizingPathIterator(rdrCtx, normalize,
|
||||
src.getPathIterator(at));
|
||||
strokerat = at;
|
||||
|
||||
// by now strokerat == at && outat == null. Input paths to
|
||||
// stroker (and maybe dasher) will have the full transform at
|
||||
// applied to them, then they will be normalized, and then
|
||||
// the inverse of *only the non translation part of at* will
|
||||
// be applied to the normalized paths. This won't cause problems
|
||||
// in stroker, because, suppose at = T*A, where T is just the
|
||||
// translation part of at, and A is the rest. T*A has already
|
||||
// been applied to Stroker/Dasher's input. Then Ainv will be
|
||||
// applied. Ainv*T*A is not equal to T, but it is a translation,
|
||||
// which means that none of stroker's assumptions about its
|
||||
// input will be violated. After all this, A will be applied
|
||||
// to stroker's output.
|
||||
} else {
|
||||
outat = at;
|
||||
pi = src.getPathIterator(null);
|
||||
// outat == at && strokerat == null. This is because if no
|
||||
// normalization is done, we can just apply all our
|
||||
// transformations to stroker's output.
|
||||
}
|
||||
// by now strokerat == at. Input paths to
|
||||
// stroker (and maybe dasher) will have the full transform at
|
||||
// applied to them, then they will be normalized, and then
|
||||
// the inverse of *only the non translation part of at* will
|
||||
// be applied to the normalized paths. This won't cause problems
|
||||
// in stroker, because, suppose at = T*A, where T is just the
|
||||
// translation part of at, and A is the rest. T*A has already
|
||||
// been applied to Stroker/Dasher's input. Then Ainv will be
|
||||
// applied. Ainv*T*A is not equal to T, but it is a translation,
|
||||
// which means that none of stroker's assumptions about its
|
||||
// input will be violated. After all this, A will be applied
|
||||
// to stroker's output.
|
||||
}
|
||||
} else {
|
||||
// either at is null or it's the identity. In either case
|
||||
// we don't transform the path.
|
||||
pi = getNormalizingPathIterator(rdrCtx, normalize,
|
||||
src.getPathIterator(null));
|
||||
at = null;
|
||||
}
|
||||
|
||||
if (useSimplifier) {
|
||||
@ -399,12 +386,7 @@ public class MarlinRenderingEngine extends RenderingEngine
|
||||
pc2d = rdrCtx.simplifier.init(pc2d);
|
||||
}
|
||||
|
||||
// by now, at least one of outat and strokerat will be null. Unless at is not
|
||||
// a constant multiple of an orthogonal transformation, they will both be
|
||||
// null. In other cases, outat == at if normalization is off, and if
|
||||
// normalization is on, strokerat == at.
|
||||
final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;
|
||||
pc2d = transformerPC2D.transformConsumer(pc2d, outat);
|
||||
pc2d = transformerPC2D.deltaTransformConsumer(pc2d, strokerat);
|
||||
|
||||
pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit);
|
||||
@ -417,18 +399,22 @@ public class MarlinRenderingEngine extends RenderingEngine
|
||||
recycleDashes);
|
||||
}
|
||||
pc2d = transformerPC2D.inverseDeltaTransformConsumer(pc2d, strokerat);
|
||||
|
||||
final PathIterator pi = getNormalizingPathIterator(rdrCtx, normalize,
|
||||
src.getPathIterator(at));
|
||||
|
||||
pathTo(rdrCtx, pi, pc2d);
|
||||
|
||||
/*
|
||||
* Pipeline seems to be:
|
||||
* shape.getPathIterator
|
||||
* -> NormalizingPathIterator
|
||||
* -> inverseDeltaTransformConsumer
|
||||
* -> Dasher
|
||||
* shape.getPathIterator(at)
|
||||
* -> (NormalizingPathIterator)
|
||||
* -> (inverseDeltaTransformConsumer)
|
||||
* -> (Dasher)
|
||||
* -> Stroker
|
||||
* -> deltaTransformConsumer OR transformConsumer
|
||||
* -> (deltaTransformConsumer)
|
||||
*
|
||||
* -> CollinearSimplifier to remove redundant segments
|
||||
* -> (CollinearSimplifier) to remove redundant segments
|
||||
*
|
||||
* -> pc2d = Renderer (bounding box)
|
||||
*/
|
||||
@ -642,27 +628,109 @@ public class MarlinRenderingEngine extends RenderingEngine
|
||||
private static void pathToLoop(final float[] coords, final PathIterator pi,
|
||||
final PathConsumer2D pc2d)
|
||||
{
|
||||
// ported from DuctusRenderingEngine.feedConsumer() but simplified:
|
||||
// - removed skip flag = !subpathStarted
|
||||
// - removed pathClosed (ie subpathStarted not set to false)
|
||||
boolean subpathStarted = false;
|
||||
|
||||
for (; !pi.isDone(); pi.next()) {
|
||||
switch (pi.currentSegment(coords)) {
|
||||
case PathIterator.SEG_MOVETO:
|
||||
case PathIterator.SEG_MOVETO:
|
||||
/* Checking SEG_MOVETO coordinates if they are out of the
|
||||
* [LOWER_BND, UPPER_BND] range. This check also handles NaN
|
||||
* and Infinity values. Skipping next path segment in case of
|
||||
* invalid data.
|
||||
*/
|
||||
if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
|
||||
coords[1] < UPPER_BND && coords[1] > LOWER_BND)
|
||||
{
|
||||
pc2d.moveTo(coords[0], coords[1]);
|
||||
continue;
|
||||
case PathIterator.SEG_LINETO:
|
||||
pc2d.lineTo(coords[0], coords[1]);
|
||||
continue;
|
||||
case PathIterator.SEG_QUADTO:
|
||||
pc2d.quadTo(coords[0], coords[1],
|
||||
coords[2], coords[3]);
|
||||
continue;
|
||||
case PathIterator.SEG_CUBICTO:
|
||||
pc2d.curveTo(coords[0], coords[1],
|
||||
coords[2], coords[3],
|
||||
coords[4], coords[5]);
|
||||
continue;
|
||||
case PathIterator.SEG_CLOSE:
|
||||
subpathStarted = true;
|
||||
}
|
||||
break;
|
||||
case PathIterator.SEG_LINETO:
|
||||
/* Checking SEG_LINETO coordinates if they are out of the
|
||||
* [LOWER_BND, UPPER_BND] range. This check also handles NaN
|
||||
* and Infinity values. Ignoring current path segment in case
|
||||
* of invalid data. If segment is skipped its endpoint
|
||||
* (if valid) is used to begin new subpath.
|
||||
*/
|
||||
if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
|
||||
coords[1] < UPPER_BND && coords[1] > LOWER_BND)
|
||||
{
|
||||
if (subpathStarted) {
|
||||
pc2d.lineTo(coords[0], coords[1]);
|
||||
} else {
|
||||
pc2d.moveTo(coords[0], coords[1]);
|
||||
subpathStarted = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PathIterator.SEG_QUADTO:
|
||||
// Quadratic curves take two points
|
||||
/* Checking SEG_QUADTO coordinates if they are out of the
|
||||
* [LOWER_BND, UPPER_BND] range. This check also handles NaN
|
||||
* and Infinity values. Ignoring current path segment in case
|
||||
* of invalid endpoints's data. Equivalent to the SEG_LINETO
|
||||
* if endpoint coordinates are valid but there are invalid data
|
||||
* among other coordinates
|
||||
*/
|
||||
if (coords[2] < UPPER_BND && coords[2] > LOWER_BND &&
|
||||
coords[3] < UPPER_BND && coords[3] > LOWER_BND)
|
||||
{
|
||||
if (subpathStarted) {
|
||||
if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
|
||||
coords[1] < UPPER_BND && coords[1] > LOWER_BND)
|
||||
{
|
||||
pc2d.quadTo(coords[0], coords[1],
|
||||
coords[2], coords[3]);
|
||||
} else {
|
||||
pc2d.lineTo(coords[2], coords[3]);
|
||||
}
|
||||
} else {
|
||||
pc2d.moveTo(coords[2], coords[3]);
|
||||
subpathStarted = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PathIterator.SEG_CUBICTO:
|
||||
// Cubic curves take three points
|
||||
/* Checking SEG_CUBICTO coordinates if they are out of the
|
||||
* [LOWER_BND, UPPER_BND] range. This check also handles NaN
|
||||
* and Infinity values. Ignoring current path segment in case
|
||||
* of invalid endpoints's data. Equivalent to the SEG_LINETO
|
||||
* if endpoint coordinates are valid but there are invalid data
|
||||
* among other coordinates
|
||||
*/
|
||||
if (coords[4] < UPPER_BND && coords[4] > LOWER_BND &&
|
||||
coords[5] < UPPER_BND && coords[5] > LOWER_BND)
|
||||
{
|
||||
if (subpathStarted) {
|
||||
if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
|
||||
coords[1] < UPPER_BND && coords[1] > LOWER_BND &&
|
||||
coords[2] < UPPER_BND && coords[2] > LOWER_BND &&
|
||||
coords[3] < UPPER_BND && coords[3] > LOWER_BND)
|
||||
{
|
||||
pc2d.curveTo(coords[0], coords[1],
|
||||
coords[2], coords[3],
|
||||
coords[4], coords[5]);
|
||||
} else {
|
||||
pc2d.lineTo(coords[4], coords[5]);
|
||||
}
|
||||
} else {
|
||||
pc2d.moveTo(coords[4], coords[5]);
|
||||
subpathStarted = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PathIterator.SEG_CLOSE:
|
||||
if (subpathStarted) {
|
||||
pc2d.closePath();
|
||||
continue;
|
||||
default:
|
||||
// do not set subpathStarted to false
|
||||
// in case of missing moveTo() after close()
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
pc2d.pathDone();
|
||||
|
||||
@ -338,11 +338,6 @@ final class Stroker implements PathConsumer2D, MarlinConst {
|
||||
}
|
||||
|
||||
private void drawRoundCap(float cx, float cy, float mx, float my) {
|
||||
// the first and second arguments of the following two calls
|
||||
// are really will be ignored by emitCurveTo (because of the false),
|
||||
// but we put them in anyway, as opposed to just giving it 4 zeroes,
|
||||
// because it's just 4 additions and it's not good to rely on this
|
||||
// sort of assumption (right now it's true, but that may change).
|
||||
emitCurveTo(cx+mx-C*my, cy+my+C*mx,
|
||||
cx-my+C*mx, cy+mx+C*my,
|
||||
cx-my, cy+mx);
|
||||
|
||||
@ -35,7 +35,7 @@ final class TransformingPathConsumer2D {
|
||||
// used by RendererContext
|
||||
}
|
||||
|
||||
// recycled PathConsumer2D instance from transformConsumer()
|
||||
// recycled PathConsumer2D instance from wrapPath2d()
|
||||
private final Path2DWrapper wp_Path2DWrapper = new Path2DWrapper();
|
||||
|
||||
PathConsumer2D wrapPath2d(Path2D.Float p2d)
|
||||
@ -43,46 +43,6 @@ final class TransformingPathConsumer2D {
|
||||
return wp_Path2DWrapper.init(p2d);
|
||||
}
|
||||
|
||||
// recycled PathConsumer2D instances from transformConsumer()
|
||||
private final TranslateFilter tx_TranslateFilter = new TranslateFilter();
|
||||
private final DeltaScaleFilter tx_DeltaScaleFilter = new DeltaScaleFilter();
|
||||
private final ScaleFilter tx_ScaleFilter = new ScaleFilter();
|
||||
private final DeltaTransformFilter tx_DeltaTransformFilter = new DeltaTransformFilter();
|
||||
private final TransformFilter tx_TransformFilter = new TransformFilter();
|
||||
|
||||
PathConsumer2D transformConsumer(PathConsumer2D out,
|
||||
AffineTransform at)
|
||||
{
|
||||
if (at == null) {
|
||||
return out;
|
||||
}
|
||||
float mxx = (float) at.getScaleX();
|
||||
float mxy = (float) at.getShearX();
|
||||
float mxt = (float) at.getTranslateX();
|
||||
float myx = (float) at.getShearY();
|
||||
float myy = (float) at.getScaleY();
|
||||
float myt = (float) at.getTranslateY();
|
||||
if (mxy == 0f && myx == 0f) {
|
||||
if (mxx == 1f && myy == 1f) {
|
||||
if (mxt == 0f && myt == 0f) {
|
||||
return out;
|
||||
} else {
|
||||
return tx_TranslateFilter.init(out, mxt, myt);
|
||||
}
|
||||
} else {
|
||||
if (mxt == 0f && myt == 0f) {
|
||||
return tx_DeltaScaleFilter.init(out, mxx, myy);
|
||||
} else {
|
||||
return tx_ScaleFilter.init(out, mxx, myy, mxt, myt);
|
||||
}
|
||||
}
|
||||
} else if (mxt == 0f && myt == 0f) {
|
||||
return tx_DeltaTransformFilter.init(out, mxx, mxy, myx, myy);
|
||||
} else {
|
||||
return tx_TransformFilter.init(out, mxx, mxy, mxt, myx, myy, myt);
|
||||
}
|
||||
}
|
||||
|
||||
// recycled PathConsumer2D instances from deltaTransformConsumer()
|
||||
private final DeltaScaleFilter dt_DeltaScaleFilter = new DeltaScaleFilter();
|
||||
private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter();
|
||||
@ -97,6 +57,7 @@ final class TransformingPathConsumer2D {
|
||||
float mxy = (float) at.getShearX();
|
||||
float myx = (float) at.getShearY();
|
||||
float myy = (float) at.getScaleY();
|
||||
|
||||
if (mxy == 0f && myx == 0f) {
|
||||
if (mxx == 1f && myy == 1f) {
|
||||
return out;
|
||||
@ -122,6 +83,7 @@ final class TransformingPathConsumer2D {
|
||||
float mxy = (float) at.getShearX();
|
||||
float myx = (float) at.getShearY();
|
||||
float myy = (float) at.getScaleY();
|
||||
|
||||
if (mxy == 0f && myx == 0f) {
|
||||
if (mxx == 1f && myy == 1f) {
|
||||
return out;
|
||||
@ -138,197 +100,6 @@ final class TransformingPathConsumer2D {
|
||||
}
|
||||
}
|
||||
|
||||
static final class TranslateFilter implements PathConsumer2D {
|
||||
private PathConsumer2D out;
|
||||
private float tx, ty;
|
||||
|
||||
TranslateFilter() {}
|
||||
|
||||
TranslateFilter init(PathConsumer2D out,
|
||||
float tx, float ty)
|
||||
{
|
||||
this.out = out;
|
||||
this.tx = tx;
|
||||
this.ty = ty;
|
||||
return this; // fluent API
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveTo(float x0, float y0) {
|
||||
out.moveTo(x0 + tx, y0 + ty);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lineTo(float x1, float y1) {
|
||||
out.lineTo(x1 + tx, y1 + ty);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void quadTo(float x1, float y1,
|
||||
float x2, float y2)
|
||||
{
|
||||
out.quadTo(x1 + tx, y1 + ty,
|
||||
x2 + tx, y2 + ty);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void curveTo(float x1, float y1,
|
||||
float x2, float y2,
|
||||
float x3, float y3)
|
||||
{
|
||||
out.curveTo(x1 + tx, y1 + ty,
|
||||
x2 + tx, y2 + ty,
|
||||
x3 + tx, y3 + ty);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closePath() {
|
||||
out.closePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pathDone() {
|
||||
out.pathDone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNativeConsumer() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static final class ScaleFilter implements PathConsumer2D {
|
||||
private PathConsumer2D out;
|
||||
private float sx, sy, tx, ty;
|
||||
|
||||
ScaleFilter() {}
|
||||
|
||||
ScaleFilter init(PathConsumer2D out,
|
||||
float sx, float sy,
|
||||
float tx, float ty)
|
||||
{
|
||||
this.out = out;
|
||||
this.sx = sx;
|
||||
this.sy = sy;
|
||||
this.tx = tx;
|
||||
this.ty = ty;
|
||||
return this; // fluent API
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveTo(float x0, float y0) {
|
||||
out.moveTo(x0 * sx + tx, y0 * sy + ty);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lineTo(float x1, float y1) {
|
||||
out.lineTo(x1 * sx + tx, y1 * sy + ty);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void quadTo(float x1, float y1,
|
||||
float x2, float y2)
|
||||
{
|
||||
out.quadTo(x1 * sx + tx, y1 * sy + ty,
|
||||
x2 * sx + tx, y2 * sy + ty);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void curveTo(float x1, float y1,
|
||||
float x2, float y2,
|
||||
float x3, float y3)
|
||||
{
|
||||
out.curveTo(x1 * sx + tx, y1 * sy + ty,
|
||||
x2 * sx + tx, y2 * sy + ty,
|
||||
x3 * sx + tx, y3 * sy + ty);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closePath() {
|
||||
out.closePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pathDone() {
|
||||
out.pathDone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNativeConsumer() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static final class TransformFilter implements PathConsumer2D {
|
||||
private PathConsumer2D out;
|
||||
private float mxx, mxy, mxt, myx, myy, myt;
|
||||
|
||||
TransformFilter() {}
|
||||
|
||||
TransformFilter init(PathConsumer2D out,
|
||||
float mxx, float mxy, float mxt,
|
||||
float myx, float myy, float myt)
|
||||
{
|
||||
this.out = out;
|
||||
this.mxx = mxx;
|
||||
this.mxy = mxy;
|
||||
this.mxt = mxt;
|
||||
this.myx = myx;
|
||||
this.myy = myy;
|
||||
this.myt = myt;
|
||||
return this; // fluent API
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveTo(float x0, float y0) {
|
||||
out.moveTo(x0 * mxx + y0 * mxy + mxt,
|
||||
x0 * myx + y0 * myy + myt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lineTo(float x1, float y1) {
|
||||
out.lineTo(x1 * mxx + y1 * mxy + mxt,
|
||||
x1 * myx + y1 * myy + myt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void quadTo(float x1, float y1,
|
||||
float x2, float y2)
|
||||
{
|
||||
out.quadTo(x1 * mxx + y1 * mxy + mxt,
|
||||
x1 * myx + y1 * myy + myt,
|
||||
x2 * mxx + y2 * mxy + mxt,
|
||||
x2 * myx + y2 * myy + myt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void curveTo(float x1, float y1,
|
||||
float x2, float y2,
|
||||
float x3, float y3)
|
||||
{
|
||||
out.curveTo(x1 * mxx + y1 * mxy + mxt,
|
||||
x1 * myx + y1 * myy + myt,
|
||||
x2 * mxx + y2 * mxy + mxt,
|
||||
x2 * myx + y2 * myy + myt,
|
||||
x3 * mxx + y3 * mxy + mxt,
|
||||
x3 * myx + y3 * myy + myt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closePath() {
|
||||
out.closePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pathDone() {
|
||||
out.pathDone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNativeConsumer() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static final class DeltaScaleFilter implements PathConsumer2D {
|
||||
private PathConsumer2D out;
|
||||
|
||||
@ -27,7 +27,7 @@ package sun.java2d.marlin;
|
||||
|
||||
public final class Version {
|
||||
|
||||
private static final String version = "marlin-0.7.3.2-Unsafe-OpenJDK";
|
||||
private static final String version = "marlin-0.7.3.3-Unsafe-OpenJDK";
|
||||
|
||||
public static String getVersion() {
|
||||
return version;
|
||||
|
||||
@ -28,7 +28,6 @@ import java.awt.BasicStroke;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import sun.awt.SunHints;
|
||||
import sun.java2d.ReentrantContext;
|
||||
import sun.java2d.ReentrantContextProvider;
|
||||
|
||||
@ -21,14 +21,22 @@
|
||||
* 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.Path2D;
|
||||
import java.awt.geom.PathIterator;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.Raster;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import static java.lang.Double.NEGATIVE_INFINITY;
|
||||
import static java.lang.Double.POSITIVE_INFINITY;
|
||||
import static java.lang.Double.NaN;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.LogRecord;
|
||||
@ -37,8 +45,9 @@ import javax.imageio.ImageIO;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8149338
|
||||
* @summary Verifies that Marlin supports NaN coordinates and no JVM crash happens !
|
||||
* @bug 8149338 8144938
|
||||
* @summary Verifies that Marlin supports NaN coordinates (no JVM crash)
|
||||
* but also it skips properly point coordinates with NaN / Infinity values
|
||||
* @run main CrashNaNTest
|
||||
*/
|
||||
public class CrashNaNTest {
|
||||
@ -77,23 +86,31 @@ public class CrashNaNTest {
|
||||
System.setProperty("sun.java2d.renderer.useLogger", "true");
|
||||
System.setProperty("sun.java2d.renderer.doChecks", "true");
|
||||
|
||||
testFillDefaultAt();
|
||||
testDrawComplexAt();
|
||||
|
||||
testPathClosed();
|
||||
|
||||
testStrokedShapes();
|
||||
}
|
||||
|
||||
private static void testFillDefaultAt() {
|
||||
final int width = 400;
|
||||
final int height = 400;
|
||||
|
||||
final BufferedImage image = new BufferedImage(width, height,
|
||||
BufferedImage.TYPE_INT_ARGB);
|
||||
BufferedImage.TYPE_INT_ARGB);
|
||||
|
||||
final Graphics2D g2d = (Graphics2D) image.getGraphics();
|
||||
try {
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
|
||||
RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
g2d.setBackground(Color.WHITE);
|
||||
g2d.clearRect(0, 0, width, height);
|
||||
|
||||
final Path2D.Double path = new Path2D.Double();
|
||||
path.moveTo(30, 30);
|
||||
path.lineTo(100, 100);
|
||||
path.moveTo(100, 100);
|
||||
|
||||
for (int i = 0; i < 20000; i++) {
|
||||
path.lineTo(110 + 0.01 * i, 110);
|
||||
@ -105,39 +122,301 @@ public class CrashNaNTest {
|
||||
path.lineTo(200, NaN);
|
||||
path.lineTo(300, 300);
|
||||
path.lineTo(NaN, NaN);
|
||||
path.lineTo(100, 100);
|
||||
path.lineTo(100, 200);
|
||||
path.closePath();
|
||||
|
||||
final Path2D.Double path2 = new Path2D.Double();
|
||||
path2.moveTo(0,0);
|
||||
path2.lineTo(width,height);
|
||||
path2.lineTo(10, 10);
|
||||
path2.moveTo(0, 0);
|
||||
path2.lineTo(100, height);
|
||||
path2.lineTo(0, 200);
|
||||
path2.closePath();
|
||||
|
||||
for (int i = 0; i < 1; i++) {
|
||||
final long start = System.nanoTime();
|
||||
g2d.setColor(Color.BLUE);
|
||||
g2d.fill(path);
|
||||
g2d.setColor(Color.BLUE);
|
||||
g2d.fill(path);
|
||||
g2d.setColor(Color.GREEN);
|
||||
g2d.fill(path2);
|
||||
|
||||
g2d.fill(path2);
|
||||
|
||||
final long time = System.nanoTime() - start;
|
||||
System.out.println("paint: duration= " + (1e-6 * time) + " ms.");
|
||||
}
|
||||
g2d.setColor(Color.BLACK);
|
||||
g2d.draw(path);
|
||||
g2d.draw(path2);
|
||||
|
||||
if (SAVE_IMAGE) {
|
||||
try {
|
||||
final File file = new File("CrashNaNTest.png");
|
||||
final File file = new File("CrashNaNTest-fill.png");
|
||||
System.out.println("Writing file: "
|
||||
+ file.getAbsolutePath());
|
||||
+ file.getAbsolutePath());
|
||||
ImageIO.write(image, "PNG", file);
|
||||
} catch (IOException ex) {
|
||||
System.out.println("Writing file failure:");
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// Check image on few pixels:
|
||||
final Raster raster = image.getData();
|
||||
|
||||
checkPixel(raster, 200, 195, Color.BLUE.getRGB());
|
||||
checkPixel(raster, 105, 195, Color.BLUE.getRGB());
|
||||
checkPixel(raster, 286, 290, Color.BLUE.getRGB());
|
||||
|
||||
checkPixel(raster, 108, 105, Color.WHITE.getRGB());
|
||||
checkPixel(raster, 205, 200, Color.WHITE.getRGB());
|
||||
|
||||
checkPixel(raster, 5, 200, Color.GREEN.getRGB());
|
||||
|
||||
} finally {
|
||||
g2d.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static void testDrawComplexAt() {
|
||||
final int width = 400;
|
||||
final int height = 400;
|
||||
|
||||
final BufferedImage image = new BufferedImage(width, height,
|
||||
BufferedImage.TYPE_INT_ARGB);
|
||||
|
||||
final Graphics2D g2d = (Graphics2D) image.getGraphics();
|
||||
try {
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
|
||||
RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
|
||||
RenderingHints.VALUE_STROKE_PURE);
|
||||
|
||||
g2d.setBackground(Color.WHITE);
|
||||
g2d.clearRect(0, 0, width, height);
|
||||
|
||||
final Path2D.Double path = new Path2D.Double();
|
||||
path.moveTo(100, 100);
|
||||
|
||||
for (int i = 0; i < 20000; i++) {
|
||||
path.lineTo(110 + 0.01 * i, 110);
|
||||
path.lineTo(111 + 0.01 * i, 100);
|
||||
}
|
||||
|
||||
path.lineTo(NaN, 200);
|
||||
path.lineTo(200, 200);
|
||||
path.lineTo(200, NaN);
|
||||
path.lineTo(300, 300);
|
||||
path.lineTo(NaN, NaN);
|
||||
path.lineTo(100, 200);
|
||||
path.closePath();
|
||||
|
||||
final Path2D.Double path2 = new Path2D.Double();
|
||||
path2.moveTo(0, 0);
|
||||
path2.lineTo(100, height);
|
||||
path2.lineTo(0, 200);
|
||||
path2.closePath();
|
||||
|
||||
// Define an non-uniform transform:
|
||||
g2d.scale(0.5, 1.0);
|
||||
g2d.rotate(Math.PI / 31);
|
||||
|
||||
g2d.setColor(Color.BLACK);
|
||||
g2d.draw(path);
|
||||
g2d.draw(path2);
|
||||
|
||||
if (SAVE_IMAGE) {
|
||||
try {
|
||||
final File file = new File("CrashNaNTest-draw.png");
|
||||
System.out.println("Writing file: "
|
||||
+ file.getAbsolutePath());
|
||||
ImageIO.write(image, "PNG", file);
|
||||
} catch (IOException ex) {
|
||||
System.out.println("Writing file failure:");
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// Check image on few pixels:
|
||||
final Raster raster = image.getData();
|
||||
|
||||
checkPixelNotWhite(raster, 40, 210);
|
||||
checkPixelNotWhite(raster, 44, 110);
|
||||
checkPixelNotWhite(raster, 60, 120);
|
||||
checkPixelNotWhite(raster, 89, 219);
|
||||
checkPixelNotWhite(raster, 28, 399);
|
||||
checkPixelNotWhite(raster, 134, 329);
|
||||
|
||||
} finally {
|
||||
g2d.dispose();
|
||||
}
|
||||
}
|
||||
private static void testPathClosed() {
|
||||
final int width = 100;
|
||||
final int height = 100;
|
||||
|
||||
final BufferedImage image = new BufferedImage(width, height,
|
||||
BufferedImage.TYPE_INT_ARGB);
|
||||
|
||||
final Graphics2D g2d = (Graphics2D) image.getGraphics();
|
||||
try {
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
|
||||
RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
g2d.setBackground(Color.WHITE);
|
||||
g2d.clearRect(0, 0, width, height);
|
||||
|
||||
final Path2D.Double path = new Path2D.Double();
|
||||
path.moveTo(40, 40);
|
||||
path.lineTo(0, 0);
|
||||
path.lineTo(80, 0);
|
||||
path.closePath();
|
||||
path.lineTo(80, 80);
|
||||
path.lineTo(0, 80);
|
||||
path.closePath();
|
||||
|
||||
g2d.setColor(Color.BLUE);
|
||||
g2d.fill(path);
|
||||
|
||||
g2d.setColor(Color.BLACK);
|
||||
g2d.draw(path);
|
||||
|
||||
if (SAVE_IMAGE) {
|
||||
try {
|
||||
final File file = new File("CrashNaNTest-path-closed.png");
|
||||
System.out.println("Writing file: "
|
||||
+ file.getAbsolutePath());
|
||||
ImageIO.write(image, "PNG", file);
|
||||
} catch (IOException ex) {
|
||||
System.out.println("Writing file failure:");
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// Check image on few pixels:
|
||||
final Raster raster = image.getData();
|
||||
|
||||
checkPixel(raster, 10, 05, Color.BLUE.getRGB());
|
||||
checkPixel(raster, 70, 05, Color.BLUE.getRGB());
|
||||
checkPixel(raster, 40, 35, Color.BLUE.getRGB());
|
||||
|
||||
checkPixel(raster, 10, 75, Color.BLUE.getRGB());
|
||||
checkPixel(raster, 70, 75, Color.BLUE.getRGB());
|
||||
checkPixel(raster, 40, 45, Color.BLUE.getRGB());
|
||||
|
||||
} finally {
|
||||
g2d.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static void testStrokedShapes() {
|
||||
final Stroke stroke = new BasicStroke();
|
||||
|
||||
final Path2D.Double path = new Path2D.Double();
|
||||
Shape s;
|
||||
|
||||
// Check filtering NaN values:
|
||||
path.reset();
|
||||
path.moveTo(100, NaN);
|
||||
path.lineTo(NaN, 100);
|
||||
path.lineTo(NaN, NaN);
|
||||
|
||||
path.quadTo(NaN, 100, NaN, 100);
|
||||
path.quadTo(100, NaN, 100, NaN);
|
||||
path.quadTo(NaN, NaN, NaN, NaN);
|
||||
|
||||
path.curveTo(NaN, 100, NaN, 100, NaN, 100);
|
||||
path.curveTo(100, NaN, 100, NaN, 100, NaN);
|
||||
path.curveTo(NaN, NaN, NaN, NaN, NaN, NaN);
|
||||
path.closePath();
|
||||
|
||||
s = stroke.createStrokedShape(path);
|
||||
checkEmptyPath(s);
|
||||
|
||||
// Check filtering +Infinity values:
|
||||
path.reset();
|
||||
path.moveTo(100, POSITIVE_INFINITY);
|
||||
path.lineTo(POSITIVE_INFINITY, 100);
|
||||
path.lineTo(POSITIVE_INFINITY, POSITIVE_INFINITY);
|
||||
|
||||
path.quadTo(POSITIVE_INFINITY, 100,
|
||||
POSITIVE_INFINITY, 100);
|
||||
path.quadTo(100, POSITIVE_INFINITY,
|
||||
100, POSITIVE_INFINITY);
|
||||
path.quadTo(POSITIVE_INFINITY, POSITIVE_INFINITY,
|
||||
POSITIVE_INFINITY, POSITIVE_INFINITY);
|
||||
|
||||
path.curveTo(POSITIVE_INFINITY, 100,
|
||||
POSITIVE_INFINITY, 100,
|
||||
POSITIVE_INFINITY, 100);
|
||||
path.curveTo(100, POSITIVE_INFINITY,
|
||||
100, POSITIVE_INFINITY,
|
||||
100, POSITIVE_INFINITY);
|
||||
path.curveTo(POSITIVE_INFINITY, POSITIVE_INFINITY,
|
||||
POSITIVE_INFINITY, POSITIVE_INFINITY,
|
||||
POSITIVE_INFINITY, POSITIVE_INFINITY);
|
||||
path.closePath();
|
||||
|
||||
s = stroke.createStrokedShape(path);
|
||||
checkEmptyPath(s);
|
||||
|
||||
// Check filtering -Infinity values:
|
||||
path.reset();
|
||||
path.moveTo(100, NEGATIVE_INFINITY);
|
||||
path.lineTo(NEGATIVE_INFINITY, 100);
|
||||
path.lineTo(NEGATIVE_INFINITY, NEGATIVE_INFINITY);
|
||||
|
||||
path.quadTo(NEGATIVE_INFINITY, 100,
|
||||
NEGATIVE_INFINITY, 100);
|
||||
path.quadTo(100, NEGATIVE_INFINITY,
|
||||
100, NEGATIVE_INFINITY);
|
||||
path.quadTo(NEGATIVE_INFINITY, NEGATIVE_INFINITY,
|
||||
NEGATIVE_INFINITY, NEGATIVE_INFINITY);
|
||||
|
||||
path.curveTo(NEGATIVE_INFINITY, 100,
|
||||
NEGATIVE_INFINITY, 100,
|
||||
NEGATIVE_INFINITY, 100);
|
||||
path.curveTo(100, NEGATIVE_INFINITY,
|
||||
100, NEGATIVE_INFINITY,
|
||||
100, NEGATIVE_INFINITY);
|
||||
path.curveTo(NEGATIVE_INFINITY, NEGATIVE_INFINITY,
|
||||
NEGATIVE_INFINITY, NEGATIVE_INFINITY,
|
||||
NEGATIVE_INFINITY, NEGATIVE_INFINITY);
|
||||
path.closePath();
|
||||
|
||||
s = stroke.createStrokedShape(path);
|
||||
checkEmptyPath(s);
|
||||
}
|
||||
|
||||
private static void checkEmptyPath(final Shape s) {
|
||||
final float[] coords = new float[6];
|
||||
final PathIterator it = s.getPathIterator(null);
|
||||
|
||||
int n = 0;
|
||||
for (; !it.isDone(); it.next()) {
|
||||
int type = it.currentSegment(coords);
|
||||
System.out.println("unexpected segment type= " + type
|
||||
+ " with coords: " + Arrays.toString(coords));
|
||||
n++;
|
||||
}
|
||||
if (n != 0) {
|
||||
System.out.println("path elements = " + n);
|
||||
throw new IllegalStateException("Not empty path: "
|
||||
+ n + " path elements !");
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkPixel(final Raster raster,
|
||||
final int x, final int y,
|
||||
final int expected) {
|
||||
|
||||
final int[] rgb = (int[]) raster.getDataElements(x, y, null);
|
||||
|
||||
if (rgb[0] != expected) {
|
||||
throw new IllegalStateException("bad pixel at (" + x + ", " + y
|
||||
+ ") = " + rgb[0] + " expected: " + expected);
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkPixelNotWhite(final Raster raster,
|
||||
final int x, final int y) {
|
||||
|
||||
final int[] rgb = (int[]) raster.getDataElements(x, y, null);
|
||||
|
||||
if (rgb[0] == Color.WHITE.getRGB()) {
|
||||
throw new IllegalStateException("bad pixel at (" + x + ", " + y
|
||||
+ ") is white (empty)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user