--- old/src/java.desktop/share/classes/sun/java2d/marlin/MarlinRenderingEngine.java 2016-03-18 17:45:10.166995873 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/MarlinRenderingEngine.java 2016-03-18 17:45:10.038995868 +0100 @@ -51,6 +51,9 @@ 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 @@ 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 @@ // 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 @@ // 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 @@ 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)); - - // 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. - } + strokerat = at; + + // 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 @@ 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 @@ 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 @@ 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(); --- old/src/java.desktop/share/classes/sun/java2d/marlin/TransformingPathConsumer2D.java 2016-03-18 17:45:10.554995887 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/TransformingPathConsumer2D.java 2016-03-18 17:45:10.426995882 +0100 @@ -35,7 +35,7 @@ // 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 @@ 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 @@ 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 @@ 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 @@ } } - 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; --- old/src/java.desktop/share/classes/sun/java2d/marlin/Version.java 2016-03-18 17:45:10.978995903 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/marlin/Version.java 2016-03-18 17:45:10.850995898 +0100 @@ -27,7 +27,7 @@ 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; --- old/src/java.desktop/share/classes/sun/java2d/pipe/AAShapePipe.java 2016-03-18 17:45:11.398995919 +0100 +++ new/src/java.desktop/share/classes/sun/java2d/pipe/AAShapePipe.java 2016-03-18 17:45:11.266995914 +0100 @@ -28,7 +28,6 @@ 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; --- old/test/sun/java2d/marlin/CrashNaNTest.java 2016-03-18 17:45:11.778995933 +0100 +++ new/test/sun/java2d/marlin/CrashNaNTest.java 2016-03-18 17:45:11.638995927 +0100 @@ -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 @@ /** * @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 @@ 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 @@ 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.setColor(Color.BLACK); + g2d.draw(path); + g2d.draw(path2); + + if (SAVE_IMAGE) { + try { + final File file = new File("CrashNaNTest-fill.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, 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(); - g2d.fill(path2); + // 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); - final long time = System.nanoTime() - start; - System.out.println("paint: duration= " + (1e-6 * time) + " ms."); + 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.png"); + final File file = new File("CrashNaNTest-path-closed.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, 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)"); + } + } }