/* * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.geom.Path2D; import static java.awt.geom.Path2D.WIND_NON_ZERO; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; import sun.java2d.pipe.RenderingEngine; /** * Simple crash rendering test using huge GeneralPaths with marlin renderer * * run it with large heap (2g): * java -Dsun.java2d.renderer=sun.java2d.marlin.MarlinRenderingEngine marlin.CrashTest * * @author bourgesl */ public class CrashTest { static final boolean SAVE_IMAGE = false; static boolean USE_ROUND_CAPS_AND_JOINS = true; public static void main(String[] args) { // try insane image sizes: // subpixel coords may overflow: // testHugeImage((Integer.MAX_VALUE >> 3) + 1, 6); // larger than 23 bits: (RLE) testHugeImage(8388608 + 1, 10); test(0.1f, false, 0); test(0.1f, true, 7f); // Exceed 2Gb OffHeap buffer for edges: try { USE_ROUND_CAPS_AND_JOINS = true; test(0.1f, true, 0.1f); System.out.println("Exception MISSING."); } catch (Throwable th) { if (th instanceof ArrayIndexOutOfBoundsException) { System.out.println("ArrayIndexOutOfBoundsException expected."); } else { System.out.println("Exception occured:"); th.printStackTrace(); } } } private static void test(final float lineStroke, final boolean useDashes, final float dashMinLen) throws ArrayIndexOutOfBoundsException { System.out.println("---\n" + "test: " + "lineStroke=" + lineStroke + ", useDashes=" + useDashes +", dashMinLen=" + dashMinLen ); final String renderer = RenderingEngine.getInstance().getClass().getSimpleName(); System.out.println("Testing renderer = " + renderer); final BasicStroke stroke = createStroke(lineStroke, useDashes, dashMinLen); // TODO: test Dasher.firstSegmentsBuffer resizing ? // array.dasher.firstSegmentsBuffer.d_float[2] sum: 6 avg: 3.0 [3 | 3] /* // Marlin growable arrays: = new StatLong("array.dasher.firstSegmentsBuffer.d_float"); = new StatLong("array.stroker.polystack.curves.d_float"); = new StatLong("array.stroker.polystack.curveTypes.d_byte"); = new StatLong("array.marlincache.rowAAChunk.d_byte"); = new StatLong("array.marlincache.touchedTile.int"); = new StatLong("array.renderer.alphaline.int"); = new StatLong("array.renderer.crossings.int"); = new StatLong("array.renderer.aux_crossings.int"); = new StatLong("array.renderer.edgeBuckets.int"); = new StatLong("array.renderer.edgeBucketCounts.int"); = new StatLong("array.renderer.edgePtrs.int"); = new StatLong("array.renderer.aux_edgePtrs.int"); */ // size > 8192 (exceed both tile and buckets arrays) final int size = 9000; System.out.println("image size = " + size); final BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); final Graphics2D g2d = (Graphics2D) image.getGraphics(); try { g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g2d.setClip(0, 0, size, size); g2d.setBackground(Color.WHITE); g2d.clearRect(0, 0, size, size); g2d.setStroke(stroke); g2d.setColor(Color.BLACK); final long start = System.nanoTime(); paint(g2d, size - 10f); final long time = System.nanoTime() - start; System.out.println("paint: duration= " + (1e-6 * time) + " ms."); if (SAVE_IMAGE) { try { final File file = new File("CrashTest-" + renderer + "-dash-" + useDashes + ".bmp"); System.out.println("Writing file: " + file.getAbsolutePath()); ImageIO.write(image, "BMP", file); } catch (IOException ex) { System.out.println("Writing file failure:"); ex.printStackTrace(); } } } finally { g2d.dispose(); } } private static void testHugeImage(final int width, final int height) throws ArrayIndexOutOfBoundsException { System.out.println("---\n" + "testHugeImage: " + "width=" + width + ", height=" + height ); final String renderer = RenderingEngine.getInstance().getClass().getSimpleName(); System.out.println("Testing renderer = " + renderer); final BasicStroke stroke = createStroke(2.5f, false, 0); // size > 24bits (exceed both tile and buckets arrays) System.out.println("image size = " + width + " x "+height); final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY); final Graphics2D g2d = (Graphics2D) image.getGraphics(); try { g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g2d.setBackground(Color.WHITE); g2d.clearRect(0, 0, width, height); g2d.setStroke(stroke); g2d.setColor(Color.BLACK); final Path2D.Float path = new Path2D.Float(WIND_NON_ZERO, 32); path.moveTo(0, 0); path.lineTo(width, 0); path.lineTo(width, height); path.lineTo(0, height); path.lineTo(0, 0); final long start = System.nanoTime(); g2d.draw(path); final long time = System.nanoTime() - start; System.out.println("paint: duration= " + (1e-6 * time) + " ms."); if (SAVE_IMAGE) { try { final File file = new File("CrashTest-" + renderer + "-huge-" + width + "x" +height + ".bmp"); System.out.println("Writing file: " + file.getAbsolutePath()); ImageIO.write(image, "BMP", file); } catch (IOException ex) { System.out.println("Writing file failure:"); ex.printStackTrace(); } } } finally { g2d.dispose(); } } private static void paint(final Graphics2D g2d, final float size) { final double halfSize = size / 2.0; final Path2D.Float path = new Path2D.Float(WIND_NON_ZERO, 32 * 1024); // show cross: path.moveTo(0, 0); path.lineTo(size, size); path.moveTo(size, 0); path.lineTo(0, size); path.moveTo(0, 0); path.lineTo(size, 0); path.moveTo(0, 0); path.lineTo(0, size); path.moveTo(0, 0); double r = size; final int ratio = 100; int repeats = 1; int n = 0; while (r > 1.0) { repeats *= ratio; if (repeats > 10000) { repeats = 10000; } for (int i = 0; i < repeats; i++) { path.lineTo(halfSize - 0.5 * r + i * r / repeats, halfSize - 0.5 * r); n++; path.lineTo(halfSize - 0.5 * r + i * r / repeats + 0.1, halfSize + 0.5 * r); n++; } r -= halfSize; } System.out.println("draw : " + n + " lines."); g2d.draw(path); } private static BasicStroke createStroke(final float width, final boolean useDashes, final float dashMinLen) { final float[] dashes; if (useDashes) { // huge dash array (exceed Dasher.INITIAL_ARRAY) dashes = new float[512]; float cur = dashMinLen; float step = 0.01f; for (int i = 0; i < dashes.length; i += 2) { dashes[i] = cur; dashes[i + 1] = cur; cur += step; } } else { dashes = null; } if (USE_ROUND_CAPS_AND_JOINS) { // Use both round Caps & Joins: return new BasicStroke(width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 100.0f, dashes, 0.0f); } return new BasicStroke(width, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 100.0f, dashes, 0.0f); } }