< prev index next >

test/jdk/sun/java2d/marlin/ClipShapeTest.java

Print this page

        

@@ -22,15 +22,18 @@
  */
 import java.awt.BasicStroke;
 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;
 import java.io.FileOutputStream;
 import java.io.IOException;

@@ -67,32 +70,35 @@
  * @run main/othervm/timeout=300 -Dsun.java2d.renderer=sun.java2d.marlin.DMarlinRenderingEngine ClipShapeTest -cubic
  * @run main/othervm/timeout=300 -Dsun.java2d.renderer=sun.java2d.marlin.DMarlinRenderingEngine ClipShapeTest -cubic -doDash
 */
 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;
 
     static final boolean SHOW_DETAILS = false; // disabled
     static final boolean SHOW_OUTLINE = true;

@@ -101,11 +107,12 @@
 
     static final int MAX_SHOW_FRAMES = 10;
     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;
     static final double RANDH = TESTH * RAND_SCALE;
     static final double OFFH = (TESTH - RANDH) / 2.0;

@@ -124,10 +131,11 @@
     static final Random RANDOM = new Random(SEED);
 
     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 {
         Locale.setDefault(Locale.US);
 

@@ -141,10 +149,11 @@
                 final String msg = record.getMessage();
                 if (msg != null) {
                     // 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"));
                     }
                 }

@@ -184,96 +193,146 @@
 
         // If any curve, increase curve accuracy:
         // 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;
     }
 
     /**
      * Test
      * @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
 //                        new float[][]{new float[]{1f, 2f}}
 // normal

@@ -289,11 +348,11 @@
                                                 ? new float[5] :
                                                   new float[]{10f};
 
                 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 {
                     nsw = 1;
                 }

@@ -308,43 +367,40 @@
 
                         for (int cap = 0; cap <= 2; cap++) {
 
                             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));
                             }
                         }
                     }
                 }
             }
 
             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) {
             // Reset seed for random numbers:
             RANDOM.setSeed(SEED);

@@ -354,23 +410,28 @@
                 + " paths (" + SHAPE_MODE + ") - setup: " + ts);
 
         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++) {
                 genShape(p2d, ts);
 

@@ -379,19 +440,28 @@
 
                 // Runtime clip setting ON:
                 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) {
                             paintShapeDetails(g2dOff, p2d);
                             paintShapeDetails(g2dOn, p2d);

@@ -399,13 +469,16 @@
 
                         if (nd < MAX_SAVE_FRAMES) {
                             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");
                         }
                     }
                 }
             }
         } finally {

@@ -416,17 +489,29 @@
                 System.out.println("paintPaths: " + NUM_TESTS + " paths - "
                         + "Number of differences = " + nd
                         + " 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);
 
         setClip(g2d, clip);
 

@@ -436,30 +521,24 @@
             g2d.draw(p2d);
         }
     }
 
     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;
     }
 
     private static void reset(final Graphics2D g2d) {

@@ -480,62 +559,228 @@
     }
 
     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;
                     }
                     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;
                     }
                 default:
             }
 
             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]);
             }
         }
     }
 
     static final float POINT_RADIUS = 2f;

@@ -752,22 +997,23 @@
 
     public static BufferedImage newImage(final int w, final int h) {
         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++) {
             ref = aRefPix[i];
             tst = aTstPix[i];

@@ -775,28 +1021,28 @@
             // grayscale diff:
             dg = (r(ref) + g(ref) + b(ref)) - (r(tst) + g(tst) + b(tst));
 
             // 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;
     }
 
     static void saveImage(final BufferedImage image, final File resDirectory, final String imageFileName) throws IOException {
         final Iterator<ImageWriter> itWriters = ImageIO.getImageWritersByFormatName("PNG");

@@ -893,10 +1139,21 @@
             if (val > max) {
                 max = val;
             }
         }
 
+        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;
         }
 
         @Override

@@ -906,12 +1163,15 @@
             return sb.toString();
         }
 
         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;
         }
 
     }
 

@@ -919,19 +1179,26 @@
 
         static final int BUCKET = 2;
         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;
             STEPS[1] = 1;
 
             for (int i = 2; i < MAX; i++) {
                 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) {
             for (int i = 1; i < MAX; i++) {
                 if (val < STEPS[i]) {

@@ -967,10 +1234,41 @@
         @Override
         final void add(long val) {
             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);
             super.toString(sb).append(" { ");
 

@@ -993,40 +1291,90 @@
         return ((long) (1e3d * value)) / 1e3d;
     }
 
     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;
+    }
 }
< prev index next >