--- old/test/jdk/sun/java2d/marlin/ClipShapeTest.java 2019-07-29 09:17:55.211852934 +0200 +++ new/test/jdk/sun/java2d/marlin/ClipShapeTest.java 2019-07-29 09:17:55.103852936 +0200 @@ -24,11 +24,14 @@ import java.awt.Color; import java.awt.Graphics2D; import java.awt.RenderingHints; -import java.awt.Shape; import java.awt.Stroke; +import java.awt.Shape; +import java.awt.geom.CubicCurve2D; import java.awt.geom.Ellipse2D; +import java.awt.geom.Line2D; import java.awt.geom.Path2D; import java.awt.geom.PathIterator; +import java.awt.geom.QuadCurve2D; import java.awt.image.BufferedImage; import java.awt.image.DataBufferInt; import java.io.File; @@ -69,28 +72,31 @@ */ public final class ClipShapeTest { - static boolean TX_SCALE = false; - static boolean TX_SHEAR = false; + // test options: + static int NUM_TESTS; + + // shape settings: + static ShapeMode SHAPE_MODE; + + static boolean USE_DASHES; + static boolean USE_VAR_STROKE; + + static int THRESHOLD_DELTA; + static long THRESHOLD_NBPIX; + + // constants: + static final boolean DO_FAIL = Boolean.valueOf(System.getProperty("ClipShapeTest.fail", "true")); static final boolean TEST_STROKER = true; static final boolean TEST_FILLER = true; - // complementary tests in slow mode: - static boolean USE_DASHES = false; - static boolean USE_VAR_STROKE = false; + static final boolean SUBDIVIDE_CURVE = true; + static final double SUBDIVIDE_LEN_TH = 50.0; + static final boolean TRACE_SUBDIVIDE_CURVE = false; - static int NUM_TESTS = 5000; static final int TESTW = 100; static final int TESTH = 100; - // shape settings: - static ShapeMode SHAPE_MODE = ShapeMode.NINE_LINE_POLYS; - - static int THRESHOLD_DELTA; - static long THRESHOLD_NBPIX; - - static final boolean SHAPE_REPEAT = true; - // dump path on console: static final boolean DUMP_SHAPE = true; @@ -103,7 +109,8 @@ static final int MAX_SAVE_FRAMES = 100; // use fixed seed to reproduce always same polygons between tests - static final boolean FIXED_SEED = false; + static final boolean FIXED_SEED = true; + static final double RAND_SCALE = 3.0; static final double RANDW = TESTW * RAND_SCALE; static final double OFFW = (TESTW - RANDW) / 2.0; @@ -126,6 +133,7 @@ static final File OUTPUT_DIR = new File("."); static final AtomicBoolean isMarlin = new AtomicBoolean(); + static final AtomicBoolean isMarlinFloat = new AtomicBoolean(); static final AtomicBoolean isClipRuntime = new AtomicBoolean(); static { @@ -143,6 +151,7 @@ // last space to avoid matching other settings: if (msg.startsWith("sun.java2d.renderer ")) { isMarlin.set(msg.contains("MarlinRenderingEngine")); + isMarlinFloat.set(!msg.contains("DMarlinRenderingEngine")); } if (msg.startsWith("sun.java2d.renderer.clip.runtime.enable")) { isClipRuntime.set(msg.contains("true")); @@ -186,12 +195,22 @@ // curve length max error: System.setProperty("sun.java2d.renderer.curve_len_err", "1e-4"); + // cubic min/max error: + System.setProperty("sun.java2d.renderer.cubic_dec_d2", "1e-3"); + System.setProperty("sun.java2d.renderer.cubic_inc_d1", "1e-4"); + // quad max error: System.setProperty("sun.java2d.renderer.quad_dec_d2", "5e-4"); + } - // cubic min/max error: - System.setProperty("sun.java2d.renderer.cubic_dec_d2", "1e-3"); - System.setProperty("sun.java2d.renderer.cubic_inc_d1", "1e-4"); // or disabled ~ 1e-6 + private static void resetOptions() { + NUM_TESTS = Integer.getInteger("ClipShapeTest.numTests", 5000); + + // shape settings: + SHAPE_MODE = ShapeMode.NINE_LINE_POLYS; + + USE_DASHES = false; + USE_VAR_STROKE = false; } /** @@ -199,79 +218,119 @@ * @param args */ public static void main(String[] args) { + { + // Bootstrap: init Renderer now: + final BufferedImage img = newImage(TESTW, TESTH); + final Graphics2D g2d = initialize(img, null); + + try { + paintShape(new Line2D.Double(0,0,100,100), g2d, true, false); + } finally { + g2d.dispose(); + } + + if (!isMarlin.get()) { + throw new RuntimeException("Marlin renderer not used at runtime !"); + } + if (!isClipRuntime.get()) { + throw new RuntimeException("Marlin clipping not enabled at runtime !"); + } + } + + System.out.println("---------------------------------------"); + System.out.println("ClipShapeTest: image = " + TESTW + " x " + TESTH); + + resetOptions(); + boolean runSlowTests = false; for (String arg : args) { if ("-slow".equals(arg)) { - System.out.println("slow: enabled."); runSlowTests = true; - } else if ("-doScale".equals(arg)) { - System.out.println("doScale: enabled."); - TX_SCALE = true; - } else if ("-doShear".equals(arg)) { - System.out.println("doShear: enabled."); - TX_SHEAR = true; } else if ("-doDash".equals(arg)) { - System.out.println("doDash: enabled."); USE_DASHES = true; } else if ("-doVarStroke".equals(arg)) { - System.out.println("doVarStroke: enabled."); USE_VAR_STROKE = true; - } - // shape mode: - else if (arg.equalsIgnoreCase("-poly")) { - SHAPE_MODE = ShapeMode.NINE_LINE_POLYS; - } else if (arg.equalsIgnoreCase("-bigpoly")) { - SHAPE_MODE = ShapeMode.FIFTY_LINE_POLYS; - } else if (arg.equalsIgnoreCase("-quad")) { - SHAPE_MODE = ShapeMode.FOUR_QUADS; - } else if (arg.equalsIgnoreCase("-cubic")) { - SHAPE_MODE = ShapeMode.TWO_CUBICS; - } else if (arg.equalsIgnoreCase("-mixed")) { - SHAPE_MODE = ShapeMode.MIXED; + } else { + // shape mode: + if (arg.equalsIgnoreCase("-poly")) { + SHAPE_MODE = ShapeMode.NINE_LINE_POLYS; + } else if (arg.equalsIgnoreCase("-bigpoly")) { + SHAPE_MODE = ShapeMode.FIFTY_LINE_POLYS; + } else if (arg.equalsIgnoreCase("-quad")) { + SHAPE_MODE = ShapeMode.FOUR_QUADS; + } else if (arg.equalsIgnoreCase("-cubic")) { + SHAPE_MODE = ShapeMode.TWO_CUBICS; + } else if (arg.equalsIgnoreCase("-mixed")) { + SHAPE_MODE = ShapeMode.MIXED; + } } } System.out.println("Shape mode: " + SHAPE_MODE); // adjust image comparison thresholds: - switch(SHAPE_MODE) { + switch (SHAPE_MODE) { case TWO_CUBICS: // Define uncertainty for curves: - THRESHOLD_DELTA = 32; // / 256 - THRESHOLD_NBPIX = 128; // / 10000 + THRESHOLD_DELTA = 32; + THRESHOLD_NBPIX = (USE_DASHES) ? 50 : 200; + if (SUBDIVIDE_CURVE) { + THRESHOLD_NBPIX = 4; + } break; case FOUR_QUADS: case MIXED: // Define uncertainty for quads: // curve subdivision causes curves to be smaller // then curve offsets are different (more accurate) - THRESHOLD_DELTA = 64; // 64 / 256 - THRESHOLD_NBPIX = 256; // 256 / 10000 + THRESHOLD_DELTA = 64; + THRESHOLD_NBPIX = (USE_DASHES) ? 40 : 420; + if (SUBDIVIDE_CURVE) { + THRESHOLD_NBPIX = 10; + } break; default: // Define uncertainty for lines: // float variant have higher uncertainty - THRESHOLD_DELTA = 8; - THRESHOLD_NBPIX = 8; + THRESHOLD_DELTA = 2; + THRESHOLD_NBPIX = (USE_DASHES) ? + // float variant have higher uncertainty + ((isMarlinFloat.get()) ? 30 : 6) // low for double + : (isMarlinFloat.get()) ? 10 : 0; } - System.out.println("THRESHOLD_DELTA: "+THRESHOLD_DELTA); - System.out.println("THRESHOLD_NBPIX: "+THRESHOLD_NBPIX); +// Visual inspection (low threshold): +// THRESHOLD_NBPIX = 2; + + System.out.println("THRESHOLD_DELTA: " + THRESHOLD_DELTA); + System.out.println("THRESHOLD_NBPIX: " + THRESHOLD_NBPIX); if (runSlowTests) { NUM_TESTS = 10000; // or 100000 (very slow) - USE_DASHES = true; USE_VAR_STROKE = true; } - System.out.println("ClipShapeTests: image = " + TESTW + " x " + TESTH); + System.out.println("NUM_TESTS: " + NUM_TESTS); + + if (USE_DASHES) { + System.out.println("USE_DASHES: enabled."); + } + if (USE_VAR_STROKE) { + System.out.println("USE_VAR_STROKE: enabled."); + } + if (!DO_FAIL) { + System.out.println("DO_FAIL: disabled."); + } + + System.out.println("---------------------------------------"); + + final DiffContext allCtx = new DiffContext("All Test setups"); + final DiffContext allWorstCtx = new DiffContext("Worst(All Test setups)"); int failures = 0; final long start = System.nanoTime(); try { - // TODO: test affine transforms ? - if (TEST_STROKER) { final float[][] dashArrays = (USE_DASHES) ? // small @@ -291,7 +350,7 @@ int nsw = 0; if (USE_VAR_STROKE) { - for (float width = 0.1f; width < 110f; width *= 5f) { + for (float width = 0.25f; width < 110f; width *= 5f) { strokeWidths[nsw++] = width; } } else { @@ -310,8 +369,8 @@ for (int join = 0; join <= 2; join++) { - failures += paintPaths(new TestSetup(SHAPE_MODE, false, width, cap, join, dashes)); - failures += paintPaths(new TestSetup(SHAPE_MODE, true, width, cap, join, dashes)); + failures += paintPaths(allCtx, allWorstCtx, new TestSetup(SHAPE_MODE, false, width, cap, join, dashes)); + failures += paintPaths(allCtx, allWorstCtx, new TestSetup(SHAPE_MODE, true, width, cap, join, dashes)); } } } @@ -320,29 +379,26 @@ if (TEST_FILLER) { // Filler tests: - failures += paintPaths(new TestSetup(SHAPE_MODE, false, Path2D.WIND_NON_ZERO)); - failures += paintPaths(new TestSetup(SHAPE_MODE, true, Path2D.WIND_NON_ZERO)); + failures += paintPaths(allCtx, allWorstCtx, new TestSetup(SHAPE_MODE, false, Path2D.WIND_NON_ZERO)); + failures += paintPaths(allCtx, allWorstCtx, new TestSetup(SHAPE_MODE, true, Path2D.WIND_NON_ZERO)); - failures += paintPaths(new TestSetup(SHAPE_MODE, false, Path2D.WIND_EVEN_ODD)); - failures += paintPaths(new TestSetup(SHAPE_MODE, true, Path2D.WIND_EVEN_ODD)); + failures += paintPaths(allCtx, allWorstCtx, new TestSetup(SHAPE_MODE, false, Path2D.WIND_EVEN_ODD)); + failures += paintPaths(allCtx, allWorstCtx, new TestSetup(SHAPE_MODE, true, Path2D.WIND_EVEN_ODD)); } } catch (IOException ioe) { throw new RuntimeException(ioe); } System.out.println("main: duration= " + (1e-6 * (System.nanoTime() - start)) + " ms."); - if (!isMarlin.get()) { - throw new RuntimeException("Marlin renderer not used at runtime !"); - } - if (!isClipRuntime.get()) { - throw new RuntimeException("Marlin clipping not enabled at runtime !"); - } - if (failures != 0) { + allWorstCtx.dump(); + allCtx.dump(); + + if (DO_FAIL && (failures != 0)) { throw new RuntimeException("Clip test failures : " + failures); } } - static int paintPaths(final TestSetup ts) throws IOException { + static int paintPaths(final DiffContext allCtx, final DiffContext allWorstCtx, final TestSetup ts) throws IOException { final long start = System.nanoTime(); if (FIXED_SEED) { @@ -356,19 +412,24 @@ final boolean fill = !ts.isStroke(); final Path2D p2d = new Path2D.Double(ts.windingRule); + final Stroke stroke = (!fill) ? createStroke(ts) : null; + final BufferedImage imgOn = newImage(TESTW, TESTH); - final Graphics2D g2dOn = initialize(imgOn, ts); + final Graphics2D g2dOn = initialize(imgOn, stroke); final BufferedImage imgOff = newImage(TESTW, TESTH); - final Graphics2D g2dOff = initialize(imgOff, ts); + final Graphics2D g2dOff = initialize(imgOff, stroke); final BufferedImage imgDiff = newImage(TESTW, TESTH); - final DiffContext globalCtx = new DiffContext("All tests"); + final DiffContext testSetupCtx = new DiffContext("Test setup"); + final DiffContext testWorstCtx = new DiffContext("Worst"); + final DiffContext testWorstThCtx = new DiffContext("Worst(>threshold)"); int nd = 0; try { final DiffContext testCtx = new DiffContext("Test"); + final DiffContext testThCtx = new DiffContext("Test(>threshold)"); BufferedImage diffImage; for (int n = 0; n < NUM_TESTS; n++) { @@ -381,15 +442,24 @@ paintShape(p2d, g2dOn, fill, true); /* compute image difference if possible */ - diffImage = computeDiffImage(testCtx, imgOn, imgOff, imgDiff, globalCtx); - - final String testName = "Setup_" + ts.id + "_test_" + n; + diffImage = computeDiffImage(testCtx, testThCtx, imgOn, imgOff, imgDiff); + // Worst (total) + if (testCtx.isDiff()) { + if (testWorstCtx.isWorse(testCtx, false)) { + testWorstCtx.set(testCtx); + } + if (testWorstThCtx.isWorse(testCtx, true)) { + testWorstThCtx.set(testCtx); + } + // accumulate data: + testSetupCtx.add(testCtx); + } if (diffImage != null) { nd++; - final double ratio = (100.0 * testCtx.histPix.count) / testCtx.histAll.count; - System.out.println("Diff ratio: " + testName + " = " + trimTo3Digits(ratio) + " %"); + testThCtx.dump(); + testCtx.dump(); if (nd < MAX_SHOW_FRAMES) { if (SHOW_DETAILS) { @@ -401,9 +471,12 @@ if (DUMP_SHAPE) { dumpShape(p2d); } + + final String testName = "Setup_" + ts.id + "_test_" + n; + saveImage(imgOff, OUTPUT_DIR, testName + "-off.png"); saveImage(imgOn, OUTPUT_DIR, testName + "-on.png"); - saveImage(diffImage, OUTPUT_DIR, testName + "-diff.png"); + saveImage(imgDiff, OUTPUT_DIR, testName + "-diff.png"); } } } @@ -418,13 +491,25 @@ + " ratio = " + (100f * nd) / NUM_TESTS + " %"); } - globalCtx.dump(); + if (testWorstCtx.isDiff()) { + testWorstCtx.dump(); + if (testWorstThCtx.isDiff() && testWorstThCtx.histPix.sum != testWorstCtx.histPix.sum) { + testWorstThCtx.dump(); + } + if (allWorstCtx.isWorse(testWorstThCtx, true)) { + allWorstCtx.set(testWorstThCtx); + } + } + testSetupCtx.dump(); + + // accumulate data: + allCtx.add(testSetupCtx); } System.out.println("paintPaths: duration= " + (1e-6 * (System.nanoTime() - start)) + " ms."); return nd; } - private static void paintShape(final Path2D p2d, final Graphics2D g2d, + private static void paintShape(final Shape p2d, final Graphics2D g2d, final boolean fill, final boolean clip) { reset(g2d); @@ -438,26 +523,20 @@ } private static Graphics2D initialize(final BufferedImage img, - final TestSetup ts) { + final Stroke s) { final Graphics2D g2d = (Graphics2D) img.getGraphics(); g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, - RenderingHints.VALUE_STROKE_PURE); +// Test normalize: +// RenderingHints.VALUE_STROKE_NORMALIZE + RenderingHints.VALUE_STROKE_PURE + ); - if (ts.isStroke()) { - g2d.setStroke(createStroke(ts)); - } - g2d.setColor(Color.GRAY); - - // Test scale - if (TX_SCALE) { - g2d.scale(1.2, 1.2); - } - // Test shear - if (TX_SHEAR) { - g2d.shear(0.1, 0.2); + if (s != null) { + g2d.setStroke(s); } + g2d.setColor(Color.BLACK); return g2d; } @@ -482,20 +561,35 @@ static void genShape(final Path2D p2d, final TestSetup ts) { p2d.reset(); - final int end = (SHAPE_REPEAT) ? 2 : 1; + /* + Test closed path: + 0: moveTo + (draw)To + closePath + 1: (draw)To + closePath (closePath + (draw)To sequence) + */ + final int end = (ts.closed) ? 2 : 1; + + final double[] in = new double[8]; + + double sx0 = 0.0, sy0 = 0.0, x0 = 0.0, y0 = 0.0; for (int p = 0; p < end; p++) { - p2d.moveTo(randX(), randY()); + if (p <= 0) { + x0 = randX(); y0 = randY(); + p2d.moveTo(x0, y0); + sx0 = x0; sy0 = y0; + } switch (ts.shapeMode) { case MIXED: - case FIFTY_LINE_POLYS: - case NINE_LINE_POLYS: case FIVE_LINE_POLYS: + case NINE_LINE_POLYS: + case FIFTY_LINE_POLYS: p2d.lineTo(randX(), randY()); p2d.lineTo(randX(), randY()); p2d.lineTo(randX(), randY()); p2d.lineTo(randX(), randY()); + x0 = randX(); y0 = randY(); + p2d.lineTo(x0, y0); if (ts.shapeMode == ShapeMode.FIVE_LINE_POLYS) { // And an implicit close makes 5 lines break; @@ -503,29 +597,75 @@ p2d.lineTo(randX(), randY()); p2d.lineTo(randX(), randY()); p2d.lineTo(randX(), randY()); - p2d.lineTo(randX(), randY()); + x0 = randX(); y0 = randY(); + p2d.lineTo(x0, y0); if (ts.shapeMode == ShapeMode.NINE_LINE_POLYS) { // And an implicit close makes 9 lines break; } if (ts.shapeMode == ShapeMode.FIFTY_LINE_POLYS) { for (int i = 0; i < 41; i++) { - p2d.lineTo(randX(), randY()); + x0 = randX(); y0 = randY(); + p2d.lineTo(x0, y0); } // And an implicit close makes 50 lines break; } case TWO_CUBICS: - p2d.curveTo(randX(), randY(), randX(), randY(), randX(), randY()); - p2d.curveTo(randX(), randY(), randX(), randY(), randX(), randY()); + if (SUBDIVIDE_CURVE) { + in[0] = x0; in[1] = y0; + in[2] = randX(); in[3] = randY(); + in[4] = randX(); in[5] = randY(); + x0 = randX(); y0 = randY(); + in[6] = x0; in[7] = y0; + subdivide(p2d, 8, in); + in[0] = x0; in[1] = y0; + in[2] = randX(); in[3] = randY(); + in[4] = randX(); in[5] = randY(); + x0 = randX(); y0 = randY(); + in[6] = x0; in[7] = y0; + subdivide(p2d, 8, in); + } else { + x0 = randX(); y0 = randY(); + p2d.curveTo(randX(), randY(), randX(), randY(), x0, y0); + x0 = randX(); y0 = randY(); + p2d.curveTo(randX(), randY(), randX(), randY(), x0, y0); + } if (ts.shapeMode == ShapeMode.TWO_CUBICS) { break; } case FOUR_QUADS: - p2d.quadTo(randX(), randY(), randX(), randY()); - p2d.quadTo(randX(), randY(), randX(), randY()); - p2d.quadTo(randX(), randY(), randX(), randY()); - p2d.quadTo(randX(), randY(), randX(), randY()); + if (SUBDIVIDE_CURVE) { + in[0] = x0; in[1] = y0; + in[2] = randX(); in[3] = randY(); + x0 = randX(); y0 = randY(); + in[4] = x0; in[5] = y0; + subdivide(p2d, 6, in); + in[0] = x0; in[1] = y0; + in[2] = randX(); in[3] = randY(); + x0 = randX(); y0 = randY(); + in[4] = x0; in[5] = y0; + subdivide(p2d, 6, in); + in[0] = x0; in[1] = y0; + in[2] = randX(); in[3] = randY(); + x0 = randX(); y0 = randY(); + in[4] = x0; in[5] = y0; + subdivide(p2d, 6, in); + in[0] = x0; in[1] = y0; + in[2] = randX(); in[3] = randY(); + x0 = randX(); y0 = randY(); + in[4] = x0; in[5] = y0; + subdivide(p2d, 6, in); + } else { + x0 = randX(); y0 = randY(); + p2d.quadTo(randX(), randY(), x0, y0); + x0 = randX(); y0 = randY(); + p2d.quadTo(randX(), randY(), x0, y0); + x0 = randX(); y0 = randY(); + p2d.quadTo(randX(), randY(), x0, y0); + x0 = randX(); y0 = randY(); + p2d.quadTo(randX(), randY(), x0, y0); + } if (ts.shapeMode == ShapeMode.FOUR_QUADS) { break; } @@ -534,6 +674,111 @@ if (ts.closed) { p2d.closePath(); + x0 = sx0; y0 = sy0; + } + } + } + + static final int SUBDIVIDE_LIMIT = 5; + static final double[][] SUBDIVIDE_CURVES = new double[SUBDIVIDE_LIMIT + 1][]; + + static { + for (int i = 0, n = 1; i < SUBDIVIDE_LIMIT; i++, n *= 2) { + SUBDIVIDE_CURVES[i] = new double[8 * n]; + } + } + + static void subdivide(final Path2D p2d, final int type, final double[] in) { + if (TRACE_SUBDIVIDE_CURVE) { + System.out.println("subdivide: " + Arrays.toString(Arrays.copyOf(in, type))); + } + + double curveLen = ((type == 8) + ? curvelen(in[0], in[1], in[2], in[3], in[4], in[5], in[6], in[7]) + : quadlen(in[0], in[1], in[2], in[3], in[4], in[5])); + + if (curveLen > SUBDIVIDE_LEN_TH) { + if (TRACE_SUBDIVIDE_CURVE) { + System.out.println("curvelen: " + curveLen); + } + + System.arraycopy(in, 0, SUBDIVIDE_CURVES[0], 0, 8); + + int level = 0; + while (curveLen >= SUBDIVIDE_LEN_TH) { + level++; + curveLen /= 2.0; + if (TRACE_SUBDIVIDE_CURVE) { + System.out.println("curvelen: " + curveLen); + } + } + + if (TRACE_SUBDIVIDE_CURVE) { + System.out.println("level: " + level); + } + + if (level > SUBDIVIDE_LIMIT) { + if (TRACE_SUBDIVIDE_CURVE) { + System.out.println("max level reached : " + level); + } + level = SUBDIVIDE_LIMIT; + } + + for (int l = 0; l < level; l++) { + if (TRACE_SUBDIVIDE_CURVE) { + System.out.println("level: " + l); + } + + double[] src = SUBDIVIDE_CURVES[l]; + double[] dst = SUBDIVIDE_CURVES[l + 1]; + + for (int i = 0, j = 0; i < src.length; i += 8, j += 16) { + if (TRACE_SUBDIVIDE_CURVE) { + System.out.println("subdivide: " + Arrays.toString(Arrays.copyOfRange(src, i, i + type))); + } + if (type == 8) { + CubicCurve2D.subdivide(src, i, dst, j, dst, j + 8); + } else { + QuadCurve2D.subdivide(src, i, dst, j, dst, j + 8); + } + if (TRACE_SUBDIVIDE_CURVE) { + System.out.println("left: " + Arrays.toString(Arrays.copyOfRange(dst, j, j + type))); + System.out.println("right: " + Arrays.toString(Arrays.copyOfRange(dst, j + 8, j + 8 + type))); + } + } + } + + // Emit curves at last level: + double[] src = SUBDIVIDE_CURVES[level]; + + double len = 0.0; + + for (int i = 0; i < src.length; i += 8) { + if (TRACE_SUBDIVIDE_CURVE) { + System.out.println("curve: " + Arrays.toString(Arrays.copyOfRange(src, i, i + type))); + } + + if (type == 8) { + if (TRACE_SUBDIVIDE_CURVE) { + len += curvelen(src[i + 0], src[i + 1], src[i + 2], src[i + 3], src[i + 4], src[i + 5], src[i + 6], src[i + 7]); + } + p2d.curveTo(src[i + 2], src[i + 3], src[i + 4], src[i + 5], src[i + 6], src[i + 7]); + } else { + if (TRACE_SUBDIVIDE_CURVE) { + len += quadlen(src[i + 0], src[i + 1], src[i + 2], src[i + 3], src[i + 4], src[i + 5]); + } + p2d.quadTo(src[i + 2], src[i + 3], src[i + 4], src[i + 5]); + } + } + + if (TRACE_SUBDIVIDE_CURVE) { + System.out.println("curveLen (final) = " + len); + } + } else { + if (type == 8) { + p2d.curveTo(in[2], in[3], in[4], in[5], in[6], in[7]); + } else { + p2d.quadTo(in[2], in[3], in[4], in[5]); } } } @@ -754,18 +999,19 @@ return new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB_PRE); } - public static BufferedImage computeDiffImage(final DiffContext localCtx, + public static BufferedImage computeDiffImage(final DiffContext testCtx, + final DiffContext testThCtx, final BufferedImage tstImage, final BufferedImage refImage, - final BufferedImage diffImage, - final DiffContext globalCtx) { + final BufferedImage diffImage) { final int[] aRefPix = ((DataBufferInt) refImage.getRaster().getDataBuffer()).getData(); final int[] aTstPix = ((DataBufferInt) tstImage.getRaster().getDataBuffer()).getData(); final int[] aDifPix = ((DataBufferInt) diffImage.getRaster().getDataBuffer()).getData(); - // reset local diff context: - localCtx.reset(); + // reset diff contexts: + testCtx.reset(); + testThCtx.reset(); int ref, tst, dg, v; for (int i = 0, len = aRefPix.length; i < len; i++) { @@ -777,24 +1023,24 @@ // max difference on grayscale values: v = (int) Math.ceil(Math.abs(dg / 3.0)); - -// TODO: count warnings if (v <= THRESHOLD_DELTA) { aDifPix[i] = 0; } else { aDifPix[i] = toInt(v, v, v); + testThCtx.add(v); + } - localCtx.add(v); + if (v != 0) { + testCtx.add(v); } - globalCtx.add(v); } - if (!localCtx.isDiff() || (localCtx.histPix.count <= THRESHOLD_NBPIX)) { + testCtx.addNbPix(testThCtx.histPix.count); + + if (!testThCtx.isDiff() || (testThCtx.histPix.count <= THRESHOLD_NBPIX)) { return null; } - localCtx.dump(); - return diffImage; } @@ -895,6 +1141,17 @@ } } + void add(StatInteger stat) { + count += stat.count; + sum += stat.sum; + if (stat.min < min) { + min = stat.min; + } + if (stat.max > max) { + max = stat.max; + } + } + public final double average() { return ((double) sum) / count; } @@ -908,8 +1165,11 @@ public final StringBuilder toString(final StringBuilder sb) { sb.append(name).append("[n: ").append(count); - sb.append("] sum: ").append(sum).append(" avg: ").append(trimTo3Digits(average())); - sb.append(" [").append(min).append(" | ").append(max).append("]"); + sb.append("] "); + if (count != 0) { + sb.append("sum: ").append(sum).append(" avg: ").append(trimTo3Digits(average())); + sb.append(" [").append(min).append(" | ").append(max).append("]"); + } return sb; } @@ -921,6 +1181,7 @@ static final int MAX = 20; static final int LAST = MAX - 1; static final int[] STEPS = new int[MAX]; + static final int BUCKET_TH; static { STEPS[0] = 0; @@ -930,6 +1191,12 @@ STEPS[i] = STEPS[i - 1] * BUCKET; } // System.out.println("Histogram.STEPS = " + Arrays.toString(STEPS)); + + if (THRESHOLD_DELTA % 2 != 0) { + throw new IllegalStateException("THRESHOLD_DELTA must be odd"); + } + + BUCKET_TH = bucket(THRESHOLD_DELTA); } static int bucket(int val) { @@ -969,6 +1236,37 @@ add((int) val); } + void add(Histogram hist) { + super.add(hist); + for (int i = 0; i < MAX; i++) { + stats[i].add(hist.stats[i]); + } + } + + boolean isWorse(Histogram hist, boolean useTh) { + boolean worst = false; + if (!useTh && (hist.sum > sum)) { + worst = true; + } else { + long sumLoc = 0l; + long sumHist = 0l; + // use running sum: + for (int i = MAX - 1; i >= BUCKET_TH; i--) { + sumLoc += stats[i].sum; + sumHist += hist.stats[i].sum; + } + if (sumHist > sumLoc) { + worst = true; + } + } + /* + System.out.println("running sum worst:"); + System.out.println("this ? " + toString()); + System.out.println("worst ? " + hist.toString()); + */ + return worst; + } + @Override public final String toString() { final StringBuilder sb = new StringBuilder(2048); @@ -995,38 +1293,88 @@ static final class DiffContext { - public final Histogram histAll; public final Histogram histPix; + public final StatInteger nbPix; + DiffContext(String name) { - histAll = new Histogram("All Pixels [" + name + "]"); histPix = new Histogram("Diff Pixels [" + name + "]"); + nbPix = new StatInteger("NbPixels [" + name + "]"); } void reset() { - histAll.reset(); histPix.reset(); + nbPix.reset(); } void dump() { if (isDiff()) { - System.out.println("Differences [" + histAll.name + "]:"); - System.out.println("Total [all pixels]:\n" + histAll.toString()); - System.out.println("Total [different pixels]:\n" + histPix.toString()); + System.out.println("Differences [" + histPix.name + "]:\n" + + ((nbPix.count != 0) ? (nbPix.toString() + "\n") : "") + + histPix.toString() + ); } else { - System.out.println("No difference for [" + histAll.name + "]."); + System.out.println("No difference for [" + histPix.name + "]."); } } void add(int val) { - histAll.add(val); - if (val != 0) { - histPix.add(val); + histPix.add(val); + } + + void add(DiffContext ctx) { + histPix.add(ctx.histPix); + if (ctx.nbPix.count != 0L) { + nbPix.add(ctx.nbPix); } } + void addNbPix(long val) { + if (val != 0L) { + nbPix.add(val); + } + } + + void set(DiffContext ctx) { + reset(); + add(ctx); + } + + boolean isWorse(DiffContext ctx, boolean useTh) { + return histPix.isWorse(ctx.histPix, useTh); + } + boolean isDiff() { - return histAll.sum != 0l; + return histPix.sum != 0l; } } + + + static double linelen(final double x0, final double y0, + final double x1, final double y1) + { + final double dx = x1 - x0; + final double dy = y1 - y0; + return Math.sqrt(dx * dx + dy * dy); + } + + static double quadlen(final double x0, final double y0, + final double x1, final double y1, + final double x2, final double y2) + { + return (linelen(x0, y0, x1, y1) + + linelen(x1, y1, x2, y2) + + linelen(x0, y0, x2, y2)) / 2.0d; + } + + static double curvelen(final double x0, final double y0, + final double x1, final double y1, + final double x2, final double y2, + final double x3, final double y3) + { + return (linelen(x0, y0, x1, y1) + + linelen(x1, y1, x2, y2) + + linelen(x2, y2, x3, y3) + + linelen(x0, y0, x3, y3)) / 2.0d; + } }