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         testPathClosed();
  93 
  94         testStrokedShapes();
  95     }
  96 
  97     private static void testFillDefaultAt() {
  98         final int width = 400;
  99         final int height = 400;
 100 
 101         final BufferedImage image = new BufferedImage(width, height,
 102                                             BufferedImage.TYPE_INT_ARGB);
 103 
 104         final Graphics2D g2d = (Graphics2D) image.getGraphics();
 105         try {
 106             g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
 107                                  RenderingHints.VALUE_ANTIALIAS_ON);
 108 
 109             g2d.setBackground(Color.WHITE);
 110             g2d.clearRect(0, 0, width, height);
 111 
 112             final Path2D.Double path = new Path2D.Double();
 113             path.moveTo(100, 100);
 114 
 115             for (int i = 0; i < 20000; i++) {
 116                 path.lineTo(110 + 0.01 * i, 110);
 117                 path.lineTo(111 + 0.01 * i, 100);
 118             }
 119 
 120             path.lineTo(NaN, 200);
 121             path.lineTo(200, 200);
 122             path.lineTo(200, NaN);
 123             path.lineTo(300, 300);
 124             path.lineTo(NaN, NaN);
 125             path.lineTo(100, 200);
 126             path.closePath();
 127 
 128             final Path2D.Double path2 = new Path2D.Double();
 129             path2.moveTo(0, 0);
 130             path2.lineTo(100, height);
 131             path2.lineTo(0, 200);
 132             path2.closePath();
 133 
 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             if (SAVE_IMAGE) {
 144                 try {
 145                     final File file = new File("CrashNaNTest-fill.png");
 146                     System.out.println("Writing file: "
 147                                        + file.getAbsolutePath());
 148                     ImageIO.write(image, "PNG", file);
 149                 } catch (IOException ex) {
 150                     System.out.println("Writing file failure:");
 151                     ex.printStackTrace();
 152                 }
 153             }
 154 
 155             // Check image on few pixels:
 156             final Raster raster = image.getData();
 157 
 158             checkPixel(raster, 200, 195, Color.BLUE.getRGB());
 159             checkPixel(raster, 105, 195, Color.BLUE.getRGB());
 160             checkPixel(raster, 286, 290, Color.BLUE.getRGB());
 161 
 162             checkPixel(raster, 108, 105, Color.WHITE.getRGB());
 163             checkPixel(raster, 205, 200, Color.WHITE.getRGB());
 164 
 165             checkPixel(raster, 5, 200, Color.GREEN.getRGB());
 166 
 167         } finally {
 168             g2d.dispose();
 169         }
 170     }
 171 
 172     private static void testDrawComplexAt() {
 173         final int width = 400;
 174         final int height = 400;
 175 
 176         final BufferedImage image = new BufferedImage(width, height,
 177                                             BufferedImage.TYPE_INT_ARGB);
 178 
 179         final Graphics2D g2d = (Graphics2D) image.getGraphics();
 180         try {
 181             g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
 182                                  RenderingHints.VALUE_ANTIALIAS_ON);
 183             g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
 184                                  RenderingHints.VALUE_STROKE_PURE);
 185 
 186             g2d.setBackground(Color.WHITE);
 187             g2d.clearRect(0, 0, width, height);
 188 
 189             final Path2D.Double path = new Path2D.Double();
 190             path.moveTo(100, 100);
 191 
 192             for (int i = 0; i < 20000; i++) {
 193                 path.lineTo(110 + 0.01 * i, 110);
 194                 path.lineTo(111 + 0.01 * i, 100);
 195             }
 196 
 197             path.lineTo(NaN, 200);
 198             path.lineTo(200, 200);
 199             path.lineTo(200, NaN);
 200             path.lineTo(300, 300);
 201             path.lineTo(NaN, NaN);
 202             path.lineTo(100, 200);
 203             path.closePath();
 204 
 205             final Path2D.Double path2 = new Path2D.Double();
 206             path2.moveTo(0, 0);
 207             path2.lineTo(100, height);
 208             path2.lineTo(0, 200);
 209             path2.closePath();
 210 
 211             // Define an non-uniform transform:
 212             g2d.scale(0.5, 1.0);
 213             g2d.rotate(Math.PI / 31);
 214 
 215             g2d.setColor(Color.BLACK);
 216             g2d.draw(path);
 217             g2d.draw(path2);
 218 
 219             if (SAVE_IMAGE) {
 220                 try {
 221                     final File file = new File("CrashNaNTest-draw.png");
 222                     System.out.println("Writing file: "
 223                                        + file.getAbsolutePath());
 224                     ImageIO.write(image, "PNG", file);
 225                 } catch (IOException ex) {
 226                     System.out.println("Writing file failure:");
 227                     ex.printStackTrace();
 228                 }
 229             }
 230 
 231             // Check image on few pixels:
 232             final Raster raster = image.getData();
 233 
 234             checkPixelNotWhite(raster, 40, 210);
 235             checkPixelNotWhite(raster, 44, 110);
 236             checkPixelNotWhite(raster, 60, 120);
 237             checkPixelNotWhite(raster, 89, 219);
 238             checkPixelNotWhite(raster, 28, 399);
 239             checkPixelNotWhite(raster, 134, 329);
 240 
 241         } finally {
 242             g2d.dispose();
 243         }
 244     }
 245     private static void testPathClosed() {
 246         final int width = 100;
 247         final int height = 100;
 248 
 249         final BufferedImage image = new BufferedImage(width, height,
 250                                             BufferedImage.TYPE_INT_ARGB);
 251 
 252         final Graphics2D g2d = (Graphics2D) image.getGraphics();
 253         try {
 254             g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
 255                                  RenderingHints.VALUE_ANTIALIAS_ON);
 256 
 257             g2d.setBackground(Color.WHITE);
 258             g2d.clearRect(0, 0, width, height);
 259 
 260             final Path2D.Double path = new Path2D.Double();
 261             path.moveTo(40, 40);
 262             path.lineTo(0,   0);
 263             path.lineTo(80, 0);
 264             path.closePath();
 265             path.lineTo(80, 80);
 266             path.lineTo(0, 80);
 267             path.closePath();
 268 
 269             g2d.setColor(Color.BLUE);
 270             g2d.fill(path);
 271 
 272             g2d.setColor(Color.BLACK);
 273             g2d.draw(path);
 274 
 275             if (SAVE_IMAGE) {
 276                 try {
 277                     final File file = new File("CrashNaNTest-path-closed.png");
 278                     System.out.println("Writing file: "
 279                                        + file.getAbsolutePath());
 280                     ImageIO.write(image, "PNG", file);
 281                 } catch (IOException ex) {
 282                     System.out.println("Writing file failure:");
 283                     ex.printStackTrace();
 284                 }
 285             }
 286 
 287             // Check image on few pixels:
 288             final Raster raster = image.getData();
 289 
 290             checkPixel(raster, 10, 05, Color.BLUE.getRGB());
 291             checkPixel(raster, 70, 05, Color.BLUE.getRGB());
 292             checkPixel(raster, 40, 35, Color.BLUE.getRGB());
 293 
 294             checkPixel(raster, 10, 75, Color.BLUE.getRGB());
 295             checkPixel(raster, 70, 75, Color.BLUE.getRGB());
 296             checkPixel(raster, 40, 45, Color.BLUE.getRGB());
 297 
 298         } finally {
 299             g2d.dispose();
 300         }
 301     }
 302 
 303     private static void testStrokedShapes() {
 304         final Stroke stroke = new BasicStroke();
 305 
 306         final Path2D.Double path = new Path2D.Double();
 307         Shape s;
 308 
 309         // Check filtering NaN values:
 310         path.reset();
 311         path.moveTo(100, NaN);
 312         path.lineTo(NaN, 100);
 313         path.lineTo(NaN, NaN);
 314 
 315         path.quadTo(NaN, 100, NaN, 100);
 316         path.quadTo(100, NaN, 100, NaN);
 317         path.quadTo(NaN, NaN, NaN, NaN);
 318 
 319         path.curveTo(NaN, 100, NaN, 100, NaN, 100);
 320         path.curveTo(100, NaN, 100, NaN, 100, NaN);
 321         path.curveTo(NaN, NaN, NaN, NaN, NaN, NaN);
 322         path.closePath();
 323 
 324         s = stroke.createStrokedShape(path);
 325         checkEmptyPath(s);
 326 
 327         // Check filtering +Infinity values:
 328         path.reset();
 329         path.moveTo(100, POSITIVE_INFINITY);
 330         path.lineTo(POSITIVE_INFINITY, 100);
 331         path.lineTo(POSITIVE_INFINITY, POSITIVE_INFINITY);
 332 
 333         path.quadTo(POSITIVE_INFINITY, 100,
 334                     POSITIVE_INFINITY, 100);
 335         path.quadTo(100, POSITIVE_INFINITY,
 336                     100, POSITIVE_INFINITY);
 337         path.quadTo(POSITIVE_INFINITY, POSITIVE_INFINITY,
 338                     POSITIVE_INFINITY, POSITIVE_INFINITY);
 339 
 340         path.curveTo(POSITIVE_INFINITY, 100,
 341                      POSITIVE_INFINITY, 100,
 342                      POSITIVE_INFINITY, 100);
 343         path.curveTo(100, POSITIVE_INFINITY,
 344                      100, POSITIVE_INFINITY,
 345                      100, POSITIVE_INFINITY);
 346         path.curveTo(POSITIVE_INFINITY, POSITIVE_INFINITY,
 347                      POSITIVE_INFINITY, POSITIVE_INFINITY,
 348                      POSITIVE_INFINITY, POSITIVE_INFINITY);
 349         path.closePath();
 350 
 351         s = stroke.createStrokedShape(path);
 352         checkEmptyPath(s);
 353 
 354         // Check filtering -Infinity values:
 355         path.reset();
 356         path.moveTo(100, NEGATIVE_INFINITY);
 357         path.lineTo(NEGATIVE_INFINITY, 100);
 358         path.lineTo(NEGATIVE_INFINITY, NEGATIVE_INFINITY);
 359 
 360         path.quadTo(NEGATIVE_INFINITY, 100,
 361                     NEGATIVE_INFINITY, 100);
 362         path.quadTo(100, NEGATIVE_INFINITY,
 363                     100, NEGATIVE_INFINITY);
 364         path.quadTo(NEGATIVE_INFINITY, NEGATIVE_INFINITY,
 365                     NEGATIVE_INFINITY, NEGATIVE_INFINITY);
 366 
 367         path.curveTo(NEGATIVE_INFINITY, 100,
 368                      NEGATIVE_INFINITY, 100,
 369                      NEGATIVE_INFINITY, 100);
 370         path.curveTo(100, NEGATIVE_INFINITY,
 371                      100, NEGATIVE_INFINITY,
 372                      100, NEGATIVE_INFINITY);
 373         path.curveTo(NEGATIVE_INFINITY, NEGATIVE_INFINITY,
 374                      NEGATIVE_INFINITY, NEGATIVE_INFINITY,
 375                      NEGATIVE_INFINITY, NEGATIVE_INFINITY);
 376         path.closePath();
 377 
 378         s = stroke.createStrokedShape(path);
 379         checkEmptyPath(s);
 380     }
 381 
 382     private static void checkEmptyPath(final Shape s) {
 383         final float[] coords = new float[6];
 384         final PathIterator it = s.getPathIterator(null);
 385 
 386         int n = 0;
 387         for (; !it.isDone(); it.next()) {
 388             int type = it.currentSegment(coords);
 389             System.out.println("unexpected segment type= " + type
 390                  + " with coords: " + Arrays.toString(coords));
 391             n++;
 392         }
 393         if (n != 0) {
 394             System.out.println("path elements = " + n);
 395             throw new IllegalStateException("Not empty path: "
 396                           + n + " path elements !");
 397         }
 398     }
 399 
 400     private static void checkPixel(final Raster raster,
 401                                    final int x, final int y,
 402                                    final int expected) {
 403 
 404         final int[] rgb = (int[]) raster.getDataElements(x, y, null);
 405 
 406         if (rgb[0] != expected) {
 407             throw new IllegalStateException("bad pixel at (" + x + ", " + y
 408                           + ") = " + rgb[0] + " expected: " + expected);
 409         }
 410     }
 411 
 412     private static void checkPixelNotWhite(final Raster raster,
 413                                            final int x, final int y) {
 414 
 415         final int[] rgb = (int[]) raster.getDataElements(x, y, null);
 416 
 417         if (rgb[0] == Color.WHITE.getRGB()) {
 418             throw new IllegalStateException("bad pixel at (" + x + ", " + y
 419                           + ") is white (empty)");
 420         }
 421     }
 422 }