8144938: Handle properly coordinate overflow in Marlin Renderer

Skip point coordinates with NaN/Infinity values

Reviewed-by: flar, prr
This commit is contained in:
Laurent Bourgès 2016-03-23 21:20:25 +01:00
parent d8f82c9477
commit 048ce8be87
6 changed files with 439 additions and 327 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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