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 }