37 import java.util.Arrays; 38 import java.util.Iterator; 39 import java.util.Locale; 40 import java.util.Random; 41 import java.util.concurrent.atomic.AtomicBoolean; 42 import java.util.concurrent.atomic.AtomicInteger; 43 import java.util.logging.Handler; 44 import java.util.logging.LogRecord; 45 import java.util.logging.Logger; 46 import javax.imageio.IIOImage; 47 import javax.imageio.ImageIO; 48 import javax.imageio.ImageWriteParam; 49 import javax.imageio.ImageWriter; 50 import javax.imageio.stream.ImageOutputStream; 51 52 /** 53 * @test 54 * @bug 8191814 55 * @summary Verifies that Marlin rendering generates the same 56 * images with and without clipping optimization with all possible 57 * stroke (cap/join) and fill modes (EO rules) 58 * Note: Use the argument -slow to run more intensive tests (too much time) 59 * @run main/othervm/timeout=120 -Dsun.java2d.renderer=sun.java2d.marlin.MarlinRenderingEngine ClipShapeTest 60 * @run main/othervm/timeout=120 -Dsun.java2d.renderer=sun.java2d.marlin.DMarlinRenderingEngine ClipShapeTest 61 */ 62 public final class ClipShapeTest { 63 64 static final boolean TEST_STROKER = true; 65 static final boolean TEST_FILLER = true; 66 67 // complementary tests in slow mode: 68 static boolean USE_DASHES = false; 69 static boolean USE_VAR_STROKE = false; 70 71 static int NUM_TESTS = 5000; 72 static final int TESTW = 100; 73 static final int TESTH = 100; 74 75 // shape settings: 76 static final ShapeMode SHAPE_MODE = ShapeMode.NINE_LINE_POLYS; 77 static final boolean SHAPE_REPEAT = true; 78 79 // dump path on console: 80 static final boolean DUMP_SHAPE = true; 81 82 static final boolean SHOW_DETAILS = true; 83 static final boolean SHOW_OUTLINE = true; 84 static final boolean SHOW_POINTS = true; 85 static final boolean SHOW_INFO = false; 86 87 static final int MAX_SHOW_FRAMES = 10; 88 89 // use fixed seed to reproduce always same polygons between tests 90 static final boolean FIXED_SEED = false; 91 static final double RAND_SCALE = 3.0; 92 static final double RANDW = TESTW * RAND_SCALE; 93 static final double OFFW = (TESTW - RANDW) / 2.0; 94 static final double RANDH = TESTH * RAND_SCALE; 95 static final double OFFH = (TESTH - RANDH) / 2.0; 96 97 static enum ShapeMode { 98 TWO_CUBICS, 99 FOUR_QUADS, 100 FIVE_LINE_POLYS, 101 NINE_LINE_POLYS, 102 FIFTY_LINE_POLYS, 103 MIXED 104 } 105 106 static final long SEED = 1666133789L; 107 // Fixed seed to avoid any difference between runs: 108 static final Random RANDOM = new Random(SEED); 109 110 static final File OUTPUT_DIR = new File("."); 111 112 /** 113 * Test 114 * @param args 115 */ 116 public static void main(String[] args) { 117 boolean runSlowTests = (args.length != 0 && "-slow".equals(args[0])); 118 119 if (runSlowTests) { 120 NUM_TESTS = 20000; // or 100000 (very slow) 121 USE_DASHES = true; 122 USE_VAR_STROKE = true; 123 } 124 125 Locale.setDefault(Locale.US); 126 127 // Get Marlin runtime state from its log: 128 final AtomicBoolean isMarlin = new AtomicBoolean(); 129 final AtomicBoolean isClipRuntime = new AtomicBoolean(); 130 131 // initialize j.u.l Looger: 132 final Logger log = Logger.getLogger("sun.java2d.marlin"); 133 log.addHandler(new Handler() { 134 @Override 135 public void publish(LogRecord record) { 136 final String msg = record.getMessage(); 137 if (msg != null) { 138 // last space to avoid matching other settings: 139 if (msg.startsWith("sun.java2d.renderer ")) { 140 isMarlin.set(msg.contains("MarlinRenderingEngine")); 141 } 142 if (msg.startsWith("sun.java2d.renderer.clip.runtime.enable")) { 143 isClipRuntime.set(msg.contains("true")); 144 } 145 } 146 147 final Throwable th = record.getThrown(); 148 // detect any Throwable: 149 if (th != null) { 154 } 155 } 156 157 @Override 158 public void flush() { 159 } 160 161 @Override 162 public void close() throws SecurityException { 163 } 164 }); 165 166 // enable Marlin logging & internal checks: 167 System.setProperty("sun.java2d.renderer.log", "true"); 168 System.setProperty("sun.java2d.renderer.useLogger", "true"); 169 170 // disable static clipping setting: 171 System.setProperty("sun.java2d.renderer.clip", "false"); 172 System.setProperty("sun.java2d.renderer.clip.runtime.enable", "true"); 173 174 System.out.println("ClipShapeTests: image = " + TESTW + " x " + TESTH); 175 176 int failures = 0; 177 final long start = System.nanoTime(); 178 try { 179 // TODO: test affine transforms ? 180 181 if (TEST_STROKER) { 182 final float[][] dashArrays = (USE_DASHES) 183 ? new float[][]{null, new float[]{1f, 2f}} 184 : new float[][]{null}; 185 186 System.out.println("dashes: " + Arrays.toString(dashArrays)); 187 188 final float[] strokeWidths = (USE_VAR_STROKE) 189 ? new float[5] : new float[]{8f}; 190 191 int nsw = 0; 192 if (USE_VAR_STROKE) { 193 for (float width = 0.1f; width < 110f; width *= 5f) { 194 strokeWidths[nsw++] = width; 195 } 196 } else { 197 nsw = 1; 198 } 199 200 System.out.println("stroke widths: " + Arrays.toString(strokeWidths)); 201 202 // Stroker tests: 203 for (int w = 0; w < nsw; w++) { 204 final float width = strokeWidths[w]; 205 206 for (float[] dashes : dashArrays) { 207 208 for (int cap = 0; cap <= 2; cap++) { 209 273 for (int n = 0; n < NUM_TESTS; n++) { 274 genShape(p2d, ts); 275 276 // Runtime clip setting OFF: 277 paintShape(p2d, g2dOff, fill, false); 278 279 // Runtime clip setting ON: 280 paintShape(p2d, g2dOn, fill, true); 281 282 /* compute image difference if possible */ 283 diffImage = computeDiffImage(testCtx, imgOn, imgOff, imgDiff, globalCtx); 284 285 final String testName = "Setup_" + ts.id + "_test_" + n; 286 287 if (diffImage != null) { 288 nd++; 289 290 final double ratio = (100.0 * testCtx.histPix.count) / testCtx.histAll.count; 291 System.out.println("Diff ratio: " + testName + " = " + trimTo3Digits(ratio) + " %"); 292 293 if (false) { 294 saveImage(diffImage, OUTPUT_DIR, testName + "-diff.png"); 295 } 296 297 if (DUMP_SHAPE) { 298 dumpShape(p2d); 299 } 300 if (nd < MAX_SHOW_FRAMES) { 301 if (SHOW_DETAILS) { 302 paintShapeDetails(g2dOff, p2d); 303 paintShapeDetails(g2dOn, p2d); 304 } 305 306 saveImage(imgOff, OUTPUT_DIR, testName + "-off.png"); 307 saveImage(imgOn, OUTPUT_DIR, testName + "-on.png"); 308 saveImage(diffImage, OUTPUT_DIR, testName + "-diff.png"); 309 } 310 } 311 } 312 } finally { 313 g2dOff.dispose(); 314 g2dOn.dispose(); 315 316 if (nd != 0) { 317 System.out.println("paintPaths: " + NUM_TESTS + " paths - " 318 + "Number of differences = " + nd 319 + " ratio = " + (100f * nd) / NUM_TESTS + " %"); 320 } 321 322 globalCtx.dump(); 323 } 324 System.out.println("paintPaths: duration= " + (1e-6 * (System.nanoTime() - start)) + " ms."); 325 return nd; 326 } 327 328 private static void paintShape(final Path2D p2d, final Graphics2D g2d, 334 if (fill) { 335 g2d.fill(p2d); 336 } else { 337 g2d.draw(p2d); 338 } 339 } 340 341 private static Graphics2D initialize(final BufferedImage img, 342 final TestSetup ts) { 343 final Graphics2D g2d = (Graphics2D) img.getGraphics(); 344 g2d.setRenderingHint(RenderingHints.KEY_RENDERING, 345 RenderingHints.VALUE_RENDER_QUALITY); 346 g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, 347 RenderingHints.VALUE_STROKE_PURE); 348 349 if (ts.isStroke()) { 350 g2d.setStroke(createStroke(ts)); 351 } 352 g2d.setColor(Color.GRAY); 353 354 return g2d; 355 } 356 357 private static void reset(final Graphics2D g2d) { 358 // Disable antialiasing: 359 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 360 RenderingHints.VALUE_ANTIALIAS_OFF); 361 g2d.setBackground(Color.WHITE); 362 g2d.clearRect(0, 0, TESTW, TESTH); 363 } 364 365 private static void setClip(final Graphics2D g2d, final boolean clip) { 366 // Enable antialiasing: 367 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 368 RenderingHints.VALUE_ANTIALIAS_ON); 369 370 // Enable or Disable clipping: 371 System.setProperty("sun.java2d.renderer.clip.runtime", (clip) ? "true" : "false"); 372 } 373 453 g2d.setColor(COLOR_LINETO_ODD); 454 g2d.draw(shape); 455 } 456 457 final float[] coords = new float[6]; 458 float px, py; 459 460 int nMove = 0; 461 int nLine = 0; 462 int n = 0; 463 464 for (final PathIterator it = shape.getPathIterator(null); !it.isDone(); it.next()) { 465 int type = it.currentSegment(coords); 466 switch (type) { 467 case PathIterator.SEG_MOVETO: 468 if (SHOW_POINTS) { 469 g2d.setColor(COLOR_MOVETO); 470 } 471 break; 472 case PathIterator.SEG_LINETO: 473 if (SHOW_POINTS) { 474 g2d.setColor((nLine % 2 == 0) ? COLOR_LINETO_ODD : COLOR_LINETO_EVEN); 475 } 476 nLine++; 477 break; 478 case PathIterator.SEG_CLOSE: 479 continue; 480 default: 481 System.out.println("unsupported segment type= " + type); 482 continue; 483 } 484 px = coords[0]; 485 py = coords[1]; 486 487 if (SHOW_INFO) { 488 System.out.println("point[" + (n++) + "|seg=" + type + "]: " + px + " " + py); 489 } 490 491 if (SHOW_POINTS) { 492 ELL_POINT.setFrame(px - POINT_RADIUS, py - POINT_RADIUS, 498 System.out.println("Path moveTo=" + nMove + ", lineTo=" + nLine); 499 System.out.println("--------------------------------------------------"); 500 } 501 502 g2d.setStroke(oldStroke); 503 g2d.setColor(oldColor); 504 } 505 506 private static void dumpShape(final Shape shape) { 507 final float[] coords = new float[6]; 508 509 for (final PathIterator it = shape.getPathIterator(null); !it.isDone(); it.next()) { 510 final int type = it.currentSegment(coords); 511 switch (type) { 512 case PathIterator.SEG_MOVETO: 513 System.out.println("p2d.moveTo(" + coords[0] + ", " + coords[1] + ");"); 514 break; 515 case PathIterator.SEG_LINETO: 516 System.out.println("p2d.lineTo(" + coords[0] + ", " + coords[1] + ");"); 517 break; 518 case PathIterator.SEG_CLOSE: 519 System.out.println("p2d.closePath();"); 520 break; 521 default: 522 System.out.println("// Unsupported segment type= " + type); 523 } 524 } 525 System.out.println("--------------------------------------------------"); 526 } 527 528 static double randX() { 529 return RANDOM.nextDouble() * RANDW + OFFW; 530 } 531 532 static double randY() { 533 return RANDOM.nextDouble() * RANDH + OFFH; 534 } 535 536 private static BasicStroke createStroke(final TestSetup ts) { 537 return new BasicStroke(ts.strokeWidth, ts.strokeCap, ts.strokeJoin, 10.0f, ts.dashes, 0.0f); 563 this.dashes = dashes; 564 this.windingRule = Path2D.WIND_NON_ZERO; 565 } 566 567 TestSetup(ShapeMode shapeMode, final boolean closed, final int windingRule) { 568 this.id = COUNT.incrementAndGet(); 569 this.shapeMode = shapeMode; 570 this.closed = closed; 571 this.strokeWidth = 0f; 572 this.strokeCap = this.strokeJoin = -1; // invalid 573 this.dashes = null; 574 this.windingRule = windingRule; 575 } 576 577 boolean isStroke() { 578 return this.strokeWidth > 0f; 579 } 580 581 @Override 582 public String toString() { 583 return "TestSetup{id=" + id + ", shapeMode=" + shapeMode + ", closed=" + closed 584 + ", strokeWidth=" + strokeWidth + ", strokeCap=" + strokeCap + ", strokeJoin=" + strokeJoin 585 + ((dashes != null) ? ", dashes: " + Arrays.toString(dashes) : "") 586 + ", windingRule=" + windingRule + '}'; 587 } 588 } 589 590 // --- utilities --- 591 private static final int DCM_ALPHA_MASK = 0xff000000; 592 593 public static BufferedImage newImage(final int w, final int h) { 594 return new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB_PRE); 595 } 596 597 public static BufferedImage computeDiffImage(final DiffContext localCtx, 598 final BufferedImage tstImage, 599 final BufferedImage refImage, 600 final BufferedImage diffImage, 601 final DiffContext globalCtx) { 602 603 final int[] aRefPix = ((DataBufferInt) refImage.getRaster().getDataBuffer()).getData(); 604 final int[] aTstPix = ((DataBufferInt) tstImage.getRaster().getDataBuffer()).getData(); 605 final int[] aDifPix = ((DataBufferInt) diffImage.getRaster().getDataBuffer()).getData(); 606 607 // reset local diff context: 608 localCtx.reset(); 609 610 int ref, tst, dg, v; 611 for (int i = 0, len = aRefPix.length; i < len; i++) { 612 ref = aRefPix[i]; 613 tst = aTstPix[i]; 614 615 // grayscale diff: 616 dg = (r(ref) + g(ref) + b(ref)) - (r(tst) + g(tst) + b(tst)); 617 618 // max difference on grayscale values: 619 v = (int) Math.ceil(Math.abs(dg / 3.0)); 620 621 aDifPix[i] = toInt(v, v, v); 622 623 localCtx.add(v); 624 globalCtx.add(v); 625 } 626 627 if (!localCtx.isDiff()) { 628 return null; 629 } 630 631 return diffImage; 632 } 633 634 static void saveImage(final BufferedImage image, final File resDirectory, final String imageFileName) throws IOException { 635 final Iterator<ImageWriter> itWriters = ImageIO.getImageWritersByFormatName("PNG"); 636 if (itWriters.hasNext()) { 637 final ImageWriter writer = itWriters.next(); 638 639 final ImageWriteParam writerParams = writer.getDefaultWriteParam(); 640 writerParams.setProgressiveMode(ImageWriteParam.MODE_DISABLED); 641 642 final File imgFile = new File(resDirectory, imageFileName); 643 644 if (!imgFile.exists() || imgFile.canWrite()) { 645 System.out.println("saveImage: saving image as PNG [" + imgFile + "]..."); 646 imgFile.delete(); 647 648 // disable cache in temporary files: 649 ImageIO.setUseCache(false); | 37 import java.util.Arrays; 38 import java.util.Iterator; 39 import java.util.Locale; 40 import java.util.Random; 41 import java.util.concurrent.atomic.AtomicBoolean; 42 import java.util.concurrent.atomic.AtomicInteger; 43 import java.util.logging.Handler; 44 import java.util.logging.LogRecord; 45 import java.util.logging.Logger; 46 import javax.imageio.IIOImage; 47 import javax.imageio.ImageIO; 48 import javax.imageio.ImageWriteParam; 49 import javax.imageio.ImageWriter; 50 import javax.imageio.stream.ImageOutputStream; 51 52 /** 53 * @test 54 * @bug 8191814 55 * @summary Verifies that Marlin rendering generates the same 56 * images with and without clipping optimization with all possible 57 * stroke (cap/join) and/or dashes or fill modes (EO rules) 58 * for paths made of either 9 lines, 4 quads, 2 cubics (random) 59 * Note: Use the argument -slow to run more intensive tests (too much time) 60 * 61 * @run main/othervm/timeout=120 -Dsun.java2d.renderer=sun.java2d.marlin.MarlinRenderingEngine ClipShapeTest -poly 62 * @run main/othervm/timeout=240 -Dsun.java2d.renderer=sun.java2d.marlin.MarlinRenderingEngine ClipShapeTest -poly -doDash 63 * @run main/othervm/timeout=120 -Dsun.java2d.renderer=sun.java2d.marlin.MarlinRenderingEngine ClipShapeTest -cubic 64 * @run main/othervm/timeout=240 -Dsun.java2d.renderer=sun.java2d.marlin.MarlinRenderingEngine ClipShapeTest -cubic -doDash 65 * @run main/othervm/timeout=120 -Dsun.java2d.renderer=sun.java2d.marlin.DMarlinRenderingEngine ClipShapeTest -poly 66 * @run main/othervm/timeout=240 -Dsun.java2d.renderer=sun.java2d.marlin.DMarlinRenderingEngine ClipShapeTest -poly -doDash 67 * @run main/othervm/timeout=120 -Dsun.java2d.renderer=sun.java2d.marlin.DMarlinRenderingEngine ClipShapeTest -cubic 68 * @run main/othervm/timeout=240 -Dsun.java2d.renderer=sun.java2d.marlin.DMarlinRenderingEngine ClipShapeTest -cubic -doDash 69 */ 70 public final class ClipShapeTest { 71 72 static boolean TX_SCALE = false; 73 static boolean TX_SHEAR = false; 74 75 static final boolean TEST_STROKER = true; 76 static final boolean TEST_FILLER = true; 77 78 // complementary tests in slow mode: 79 static boolean USE_DASHES = false; 80 static boolean USE_VAR_STROKE = false; 81 82 static int NUM_TESTS = 5000; 83 static final int TESTW = 100; 84 static final int TESTH = 100; 85 86 // shape settings: 87 static ShapeMode SHAPE_MODE = ShapeMode.NINE_LINE_POLYS; 88 89 static int THRESHOLD_DELTA; 90 static long THRESHOLD_NBPIX; 91 92 static final boolean SHAPE_REPEAT = true; 93 94 // dump path on console: 95 static final boolean DUMP_SHAPE = true; 96 97 static final boolean SHOW_DETAILS = false; // disabled 98 static final boolean SHOW_OUTLINE = true; 99 static final boolean SHOW_POINTS = true; 100 static final boolean SHOW_INFO = false; 101 102 static final int MAX_SHOW_FRAMES = 10; 103 static final int MAX_SAVE_FRAMES = 100; 104 105 // use fixed seed to reproduce always same polygons between tests 106 static final boolean FIXED_SEED = false; 107 static final double RAND_SCALE = 3.0; 108 static final double RANDW = TESTW * RAND_SCALE; 109 static final double OFFW = (TESTW - RANDW) / 2.0; 110 static final double RANDH = TESTH * RAND_SCALE; 111 static final double OFFH = (TESTH - RANDH) / 2.0; 112 113 static enum ShapeMode { 114 TWO_CUBICS, 115 FOUR_QUADS, 116 FIVE_LINE_POLYS, 117 NINE_LINE_POLYS, 118 FIFTY_LINE_POLYS, 119 MIXED 120 } 121 122 static final long SEED = 1666133789L; 123 // Fixed seed to avoid any difference between runs: 124 static final Random RANDOM = new Random(SEED); 125 126 static final File OUTPUT_DIR = new File("."); 127 128 static final AtomicBoolean isMarlin = new AtomicBoolean(); 129 static final AtomicBoolean isClipRuntime = new AtomicBoolean(); 130 131 static { 132 Locale.setDefault(Locale.US); 133 134 // FIRST: Get Marlin runtime state from its log: 135 136 // initialize j.u.l Looger: 137 final Logger log = Logger.getLogger("sun.java2d.marlin"); 138 log.addHandler(new Handler() { 139 @Override 140 public void publish(LogRecord record) { 141 final String msg = record.getMessage(); 142 if (msg != null) { 143 // last space to avoid matching other settings: 144 if (msg.startsWith("sun.java2d.renderer ")) { 145 isMarlin.set(msg.contains("MarlinRenderingEngine")); 146 } 147 if (msg.startsWith("sun.java2d.renderer.clip.runtime.enable")) { 148 isClipRuntime.set(msg.contains("true")); 149 } 150 } 151 152 final Throwable th = record.getThrown(); 153 // detect any Throwable: 154 if (th != null) { 159 } 160 } 161 162 @Override 163 public void flush() { 164 } 165 166 @Override 167 public void close() throws SecurityException { 168 } 169 }); 170 171 // enable Marlin logging & internal checks: 172 System.setProperty("sun.java2d.renderer.log", "true"); 173 System.setProperty("sun.java2d.renderer.useLogger", "true"); 174 175 // disable static clipping setting: 176 System.setProperty("sun.java2d.renderer.clip", "false"); 177 System.setProperty("sun.java2d.renderer.clip.runtime.enable", "true"); 178 179 // enable subdivider: 180 System.setProperty("sun.java2d.renderer.clip.subdivider", "true"); 181 182 // disable min length check: always subdivide curves at clip edges 183 System.setProperty("sun.java2d.renderer.clip.subdivider.minLength", "-1"); 184 185 // If any curve, increase curve accuracy: 186 // curve length max error: 187 System.setProperty("sun.java2d.renderer.curve_len_err", "1e-4"); 188 189 // quad max error: 190 System.setProperty("sun.java2d.renderer.quad_dec_d2", "5e-4"); 191 192 // cubic min/max error: 193 System.setProperty("sun.java2d.renderer.cubic_dec_d2", "1e-3"); 194 System.setProperty("sun.java2d.renderer.cubic_inc_d1", "1e-4"); // or disabled ~ 1e-6 195 } 196 197 /** 198 * Test 199 * @param args 200 */ 201 public static void main(String[] args) { 202 boolean runSlowTests = false; 203 204 for (String arg : args) { 205 if ("-slow".equals(arg)) { 206 System.out.println("slow: enabled."); 207 runSlowTests = true; 208 } else if ("-doScale".equals(arg)) { 209 System.out.println("doScale: enabled."); 210 TX_SCALE = true; 211 } else if ("-doShear".equals(arg)) { 212 System.out.println("doShear: enabled."); 213 TX_SHEAR = true; 214 } else if ("-doDash".equals(arg)) { 215 System.out.println("doDash: enabled."); 216 USE_DASHES = true; 217 } else if ("-doVarStroke".equals(arg)) { 218 System.out.println("doVarStroke: enabled."); 219 USE_VAR_STROKE = true; 220 } 221 // shape mode: 222 else if (arg.equalsIgnoreCase("-poly")) { 223 SHAPE_MODE = ShapeMode.NINE_LINE_POLYS; 224 } else if (arg.equalsIgnoreCase("-bigpoly")) { 225 SHAPE_MODE = ShapeMode.FIFTY_LINE_POLYS; 226 } else if (arg.equalsIgnoreCase("-quad")) { 227 SHAPE_MODE = ShapeMode.FOUR_QUADS; 228 } else if (arg.equalsIgnoreCase("-cubic")) { 229 SHAPE_MODE = ShapeMode.TWO_CUBICS; 230 } else if (arg.equalsIgnoreCase("-mixed")) { 231 SHAPE_MODE = ShapeMode.MIXED; 232 } 233 } 234 235 System.out.println("Shape mode: " + SHAPE_MODE); 236 237 // adjust image comparison thresholds: 238 switch(SHAPE_MODE) { 239 case TWO_CUBICS: 240 // Define uncertainty for curves: 241 THRESHOLD_DELTA = 32; // / 256 242 THRESHOLD_NBPIX = 128; // / 10000 243 break; 244 case FOUR_QUADS: 245 case MIXED: 246 // Define uncertainty for quads: 247 // curve subdivision causes curves to be smaller 248 // then curve offsets are different (more accurate) 249 THRESHOLD_DELTA = 64; // 64 / 256 250 THRESHOLD_NBPIX = 256; // 256 / 10000 251 break; 252 default: 253 // Define uncertainty for lines: 254 // float variant have higher uncertainty 255 THRESHOLD_DELTA = 8; 256 THRESHOLD_NBPIX = 8; 257 } 258 259 System.out.println("THRESHOLD_DELTA: "+THRESHOLD_DELTA); 260 System.out.println("THRESHOLD_NBPIX: "+THRESHOLD_NBPIX); 261 262 if (runSlowTests) { 263 NUM_TESTS = 10000; // or 100000 (very slow) 264 USE_DASHES = true; 265 USE_VAR_STROKE = true; 266 } 267 268 System.out.println("ClipShapeTests: image = " + TESTW + " x " + TESTH); 269 270 int failures = 0; 271 final long start = System.nanoTime(); 272 try { 273 // TODO: test affine transforms ? 274 275 if (TEST_STROKER) { 276 final float[][] dashArrays = (USE_DASHES) ? 277 // small 278 // new float[][]{new float[]{1f, 2f}} 279 // normal 280 new float[][]{new float[]{13f, 7f}} 281 // large (prime) 282 // new float[][]{new float[]{41f, 7f}} 283 // none 284 : new float[][]{null}; 285 286 System.out.println("dashes: " + Arrays.deepToString(dashArrays)); 287 288 final float[] strokeWidths = (USE_VAR_STROKE) 289 ? new float[5] : 290 new float[]{10f}; 291 292 int nsw = 0; 293 if (USE_VAR_STROKE) { 294 for (float width = 0.1f; width < 110f; width *= 5f) { 295 strokeWidths[nsw++] = width; 296 } 297 } else { 298 nsw = 1; 299 } 300 301 System.out.println("stroke widths: " + Arrays.toString(strokeWidths)); 302 303 // Stroker tests: 304 for (int w = 0; w < nsw; w++) { 305 final float width = strokeWidths[w]; 306 307 for (float[] dashes : dashArrays) { 308 309 for (int cap = 0; cap <= 2; cap++) { 310 374 for (int n = 0; n < NUM_TESTS; n++) { 375 genShape(p2d, ts); 376 377 // Runtime clip setting OFF: 378 paintShape(p2d, g2dOff, fill, false); 379 380 // Runtime clip setting ON: 381 paintShape(p2d, g2dOn, fill, true); 382 383 /* compute image difference if possible */ 384 diffImage = computeDiffImage(testCtx, imgOn, imgOff, imgDiff, globalCtx); 385 386 final String testName = "Setup_" + ts.id + "_test_" + n; 387 388 if (diffImage != null) { 389 nd++; 390 391 final double ratio = (100.0 * testCtx.histPix.count) / testCtx.histAll.count; 392 System.out.println("Diff ratio: " + testName + " = " + trimTo3Digits(ratio) + " %"); 393 394 if (nd < MAX_SHOW_FRAMES) { 395 if (SHOW_DETAILS) { 396 paintShapeDetails(g2dOff, p2d); 397 paintShapeDetails(g2dOn, p2d); 398 } 399 400 if (nd < MAX_SAVE_FRAMES) { 401 if (DUMP_SHAPE) { 402 dumpShape(p2d); 403 } 404 saveImage(imgOff, OUTPUT_DIR, testName + "-off.png"); 405 saveImage(imgOn, OUTPUT_DIR, testName + "-on.png"); 406 saveImage(diffImage, OUTPUT_DIR, testName + "-diff.png"); 407 } 408 } 409 } 410 } 411 } finally { 412 g2dOff.dispose(); 413 g2dOn.dispose(); 414 415 if (nd != 0) { 416 System.out.println("paintPaths: " + NUM_TESTS + " paths - " 417 + "Number of differences = " + nd 418 + " ratio = " + (100f * nd) / NUM_TESTS + " %"); 419 } 420 421 globalCtx.dump(); 422 } 423 System.out.println("paintPaths: duration= " + (1e-6 * (System.nanoTime() - start)) + " ms."); 424 return nd; 425 } 426 427 private static void paintShape(final Path2D p2d, final Graphics2D g2d, 433 if (fill) { 434 g2d.fill(p2d); 435 } else { 436 g2d.draw(p2d); 437 } 438 } 439 440 private static Graphics2D initialize(final BufferedImage img, 441 final TestSetup ts) { 442 final Graphics2D g2d = (Graphics2D) img.getGraphics(); 443 g2d.setRenderingHint(RenderingHints.KEY_RENDERING, 444 RenderingHints.VALUE_RENDER_QUALITY); 445 g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, 446 RenderingHints.VALUE_STROKE_PURE); 447 448 if (ts.isStroke()) { 449 g2d.setStroke(createStroke(ts)); 450 } 451 g2d.setColor(Color.GRAY); 452 453 // Test scale 454 if (TX_SCALE) { 455 g2d.scale(1.2, 1.2); 456 } 457 // Test shear 458 if (TX_SHEAR) { 459 g2d.shear(0.1, 0.2); 460 } 461 462 return g2d; 463 } 464 465 private static void reset(final Graphics2D g2d) { 466 // Disable antialiasing: 467 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 468 RenderingHints.VALUE_ANTIALIAS_OFF); 469 g2d.setBackground(Color.WHITE); 470 g2d.clearRect(0, 0, TESTW, TESTH); 471 } 472 473 private static void setClip(final Graphics2D g2d, final boolean clip) { 474 // Enable antialiasing: 475 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 476 RenderingHints.VALUE_ANTIALIAS_ON); 477 478 // Enable or Disable clipping: 479 System.setProperty("sun.java2d.renderer.clip.runtime", (clip) ? "true" : "false"); 480 } 481 561 g2d.setColor(COLOR_LINETO_ODD); 562 g2d.draw(shape); 563 } 564 565 final float[] coords = new float[6]; 566 float px, py; 567 568 int nMove = 0; 569 int nLine = 0; 570 int n = 0; 571 572 for (final PathIterator it = shape.getPathIterator(null); !it.isDone(); it.next()) { 573 int type = it.currentSegment(coords); 574 switch (type) { 575 case PathIterator.SEG_MOVETO: 576 if (SHOW_POINTS) { 577 g2d.setColor(COLOR_MOVETO); 578 } 579 break; 580 case PathIterator.SEG_LINETO: 581 case PathIterator.SEG_QUADTO: 582 case PathIterator.SEG_CUBICTO: 583 if (SHOW_POINTS) { 584 g2d.setColor((nLine % 2 == 0) ? COLOR_LINETO_ODD : COLOR_LINETO_EVEN); 585 } 586 nLine++; 587 break; 588 case PathIterator.SEG_CLOSE: 589 continue; 590 default: 591 System.out.println("unsupported segment type= " + type); 592 continue; 593 } 594 px = coords[0]; 595 py = coords[1]; 596 597 if (SHOW_INFO) { 598 System.out.println("point[" + (n++) + "|seg=" + type + "]: " + px + " " + py); 599 } 600 601 if (SHOW_POINTS) { 602 ELL_POINT.setFrame(px - POINT_RADIUS, py - POINT_RADIUS, 608 System.out.println("Path moveTo=" + nMove + ", lineTo=" + nLine); 609 System.out.println("--------------------------------------------------"); 610 } 611 612 g2d.setStroke(oldStroke); 613 g2d.setColor(oldColor); 614 } 615 616 private static void dumpShape(final Shape shape) { 617 final float[] coords = new float[6]; 618 619 for (final PathIterator it = shape.getPathIterator(null); !it.isDone(); it.next()) { 620 final int type = it.currentSegment(coords); 621 switch (type) { 622 case PathIterator.SEG_MOVETO: 623 System.out.println("p2d.moveTo(" + coords[0] + ", " + coords[1] + ");"); 624 break; 625 case PathIterator.SEG_LINETO: 626 System.out.println("p2d.lineTo(" + coords[0] + ", " + coords[1] + ");"); 627 break; 628 case PathIterator.SEG_QUADTO: 629 System.out.println("p2d.quadTo(" + coords[0] + ", " + coords[1] + ", " + coords[2] + ", " + coords[3] + ");"); 630 break; 631 case PathIterator.SEG_CUBICTO: 632 System.out.println("p2d.curveTo(" + coords[0] + ", " + coords[1] + ", " + coords[2] + ", " + coords[3] + ", " + coords[4] + ", " + coords[5] + ");"); 633 break; 634 case PathIterator.SEG_CLOSE: 635 System.out.println("p2d.closePath();"); 636 break; 637 default: 638 System.out.println("// Unsupported segment type= " + type); 639 } 640 } 641 System.out.println("--------------------------------------------------"); 642 } 643 644 static double randX() { 645 return RANDOM.nextDouble() * RANDW + OFFW; 646 } 647 648 static double randY() { 649 return RANDOM.nextDouble() * RANDH + OFFH; 650 } 651 652 private static BasicStroke createStroke(final TestSetup ts) { 653 return new BasicStroke(ts.strokeWidth, ts.strokeCap, ts.strokeJoin, 10.0f, ts.dashes, 0.0f); 679 this.dashes = dashes; 680 this.windingRule = Path2D.WIND_NON_ZERO; 681 } 682 683 TestSetup(ShapeMode shapeMode, final boolean closed, final int windingRule) { 684 this.id = COUNT.incrementAndGet(); 685 this.shapeMode = shapeMode; 686 this.closed = closed; 687 this.strokeWidth = 0f; 688 this.strokeCap = this.strokeJoin = -1; // invalid 689 this.dashes = null; 690 this.windingRule = windingRule; 691 } 692 693 boolean isStroke() { 694 return this.strokeWidth > 0f; 695 } 696 697 @Override 698 public String toString() { 699 if (isStroke()) { 700 return "TestSetup{id=" + id + ", shapeMode=" + shapeMode + ", closed=" + closed 701 + ", strokeWidth=" + strokeWidth + ", strokeCap=" + getCap(strokeCap) + ", strokeJoin=" + getJoin(strokeJoin) 702 + ((dashes != null) ? ", dashes: " + Arrays.toString(dashes) : "") 703 + '}'; 704 } 705 return "TestSetup{id=" + id + ", shapeMode=" + shapeMode + ", closed=" + closed 706 + ", fill" 707 + ", windingRule=" + getWindingRule(windingRule) + '}'; 708 } 709 710 private static String getCap(final int cap) { 711 switch (cap) { 712 case BasicStroke.CAP_BUTT: 713 return "CAP_BUTT"; 714 case BasicStroke.CAP_ROUND: 715 return "CAP_ROUND"; 716 case BasicStroke.CAP_SQUARE: 717 return "CAP_SQUARE"; 718 default: 719 return ""; 720 } 721 722 } 723 724 private static String getJoin(final int join) { 725 switch (join) { 726 case BasicStroke.JOIN_MITER: 727 return "JOIN_MITER"; 728 case BasicStroke.JOIN_ROUND: 729 return "JOIN_ROUND"; 730 case BasicStroke.JOIN_BEVEL: 731 return "JOIN_BEVEL"; 732 default: 733 return ""; 734 } 735 736 } 737 738 private static String getWindingRule(final int rule) { 739 switch (rule) { 740 case PathIterator.WIND_EVEN_ODD: 741 return "WIND_EVEN_ODD"; 742 case PathIterator.WIND_NON_ZERO: 743 return "WIND_NON_ZERO"; 744 default: 745 return ""; 746 } 747 } 748 } 749 750 // --- utilities --- 751 private static final int DCM_ALPHA_MASK = 0xff000000; 752 753 public static BufferedImage newImage(final int w, final int h) { 754 return new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB_PRE); 755 } 756 757 public static BufferedImage computeDiffImage(final DiffContext localCtx, 758 final BufferedImage tstImage, 759 final BufferedImage refImage, 760 final BufferedImage diffImage, 761 final DiffContext globalCtx) { 762 763 final int[] aRefPix = ((DataBufferInt) refImage.getRaster().getDataBuffer()).getData(); 764 final int[] aTstPix = ((DataBufferInt) tstImage.getRaster().getDataBuffer()).getData(); 765 final int[] aDifPix = ((DataBufferInt) diffImage.getRaster().getDataBuffer()).getData(); 766 767 // reset local diff context: 768 localCtx.reset(); 769 770 int ref, tst, dg, v; 771 for (int i = 0, len = aRefPix.length; i < len; i++) { 772 ref = aRefPix[i]; 773 tst = aTstPix[i]; 774 775 // grayscale diff: 776 dg = (r(ref) + g(ref) + b(ref)) - (r(tst) + g(tst) + b(tst)); 777 778 // max difference on grayscale values: 779 v = (int) Math.ceil(Math.abs(dg / 3.0)); 780 781 // TODO: count warnings 782 if (v <= THRESHOLD_DELTA) { 783 aDifPix[i] = 0; 784 } else { 785 aDifPix[i] = toInt(v, v, v); 786 787 localCtx.add(v); 788 } 789 globalCtx.add(v); 790 } 791 792 if (!localCtx.isDiff() || (localCtx.histPix.count <= THRESHOLD_NBPIX)) { 793 return null; 794 } 795 796 localCtx.dump(); 797 798 return diffImage; 799 } 800 801 static void saveImage(final BufferedImage image, final File resDirectory, final String imageFileName) throws IOException { 802 final Iterator<ImageWriter> itWriters = ImageIO.getImageWritersByFormatName("PNG"); 803 if (itWriters.hasNext()) { 804 final ImageWriter writer = itWriters.next(); 805 806 final ImageWriteParam writerParams = writer.getDefaultWriteParam(); 807 writerParams.setProgressiveMode(ImageWriteParam.MODE_DISABLED); 808 809 final File imgFile = new File(resDirectory, imageFileName); 810 811 if (!imgFile.exists() || imgFile.canWrite()) { 812 System.out.println("saveImage: saving image as PNG [" + imgFile + "]..."); 813 imgFile.delete(); 814 815 // disable cache in temporary files: 816 ImageIO.setUseCache(false); |