1 /*
   2  * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import java.awt.BasicStroke;
  25 import java.awt.Color;
  26 import java.awt.Graphics2D;
  27 import java.awt.RenderingHints;
  28 import java.awt.Shape;
  29 import java.awt.Stroke;
  30 import java.awt.geom.Path2D;
  31 import java.awt.geom.PathIterator;
  32 import java.awt.image.BufferedImage;
  33 import java.awt.image.Raster;
  34 import java.io.File;
  35 import java.io.IOException;
  36 import static java.lang.Double.NEGATIVE_INFINITY;
  37 import static java.lang.Double.POSITIVE_INFINITY;
  38 import static java.lang.Double.NaN;
  39 import java.util.Arrays;
  40 import java.util.Locale;
  41 import java.util.logging.Handler;
  42 import java.util.logging.LogRecord;
  43 import java.util.logging.Logger;
  44 import javax.imageio.ImageIO;
  45 
  46 /**
  47  * @test
  48  * @bug 8149338 8144938
  49  * @summary Verifies that Marlin supports NaN coordinates (no JVM crash)
  50  * but also it skips properly point coordinates with NaN / Infinity values
  51  * @run main CrashNaNTest
  52  */
  53 public class CrashNaNTest {
  54 
  55     static final boolean SAVE_IMAGE = false;
  56 
  57     public static void main(String argv[]) {
  58         Locale.setDefault(Locale.US);
  59 
  60         // initialize j.u.l Looger:
  61         final Logger log = Logger.getLogger("sun.java2d.marlin");
  62         log.addHandler(new Handler() {
  63             @Override
  64             public void publish(LogRecord record) {
  65                 Throwable th = record.getThrown();
  66                 // detect any Throwable:
  67                 if (th != null) {
  68                     System.out.println("Test failed:\n" + record.getMessage());
  69                     th.printStackTrace(System.out);
  70 
  71                     throw new RuntimeException("Test failed: ", th);
  72                 }
  73             }
  74 
  75             @Override
  76             public void flush() {
  77             }
  78 
  79             @Override
  80             public void close() throws SecurityException {
  81             }
  82         });
  83 
  84         // enable Marlin logging & internal checks:
  85         System.setProperty("sun.java2d.renderer.log", "true");
  86         System.setProperty("sun.java2d.renderer.useLogger", "true");
  87         System.setProperty("sun.java2d.renderer.doChecks", "true");
  88 
  89         testFillDefaultAt();
  90         testDrawComplexAt();
  91 
  92         testStrokedShapes();
  93     }
  94 
  95     private static void testFillDefaultAt() {
  96         final int width = 400;
  97         final int height = 400;
  98 
  99         final BufferedImage image = new BufferedImage(width, height,
 100                                             BufferedImage.TYPE_INT_ARGB);
 101 
 102         final Graphics2D g2d = (Graphics2D) image.getGraphics();
 103         try {
 104             g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
 105                                  RenderingHints.VALUE_ANTIALIAS_ON);
 106 
 107             g2d.setBackground(Color.WHITE);
 108             g2d.clearRect(0, 0, width, height);
 109 
 110             final Path2D.Double path = new Path2D.Double();
 111             path.moveTo(100, 100);
 112 
 113             for (int i = 0; i < 20000; i++) {
 114                 path.lineTo(110 + 0.01 * i, 110);
 115                 path.lineTo(111 + 0.01 * i, 100);
 116             }
 117 
 118             path.lineTo(NaN, 200);
 119             path.lineTo(200, 200);
 120             path.lineTo(200, NaN);
 121             path.lineTo(300, 300);
 122             path.lineTo(NaN, NaN);
 123             path.lineTo(100, 200);
 124             path.closePath();
 125 
 126             final Path2D.Double path2 = new Path2D.Double();
 127             path2.moveTo(0, 0);
 128             path2.lineTo(100, height);
 129             path2.lineTo(0, 200);
 130             path2.closePath();
 131 
 132             for (int i = 0; i < 1; i++) {
 133                 final long start = System.nanoTime();
 134                 g2d.setColor(Color.BLUE);
 135                 g2d.fill(path);
 136                 g2d.setColor(Color.GREEN);
 137                 g2d.fill(path2);
 138 
 139                 g2d.setColor(Color.BLACK);
 140                 g2d.draw(path);
 141                 g2d.draw(path2);
 142 
 143                 final long time = System.nanoTime() - start;
 144                 System.out.println("paint: duration= " + (1e-6 * time) + " ms.");
 145             }
 146 
 147             if (SAVE_IMAGE) {
 148                 try {
 149                     final File file = new File("CrashNaNTest-fill.png");
 150                     System.out.println("Writing file: "
 151                                        + file.getAbsolutePath());
 152                     ImageIO.write(image, "PNG", file);
 153                 } catch (IOException ex) {
 154                     System.out.println("Writing file failure:");
 155                     ex.printStackTrace();
 156                 }
 157             }
 158 
 159             // Check image on few pixels:
 160             final Raster raster = image.getData();
 161 
 162             checkPixel(raster, 200, 195, Color.BLUE.getRGB());
 163             checkPixel(raster, 105, 195, Color.BLUE.getRGB());
 164             checkPixel(raster, 286, 290, Color.BLUE.getRGB());
 165 
 166             checkPixel(raster, 108, 105, Color.WHITE.getRGB());
 167             checkPixel(raster, 205, 200, Color.WHITE.getRGB());
 168 
 169             checkPixel(raster, 5, 200, Color.GREEN.getRGB());
 170 
 171         } finally {
 172             g2d.dispose();
 173         }
 174     }
 175 
 176     private static void testDrawComplexAt() {
 177         final int width = 400;
 178         final int height = 400;
 179 
 180         final BufferedImage image = new BufferedImage(width, height,
 181                                             BufferedImage.TYPE_INT_ARGB);
 182 
 183         final Graphics2D g2d = (Graphics2D) image.getGraphics();
 184         try {
 185             g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
 186                                  RenderingHints.VALUE_ANTIALIAS_ON);
 187             g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
 188                                  RenderingHints.VALUE_STROKE_PURE);
 189 
 190             g2d.setBackground(Color.WHITE);
 191             g2d.clearRect(0, 0, width, height);
 192 
 193             final Path2D.Double path = new Path2D.Double();
 194             path.moveTo(100, 100);
 195 
 196             for (int i = 0; i < 20000; i++) {
 197                 path.lineTo(110 + 0.01 * i, 110);
 198                 path.lineTo(111 + 0.01 * i, 100);
 199             }
 200 
 201             path.lineTo(NaN, 200);
 202             path.lineTo(200, 200);
 203             path.lineTo(200, NaN);
 204             path.lineTo(300, 300);
 205             path.lineTo(NaN, NaN);
 206             path.lineTo(100, 200);
 207             path.closePath();
 208 
 209             final Path2D.Double path2 = new Path2D.Double();
 210             path2.moveTo(0, 0);
 211             path2.lineTo(100, height);
 212             path2.lineTo(0, 200);
 213             path2.closePath();
 214 
 215             // Define an non-uniform transform:
 216             g2d.scale(0.5, 1.0);
 217             g2d.rotate(Math.PI / 31);
 218 
 219             for (int i = 0; i < 1; i++) {
 220                 final long start = System.nanoTime();
 221 
 222                 g2d.setColor(Color.BLACK);
 223                 g2d.draw(path);
 224                 g2d.draw(path2);
 225 
 226                 final long time = System.nanoTime() - start;
 227                 System.out.println("paint: duration= " + (1e-6 * time) + " ms.");
 228             }
 229 
 230             if (SAVE_IMAGE) {
 231                 try {
 232                     final File file = new File("CrashNaNTest-draw.png");
 233                     System.out.println("Writing file: "
 234                                        + file.getAbsolutePath());
 235                     ImageIO.write(image, "PNG", file);
 236                 } catch (IOException ex) {
 237                     System.out.println("Writing file failure:");
 238                     ex.printStackTrace();
 239                 }
 240             }
 241 
 242             // Check image on few pixels:
 243             final Raster raster = image.getData();
 244 
 245             checkPixelNotWhite(raster, 40, 210);
 246             checkPixelNotWhite(raster, 44, 110);
 247             checkPixelNotWhite(raster, 60, 120);
 248             checkPixelNotWhite(raster, 89, 219);
 249             checkPixelNotWhite(raster, 28, 399);
 250             checkPixelNotWhite(raster, 134, 329);
 251 
 252         } finally {
 253             g2d.dispose();
 254         }
 255     }
 256 
 257     private static void testStrokedShapes() {
 258         final Stroke stroke = new BasicStroke();
 259 
 260         final Path2D.Double path = new Path2D.Double();
 261         Shape s;
 262 
 263         // Check filtering NaN values:
 264         path.reset();
 265         path.moveTo(100, NaN);
 266         path.lineTo(NaN, 100);
 267         path.lineTo(NaN, NaN);
 268 
 269         path.quadTo(NaN, 100, NaN, 100);
 270         path.quadTo(100, NaN, 100, NaN);
 271         path.quadTo(NaN, NaN, NaN, NaN);
 272 
 273         path.curveTo(NaN, 100, NaN, 100, NaN, 100);
 274         path.curveTo(100, NaN, 100, NaN, 100, NaN);
 275         path.curveTo(NaN, NaN, NaN, NaN, NaN, NaN);
 276         path.closePath();
 277 
 278         s = stroke.createStrokedShape(path);
 279         checkEmptyPath(s);
 280 
 281         // Check filtering +Infinity values:
 282         path.reset();
 283         path.moveTo(100, POSITIVE_INFINITY);
 284         path.lineTo(POSITIVE_INFINITY, 100);
 285         path.lineTo(POSITIVE_INFINITY, POSITIVE_INFINITY);
 286 
 287         path.quadTo(POSITIVE_INFINITY, 100,
 288                     POSITIVE_INFINITY, 100);
 289         path.quadTo(100, POSITIVE_INFINITY,
 290                     100, POSITIVE_INFINITY);
 291         path.quadTo(POSITIVE_INFINITY, POSITIVE_INFINITY,
 292                     POSITIVE_INFINITY, POSITIVE_INFINITY);
 293 
 294         path.curveTo(POSITIVE_INFINITY, 100,
 295                      POSITIVE_INFINITY, 100,
 296                      POSITIVE_INFINITY, 100);
 297         path.curveTo(100, POSITIVE_INFINITY,
 298                      100, POSITIVE_INFINITY,
 299                      100, POSITIVE_INFINITY);
 300         path.curveTo(POSITIVE_INFINITY, POSITIVE_INFINITY,
 301                      POSITIVE_INFINITY, POSITIVE_INFINITY,
 302                      POSITIVE_INFINITY, POSITIVE_INFINITY);
 303         path.closePath();
 304 
 305         s = stroke.createStrokedShape(path);
 306         checkEmptyPath(s);
 307 
 308         // Check filtering -Infinity values:
 309         path.reset();
 310         path.moveTo(100, NEGATIVE_INFINITY);
 311         path.lineTo(NEGATIVE_INFINITY, 100);
 312         path.lineTo(NEGATIVE_INFINITY, NEGATIVE_INFINITY);
 313 
 314         path.quadTo(NEGATIVE_INFINITY, 100,
 315                     NEGATIVE_INFINITY, 100);
 316         path.quadTo(100, NEGATIVE_INFINITY,
 317                     100, NEGATIVE_INFINITY);
 318         path.quadTo(NEGATIVE_INFINITY, NEGATIVE_INFINITY,
 319                     NEGATIVE_INFINITY, NEGATIVE_INFINITY);
 320 
 321         path.curveTo(NEGATIVE_INFINITY, 100,
 322                      NEGATIVE_INFINITY, 100,
 323                      NEGATIVE_INFINITY, 100);
 324         path.curveTo(100, NEGATIVE_INFINITY,
 325                      100, NEGATIVE_INFINITY,
 326                      100, NEGATIVE_INFINITY);
 327         path.curveTo(NEGATIVE_INFINITY, NEGATIVE_INFINITY,
 328                      NEGATIVE_INFINITY, NEGATIVE_INFINITY,
 329                      NEGATIVE_INFINITY, NEGATIVE_INFINITY);
 330         path.closePath();
 331 
 332         s = stroke.createStrokedShape(path);
 333         checkEmptyPath(s);
 334     }
 335 
 336     private static void checkEmptyPath(final Shape s) {
 337         final float[] coords = new float[6];
 338         final PathIterator it = s.getPathIterator(null);
 339 
 340         int n = 0;
 341         for (; !it.isDone(); it.next()) {
 342             int type = it.currentSegment(coords);
 343             System.out.println("unexpected segment type= " + type
 344                  + " with coords: " + Arrays.toString(coords));
 345             n++;
 346         }
 347         if (n != 0) {
 348             System.out.println("path elements = " + n);
 349             throw new IllegalStateException("Not empty path: "
 350                           + n + " path elements !");
 351         }
 352     }
 353 
 354     private static void checkPixel(final Raster raster,
 355                                    final int x, final int y,
 356                                    final int expected) {
 357 
 358         final int[] rgb = (int[]) raster.getDataElements(x, y, null);
 359 
 360         if (rgb[0] != expected) {
 361             throw new IllegalStateException("bad pixel at (" + x + ", " + y
 362                           + ") = " + rgb[0] + " expected: " + expected);
 363         }
 364     }
 365 
 366     private static void checkPixelNotWhite(final Raster raster,
 367                                            final int x, final int y) {
 368 
 369         final int[] rgb = (int[]) raster.getDataElements(x, y, null);
 370 
 371         if (rgb[0] == Color.WHITE.getRGB()) {
 372             throw new IllegalStateException("bad pixel at (" + x + ", " + y
 373                           + ") is white (empty)");
 374         }
 375     }
 376 }