1 /*
   2  * Copyright (c) 2008, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * @test
  26  * @bug 6766342
  27  * @summary Tests clipping invariance for AA rectangle and line primitives
  28  * @run main RenderClipTest -strict -readfile 6766342.tests
  29  * @run main RenderClipTest -rectsuite -count 10
  30  */
  31 
  32 import java.awt.*;
  33 import java.awt.geom.*;
  34 import java.awt.image.*;
  35 import java.awt.event.*;
  36 import java.util.Vector;
  37 import java.io.*;
  38 
  39 public class RenderClipTest {
  40     public static double randDblCoord() {
  41         return Math.random()*60 - 10;
  42     }
  43 
  44     public static float randFltCoord() {
  45         return (float) randDblCoord();
  46     }
  47 
  48     public static int randIntCoord() {
  49         return (int) Math.round(randDblCoord());
  50     }
  51 
  52     public static int randInt(int n) {
  53         return ((int) (Math.random() * (n*4))) >> 2;
  54     }
  55 
  56     static int numtests;
  57     static int numerrors;
  58     static int numfillfailures;
  59     static int numstrokefailures;
  60     static int maxerr;
  61 
  62     static boolean useAA;
  63     static boolean strokePure;
  64     static boolean testFill;
  65     static boolean testDraw;
  66     static boolean silent;
  67     static boolean verbose;
  68     static boolean strict;
  69     static boolean showErrors;
  70     static float lw;
  71     static double rot;
  72 
  73     static BufferedImage imgref;
  74     static BufferedImage imgtst;
  75 
  76     static Graphics2D grefclear;
  77     static Graphics2D gtstclear;
  78     static Graphics2D grefrender;
  79     static Graphics2D gtstrender;
  80 
  81     public static abstract class AnnotatedRenderOp {
  82         public static AnnotatedRenderOp parse(String str) {
  83             AnnotatedRenderOp ar;
  84             if (((ar = Cubic.tryparse(str)) != null) ||
  85                 ((ar = Quad.tryparse(str)) != null) ||
  86                 ((ar = Poly.tryparse(str)) != null) ||
  87                 ((ar = Path.tryparse(str)) != null) ||
  88                 ((ar = Rect.tryparse(str)) != null) ||
  89                 ((ar = Line.tryparse(str)) != null) ||
  90                 ((ar = RectMethod.tryparse(str)) != null) ||
  91                 ((ar = LineMethod.tryparse(str)) != null))
  92             {
  93                 return ar;
  94             }
  95             System.err.println("Unable to parse shape: "+str);
  96             return null;
  97         }
  98 
  99         public abstract void randomize();
 100 
 101         public abstract void fill(Graphics2D g2d);
 102 
 103         public abstract void draw(Graphics2D g2d);
 104     }
 105 
 106     public static abstract class AnnotatedShapeOp extends AnnotatedRenderOp {
 107         public abstract Shape getShape();
 108 
 109         public void fill(Graphics2D g2d) {
 110             g2d.fill(getShape());
 111         }
 112 
 113         public void draw(Graphics2D g2d) {
 114             g2d.draw(getShape());
 115         }
 116     }
 117 
 118     public static void usage(String err) {
 119         if (err != null) {
 120             System.err.println(err);
 121         }
 122         System.err.println("usage: java RenderClipTest "+
 123                            "[-read[file F]] [-rectsuite] [-fill] [-draw]");
 124         System.err.println("                           "+
 125                            "[-aa] [-pure] [-lw N] [-rot N]");
 126         System.err.println("                           "+
 127                            "[-rectmethod] [-linemethod] [-rect] [-line]");
 128         System.err.println("                           "+
 129                            "[-cubic] [-quad] [-poly] [-path]");
 130         System.err.println("                           "+
 131                            "[-silent] [-verbose] [-showerr] [-count N]");
 132         System.err.println("                           "+
 133                            "[-strict] [-usage]");
 134         System.err.println("    -read         Read test data from stdin");
 135         System.err.println("    -readfile F   Read test data from file F");
 136         System.err.println("    -rectsuite    Run a suite of rect/line tests");
 137         System.err.println("    -fill         Test g.fill*(...)");
 138         System.err.println("    -draw         Test g.draw*(...)");
 139         System.err.println("    -aa           Use antialiased rendering");
 140         System.err.println("    -pure         Use STROKE_PURE hint");
 141         System.err.println("    -lw N         Test line widths of N "+
 142                            "(default 1.0)");
 143         System.err.println("    -rot N        Test rotation by N degrees "+
 144                            "(default 0.0)");
 145         System.err.println("    -rectmethod   Test fillRect/drawRect methods");
 146         System.err.println("    -linemethod   Test drawLine method");
 147         System.err.println("    -rect         Test Rectangle2D shapes");
 148         System.err.println("    -line         Test Line2D shapes");
 149         System.err.println("    -cubic        Test CubicCurve2D shapes");
 150         System.err.println("    -quad         Test QuadCurve2D shapes");
 151         System.err.println("    -poly         Test Polygon shapes");
 152         System.err.println("    -path         Test GeneralPath shapes");
 153         System.err.println("    -silent       Do not print out error curves");
 154         System.err.println("    -verbose      Print out progress info");
 155         System.err.println("    -showerr      Display errors on screen");
 156         System.err.println("    -count N      N tests per shape, then exit "+
 157                            "(default 1000)");
 158         System.err.println("    -strict       All failures are important");
 159         System.err.println("    -usage        Print this help, then exit");
 160         System.exit((err != null) ? -1 : 0);
 161     }
 162 
 163     public static void main(String argv[]) {
 164         boolean readTests = false;
 165         String readFile = null;
 166         boolean rectsuite = false;
 167         int count = 1000;
 168         lw = 1.0f;
 169         rot = 0.0;
 170         Vector<AnnotatedRenderOp> testOps = new Vector<AnnotatedRenderOp>();
 171         for (int i = 0; i < argv.length; i++) {
 172             String arg = argv[i].toLowerCase();
 173             if (arg.equals("-aa")) {
 174                 useAA = true;
 175             } else if (arg.equals("-pure")) {
 176                 strokePure = true;
 177             } else if (arg.equals("-fill")) {
 178                 testFill = true;
 179             } else if (arg.equals("-draw")) {
 180                 testDraw = true;
 181             } else if (arg.equals("-lw")) {
 182                 if (i+1 >= argv.length) {
 183                     usage("Missing argument: "+argv[i]);
 184                 }
 185                 lw = Float.parseFloat(argv[++i]);
 186             } else if (arg.equals("-rot")) {
 187                 if (i+1 >= argv.length) {
 188                     usage("Missing argument: "+argv[i]);
 189                 }
 190                 rot = Double.parseDouble(argv[++i]);
 191             } else if (arg.equals("-cubic")) {
 192                 testOps.add(new Cubic());
 193             } else if (arg.equals("-quad")) {
 194                 testOps.add(new Quad());
 195             } else if (arg.equals("-poly")) {
 196                 testOps.add(new Poly());
 197             } else if (arg.equals("-path")) {
 198                 testOps.add(new Path());
 199             } else if (arg.equals("-rect")) {
 200                 testOps.add(new Rect());
 201             } else if (arg.equals("-line")) {
 202                 testOps.add(new Line());
 203             } else if (arg.equals("-rectmethod")) {
 204                 testOps.add(new RectMethod());
 205             } else if (arg.equals("-linemethod")) {
 206                 testOps.add(new LineMethod());
 207             } else if (arg.equals("-verbose")) {
 208                 verbose = true;
 209             } else if (arg.equals("-strict")) {
 210                 strict = true;
 211             } else if (arg.equals("-silent")) {
 212                 silent = true;
 213             } else if (arg.equals("-showerr")) {
 214                 showErrors = true;
 215             } else if (arg.equals("-readfile")) {
 216                 if (i+1 >= argv.length) {
 217                     usage("Missing argument: "+argv[i]);
 218                 }
 219                 readTests = true;
 220                 readFile = argv[++i];
 221             } else if (arg.equals("-read")) {
 222                 readTests = true;
 223                 readFile = null;
 224             } else if (arg.equals("-rectsuite")) {
 225                 rectsuite = true;
 226             } else if (arg.equals("-count")) {
 227                 if (i+1 >= argv.length) {
 228                     usage("Missing argument: "+argv[i]);
 229                 }
 230                 count = Integer.parseInt(argv[++i]);
 231             } else if (arg.equals("-usage")) {
 232                 usage(null);
 233             } else {
 234                 usage("Unknown argument: "+argv[i]);
 235             }
 236         }
 237         if (readTests) {
 238             if (rectsuite || testDraw || testFill ||
 239                 useAA || strokePure ||
 240                 lw != 1.0f || rot != 0.0 ||
 241                 testOps.size() > 0)
 242             {
 243                 usage("Should not specify test types with -read options");
 244             }
 245         } else if (rectsuite) {
 246             if (testDraw || testFill ||
 247                 useAA || strokePure ||
 248                 lw != 1.0f || rot != 0.0 ||
 249                 testOps.size() > 0)
 250             {
 251                 usage("Should not specify test types with -rectsuite option");
 252             }
 253         } else {
 254             if (!testDraw && !testFill) {
 255                 usage("No work: Must specify one or both of "+
 256                       "-fill or -draw");
 257             }
 258             if (testOps.size() == 0) {
 259                 usage("No work: Must specify one or more of "+
 260                       "-rect[method], -line[method], "+
 261                       "-cubic, -quad, -poly, or -path");
 262             }
 263         }
 264         initImages();
 265         if (readTests) {
 266             try {
 267                 InputStream is;
 268                 if (readFile == null) {
 269                     is = System.in;
 270                 } else {
 271                     File f =
 272                         new File(System.getProperty("test.src", "."),
 273                                  readFile);
 274                     is = new FileInputStream(f);
 275                 }
 276                 parseAndRun(is);
 277             } catch (IOException e) {
 278                 throw new RuntimeException(e);
 279             }
 280         } else if (rectsuite) {
 281             runRectSuite(count);
 282         } else {
 283             initGCs();
 284             for (int k = 0; k < testOps.size(); k++) {
 285                 AnnotatedRenderOp ar = testOps.get(k);
 286                 runRandomTests(ar, count);
 287             }
 288             disposeGCs();
 289         }
 290         grefclear.dispose();
 291         gtstclear.dispose();
 292         grefclear = gtstclear = null;
 293         reportStatistics();
 294     }
 295 
 296     public static int reportStatistics() {
 297         String connector = "";
 298         if (numfillfailures > 0) {
 299             System.out.print(numfillfailures+" fills ");
 300             connector = "and ";
 301         }
 302         if (numstrokefailures > 0) {
 303             System.out.print(connector+numstrokefailures+" strokes ");
 304         }
 305         int totalfailures = numfillfailures + numstrokefailures;
 306         if (totalfailures == 0) {
 307             System.out.print("0 ");
 308         }
 309         System.out.println("out of "+numtests+" tests failed...");
 310         int critical = numerrors;
 311         if (strict) {
 312             critical += totalfailures;
 313         }
 314         if (critical > 0) {
 315             throw new RuntimeException(critical+" tests had critical errors");
 316         }
 317         System.out.println("No tests had critical errors");
 318         return (numerrors+totalfailures);
 319     }
 320 
 321     public static void runRectSuite(int count) {
 322         AnnotatedRenderOp ops[] = {
 323             new Rect(),
 324             new RectMethod(),
 325             new Line(),
 326             new LineMethod(),
 327         };
 328         // Sometimes different fill algorithms are chosen for
 329         // thin and wide line modes, make sure we test both...
 330         float filllinewidths[] = { 0.0f, 2.0f };
 331         float drawlinewidths[] = { 0.0f, 0.5f, 1.0f,
 332                                    2.0f, 2.5f,
 333                                    5.0f, 5.3f };
 334         double rotations[] = { 0.0, 15.0, 90.0,
 335                                135.0, 180.0,
 336                                200.0, 270.0,
 337                                300.0};
 338         for (AnnotatedRenderOp ar: ops) {
 339             for (double r: rotations) {
 340                 rot = r;
 341                 for (int i = 0; i < 8; i++) {
 342                     float linewidths[];
 343                     if ((i & 1) == 0) {
 344                         if ((ar instanceof Line) ||
 345                             (ar instanceof LineMethod))
 346                         {
 347                             continue;
 348                         }
 349                         testFill = true;
 350                         testDraw = false;
 351                         linewidths = filllinewidths;
 352                     } else {
 353                         testFill = false;
 354                         testDraw = true;
 355                         linewidths = drawlinewidths;
 356                     }
 357                     useAA = ((i & 2) != 0);
 358                     strokePure = ((i & 4) != 0);
 359                     for (float w : linewidths) {
 360                         lw = w;
 361                         runSuiteTests(ar, count);
 362                     }
 363                 }
 364             }
 365         }
 366     }
 367 
 368     public static void runSuiteTests(AnnotatedRenderOp ar, int count) {
 369         if (verbose) {
 370             System.out.print("Running ");
 371             System.out.print(testFill ? "Fill " : "Draw ");
 372             System.out.print(BaseName(ar));
 373             if (useAA) {
 374                 System.out.print(" AA");
 375             }
 376             if (strokePure) {
 377                 System.out.print(" Pure");
 378             }
 379             if (lw != 1.0f) {
 380                 System.out.print(" lw="+lw);
 381             }
 382             if (rot != 0.0f) {
 383                 System.out.print(" rot="+rot);
 384             }
 385             System.out.println();
 386         }
 387         initGCs();
 388         runRandomTests(ar, count);
 389         disposeGCs();
 390     }
 391 
 392     public static String BaseName(AnnotatedRenderOp ar) {
 393         String s = ar.toString();
 394         int leftparen = s.indexOf('(');
 395         if (leftparen >= 0) {
 396             s = s.substring(0, leftparen);
 397         }
 398         return s;
 399     }
 400 
 401     public static void runRandomTests(AnnotatedRenderOp ar, int count) {
 402         for (int i = 0; i < count; i++) {
 403             ar.randomize();
 404             if (testDraw) {
 405                 test(ar, false);
 406             }
 407             if (testFill) {
 408                 test(ar, true);
 409             }
 410         }
 411     }
 412 
 413     public static void initImages() {
 414         imgref = new BufferedImage(40, 40, BufferedImage.TYPE_INT_RGB);
 415         imgtst = new BufferedImage(40, 40, BufferedImage.TYPE_INT_RGB);
 416         grefclear = imgref.createGraphics();
 417         gtstclear = imgtst.createGraphics();
 418         grefclear.setColor(Color.white);
 419         gtstclear.setColor(Color.white);
 420     }
 421 
 422     public static void initGCs() {
 423         grefrender = imgref.createGraphics();
 424         gtstrender = imgtst.createGraphics();
 425         gtstrender.clipRect(10, 10, 20, 20);
 426         grefrender.setColor(Color.blue);
 427         gtstrender.setColor(Color.blue);
 428         if (lw != 1.0f) {
 429             BasicStroke bs = new BasicStroke(lw);
 430             grefrender.setStroke(bs);
 431             gtstrender.setStroke(bs);
 432         }
 433         if (rot != 0.0) {
 434             double rotrad = Math.toRadians(rot);
 435             grefrender.rotate(rotrad, 20, 20);
 436             gtstrender.rotate(rotrad, 20, 20);
 437         }
 438         if (strokePure) {
 439             grefrender.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
 440                                         RenderingHints.VALUE_STROKE_PURE);
 441             gtstrender.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
 442                                         RenderingHints.VALUE_STROKE_PURE);
 443         }
 444         if (useAA) {
 445             grefrender.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
 446                                         RenderingHints.VALUE_ANTIALIAS_ON);
 447             gtstrender.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
 448                                         RenderingHints.VALUE_ANTIALIAS_ON);
 449             maxerr = 1;
 450         }
 451     }
 452 
 453     public static void disposeGCs() {
 454         grefrender.dispose();
 455         gtstrender.dispose();
 456         grefrender = gtstrender = null;
 457     }
 458 
 459     public static void parseAndRun(InputStream in) throws IOException {
 460         BufferedReader br = new BufferedReader(new InputStreamReader(in));
 461         String str;
 462         while ((str = br.readLine()) != null) {
 463             if (str.startsWith("Stroked ") || str.startsWith("Filled ")) {
 464                 parseTest(str);
 465                 continue;
 466             }
 467             if (str.startsWith("Running ")) {
 468                 continue;
 469             }
 470             if (str.startsWith("Failed: ")) {
 471                 continue;
 472             }
 473             if (str.indexOf(" out of ") > 0 &&
 474                 str.indexOf(" tests failed...") > 0)
 475             {
 476                 continue;
 477             }
 478             if (str.indexOf(" tests had critical errors") > 0) {
 479                 continue;
 480             }
 481             System.err.println("Unparseable line: "+str);
 482         }
 483     }
 484 
 485     public static void parseTest(String origstr) {
 486         String str = origstr;
 487         boolean isfill = false;
 488         useAA = strokePure = false;
 489         lw = 1.0f;
 490         rot = 0.0;
 491         if (str.startsWith("Stroked ")) {
 492             str = str.substring(8);
 493             isfill = false;
 494         } else if (str.startsWith("Filled ")) {
 495             str = str.substring(7);
 496             isfill = true;
 497         } else {
 498             System.err.println("Unparseable test line: "+origstr);
 499         }
 500         if (str.startsWith("AA ")) {
 501             str = str.substring(3);
 502             useAA = true;
 503         }
 504         if (str.startsWith("Pure ")) {
 505             str = str.substring(5);
 506             strokePure = true;
 507         }
 508         if (str.startsWith("Lw=")) {
 509             int index = str.indexOf(' ', 3);
 510             if (index > 0) {
 511                 lw = Float.parseFloat(str.substring(3, index));
 512                 str = str.substring(index+1);
 513             }
 514         }
 515         if (str.startsWith("Rot=")) {
 516             int index = str.indexOf(' ', 4);
 517             if (index > 0) {
 518                 rot = Double.parseDouble(str.substring(4, index));
 519                 str = str.substring(index+1);
 520             }
 521         }
 522         AnnotatedRenderOp ar = AnnotatedRenderOp.parse(str);
 523         if (ar != null) {
 524             initGCs();
 525             test(ar, isfill);
 526             disposeGCs();
 527         } else {
 528             System.err.println("Unparseable test line: "+origstr);
 529         }
 530     }
 531 
 532     public static void test(AnnotatedRenderOp ar, boolean isfill) {
 533         grefclear.fillRect(0, 0, 40, 40);
 534         gtstclear.fillRect(0, 0, 40, 40);
 535         if (isfill) {
 536             ar.fill(grefrender);
 537             ar.fill(gtstrender);
 538         } else {
 539             ar.draw(grefrender);
 540             ar.draw(gtstrender);
 541         }
 542         check(imgref, imgtst, ar, isfill);
 543     }
 544 
 545     public static int[] getData(BufferedImage img) {
 546         Raster r = img.getRaster();
 547         DataBufferInt dbi = (DataBufferInt) r.getDataBuffer();
 548         return dbi.getData();
 549     }
 550 
 551     public static int getScan(BufferedImage img) {
 552         Raster r = img.getRaster();
 553         SinglePixelPackedSampleModel sppsm =
 554             (SinglePixelPackedSampleModel) r.getSampleModel();
 555         return sppsm.getScanlineStride();
 556     }
 557 
 558     public static int getOffset(BufferedImage img) {
 559         Raster r = img.getRaster();
 560         SinglePixelPackedSampleModel sppsm =
 561             (SinglePixelPackedSampleModel) r.getSampleModel();
 562         return sppsm.getOffset(-r.getSampleModelTranslateX(),
 563                                -r.getSampleModelTranslateY());
 564     }
 565 
 566     final static int opaque = 0xff000000;
 567     final static int whitergb = Color.white.getRGB();
 568 
 569     public static final int maxdiff(int rgb1, int rgb2) {
 570         int maxd = 0;
 571         for (int i = 0; i < 32; i += 8) {
 572             int c1 = (rgb1 >> i) & 0xff;
 573             int c2 = (rgb2 >> i) & 0xff;
 574             int d = Math.abs(c1-c2);
 575             if (maxd < d) {
 576                 maxd = d;
 577             }
 578         }
 579         return maxd;
 580     }
 581 
 582     public static void check(BufferedImage imgref, BufferedImage imgtst,
 583                              AnnotatedRenderOp ar, boolean wasfill)
 584     {
 585         numtests++;
 586         int dataref[] = getData(imgref);
 587         int datatst[] = getData(imgtst);
 588         int scanref = getScan(imgref);
 589         int scantst = getScan(imgtst);
 590         int offref = getOffset(imgref);
 591         int offtst = getOffset(imgtst);
 592 
 593         // We want to check for errors outside the clip at a higher
 594         // priority than errors involving different pixels touched
 595         // inside the clip.
 596 
 597         // Check above clip
 598         if (check(ar, wasfill,
 599                   null, 0, 0,
 600                   datatst, scantst, offtst,
 601                   0, 0, 40, 10))
 602         {
 603             return;
 604         }
 605         // Check below clip
 606         if (check(ar, wasfill,
 607                   null, 0, 0,
 608                   datatst, scantst, offtst,
 609                   0, 30, 40, 40))
 610         {
 611             return;
 612         }
 613         // Check left of clip
 614         if (check(ar, wasfill,
 615                   null, 0, 0,
 616                   datatst, scantst, offtst,
 617                   0, 10, 10, 30))
 618         {
 619             return;
 620         }
 621         // Check right of clip
 622         if (check(ar, wasfill,
 623                   null, 0, 0,
 624                   datatst, scantst, offtst,
 625                   30, 10, 40, 30))
 626         {
 627             return;
 628         }
 629         // Check inside clip
 630         check(ar, wasfill,
 631               dataref, scanref, offref,
 632               datatst, scantst, offtst,
 633               10, 10, 30, 30);
 634     }
 635 
 636     public static boolean check(AnnotatedRenderOp ar, boolean wasfill,
 637                                 int dataref[], int scanref, int offref,
 638                                 int datatst[], int scantst, int offtst,
 639                                 int x0, int y0, int x1, int y1)
 640     {
 641         offref += scanref * y0;
 642         offtst += scantst * y0;
 643         for (int y = y0; y < y1; y++) {
 644             for (int x = x0; x < x1; x++) {
 645                 boolean failed;
 646                 String reason;
 647                 int rgbref;
 648                 int rgbtst;
 649 
 650                 rgbtst = datatst[offtst+x] | opaque;
 651                 if (dataref == null) {
 652                     /* Outside of clip, must be white, no error tolerance */
 653                     rgbref = whitergb;
 654                     failed = (rgbtst != rgbref);
 655                     reason = "stray pixel rendered outside of clip";
 656                 } else {
 657                     /* Inside of clip, check for maxerr delta in components */
 658                     rgbref = dataref[offref+x] | opaque;
 659                     failed = (rgbref != rgbtst &&
 660                               maxdiff(rgbref, rgbtst) > maxerr);
 661                     reason = "different pixel rendered inside clip";
 662                 }
 663                 if (failed) {
 664                     if (dataref == null) {
 665                         numerrors++;
 666                     }
 667                     if (wasfill) {
 668                         numfillfailures++;
 669                     } else {
 670                         numstrokefailures++;
 671                     }
 672                     if (!silent) {
 673                         System.out.println("Failed: "+reason+" at "+x+", "+y+
 674                                            " ["+Integer.toHexString(rgbref)+
 675                                            " != "+Integer.toHexString(rgbtst)+
 676                                            "]");
 677                         System.out.print(wasfill ? "Filled " : "Stroked ");
 678                         if (useAA) System.out.print("AA ");
 679                         if (strokePure) System.out.print("Pure ");
 680                         if (lw != 1) System.out.print("Lw="+lw+" ");
 681                         if (rot != 0) System.out.print("Rot="+rot+" ");
 682                         System.out.println(ar);
 683                     }
 684                     if (showErrors) {
 685                         show(imgref, imgtst);
 686                     }
 687                     return true;
 688                 }
 689             }
 690             offref += scanref;
 691             offtst += scantst;
 692         }
 693         return false;
 694     }
 695 
 696     static ErrorWindow errw;
 697 
 698     public static void show(BufferedImage imgref, BufferedImage imgtst) {
 699         ErrorWindow errw = new ErrorWindow();
 700         errw.setImages(imgref, imgtst);
 701         errw.setVisible(true);
 702         errw.waitForHide();
 703         errw.dispose();
 704     }
 705 
 706     public static class Cubic extends AnnotatedShapeOp {
 707         public static Cubic tryparse(String str) {
 708             str = str.trim();
 709             if (!str.startsWith("Cubic(")) {
 710                 return null;
 711             }
 712             str = str.substring(6);
 713             double coords[] = new double[8];
 714             boolean foundparen = false;
 715             for (int i = 0; i < coords.length; i++) {
 716                 int index = str.indexOf(",");
 717                 if (index < 0) {
 718                     if (i < coords.length-1) {
 719                         return null;
 720                     }
 721                     index = str.indexOf(")");
 722                     if (index < 0) {
 723                         return null;
 724                     }
 725                     foundparen = true;
 726                 }
 727                 String num = str.substring(0, index);
 728                 try {
 729                     coords[i] = Double.parseDouble(num);
 730                 } catch (NumberFormatException nfe) {
 731                     return null;
 732                 }
 733                 str = str.substring(index+1);
 734             }
 735             if (!foundparen || str.length() > 0) {
 736                 return null;
 737             }
 738             Cubic c = new Cubic();
 739             c.cubic.setCurve(coords[0], coords[1],
 740                              coords[2], coords[3],
 741                              coords[4], coords[5],
 742                              coords[6], coords[7]);
 743             return c;
 744         }
 745 
 746         private CubicCurve2D cubic = new CubicCurve2D.Double();
 747 
 748         public void randomize() {
 749             cubic.setCurve(randDblCoord(), randDblCoord(),
 750                            randDblCoord(), randDblCoord(),
 751                            randDblCoord(), randDblCoord(),
 752                            randDblCoord(), randDblCoord());
 753         }
 754 
 755         public Shape getShape() {
 756             return cubic;
 757         }
 758 
 759         public String toString() {
 760             return ("Cubic("+
 761                     cubic.getX1()+", "+
 762                     cubic.getY1()+", "+
 763                     cubic.getCtrlX1()+", "+
 764                     cubic.getCtrlY1()+", "+
 765                     cubic.getCtrlX2()+", "+
 766                     cubic.getCtrlY2()+", "+
 767                     cubic.getX2()+", "+
 768                     cubic.getY2()
 769                     +")");
 770         }
 771     }
 772 
 773     public static class Quad extends AnnotatedShapeOp {
 774         public static Quad tryparse(String str) {
 775             str = str.trim();
 776             if (!str.startsWith("Quad(")) {
 777                 return null;
 778             }
 779             str = str.substring(5);
 780             double coords[] = new double[6];
 781             boolean foundparen = false;
 782             for (int i = 0; i < coords.length; i++) {
 783                 int index = str.indexOf(",");
 784                 if (index < 0) {
 785                     if (i < coords.length-1) {
 786                         return null;
 787                     }
 788                     index = str.indexOf(")");
 789                     if (index < 0) {
 790                         return null;
 791                     }
 792                     foundparen = true;
 793                 }
 794                 String num = str.substring(0, index);
 795                 try {
 796                     coords[i] = Double.parseDouble(num);
 797                 } catch (NumberFormatException nfe) {
 798                     return null;
 799                 }
 800                 str = str.substring(index+1);
 801             }
 802             if (!foundparen || str.length() > 0) {
 803                 return null;
 804             }
 805             Quad c = new Quad();
 806             c.quad.setCurve(coords[0], coords[1],
 807                             coords[2], coords[3],
 808                             coords[4], coords[5]);
 809             return c;
 810         }
 811 
 812         private QuadCurve2D quad = new QuadCurve2D.Double();
 813 
 814         public void randomize() {
 815             quad.setCurve(randDblCoord(), randDblCoord(),
 816                           randDblCoord(), randDblCoord(),
 817                           randDblCoord(), randDblCoord());
 818         }
 819 
 820         public Shape getShape() {
 821             return quad;
 822         }
 823 
 824         public String toString() {
 825             return ("Quad("+
 826                     quad.getX1()+", "+
 827                     quad.getY1()+", "+
 828                     quad.getCtrlX()+", "+
 829                     quad.getCtrlY()+", "+
 830                     quad.getX2()+", "+
 831                     quad.getY2()
 832                     +")");
 833         }
 834     }
 835 
 836     public static class Poly extends AnnotatedShapeOp {
 837         public static Poly tryparse(String str) {
 838             str = str.trim();
 839             if (!str.startsWith("Poly(")) {
 840                 return null;
 841             }
 842             str = str.substring(5);
 843             Polygon p = new Polygon();
 844             while (true) {
 845                 int x, y;
 846                 str = str.trim();
 847                 if (str.startsWith(")")) {
 848                     str = str.substring(1);
 849                     break;
 850                 }
 851                 if (p.npoints > 0) {
 852                     if (str.startsWith(",")) {
 853                         str = str.substring(2).trim();
 854                     } else {
 855                         return null;
 856                     }
 857                 }
 858                 if (str.startsWith("[")) {
 859                     str = str.substring(1);
 860                 } else {
 861                     return null;
 862                 }
 863                 int index = str.indexOf(",");
 864                 if (index < 0) {
 865                     return null;
 866                 }
 867                 String num = str.substring(0, index);
 868                 try {
 869                     x = Integer.parseInt(num);
 870                 } catch (NumberFormatException nfe) {
 871                     return null;
 872                 }
 873                 str = str.substring(index+1);
 874                 index = str.indexOf("]");
 875                 if (index < 0) {
 876                     return null;
 877                 }
 878                 num = str.substring(0, index).trim();
 879                 try {
 880                     y = Integer.parseInt(num);
 881                 } catch (NumberFormatException nfe) {
 882                     return null;
 883                 }
 884                 str = str.substring(index+1);
 885                 p.addPoint(x, y);
 886             }
 887             if (str.length() > 0) {
 888                 return null;
 889             }
 890             if (p.npoints < 3) {
 891                 return null;
 892             }
 893             return new Poly(p);
 894         }
 895 
 896         private Polygon poly;
 897 
 898         public Poly() {
 899             this.poly = new Polygon();
 900         }
 901 
 902         private Poly(Polygon p) {
 903             this.poly = p;
 904         }
 905 
 906         public void randomize() {
 907             poly.reset();
 908             poly.addPoint(randIntCoord(), randIntCoord());
 909             poly.addPoint(randIntCoord(), randIntCoord());
 910             poly.addPoint(randIntCoord(), randIntCoord());
 911             poly.addPoint(randIntCoord(), randIntCoord());
 912             poly.addPoint(randIntCoord(), randIntCoord());
 913         }
 914 
 915         public Shape getShape() {
 916             return poly;
 917         }
 918 
 919         public String toString() {
 920             StringBuffer sb = new StringBuffer(100);
 921             sb.append("Poly(");
 922             for (int i = 0; i < poly.npoints; i++) {
 923                 if (i != 0) {
 924                     sb.append(", ");
 925                 }
 926                 sb.append("[");
 927                 sb.append(poly.xpoints[i]);
 928                 sb.append(", ");
 929                 sb.append(poly.ypoints[i]);
 930                 sb.append("]");
 931             }
 932             sb.append(")");
 933             return sb.toString();
 934         }
 935     }
 936 
 937     public static class Path extends AnnotatedShapeOp {
 938         public static Path tryparse(String str) {
 939             str = str.trim();
 940             if (!str.startsWith("Path(")) {
 941                 return null;
 942             }
 943             str = str.substring(5);
 944             GeneralPath gp = new GeneralPath();
 945             float coords[] = new float[6];
 946             int numsegs = 0;
 947             while (true) {
 948                 int type;
 949                 int n;
 950                 str = str.trim();
 951                 if (str.startsWith(")")) {
 952                     str = str.substring(1);
 953                     break;
 954                 }
 955                 if (str.startsWith("M[")) {
 956                     type = PathIterator.SEG_MOVETO;
 957                     n = 2;
 958                 } else if (str.startsWith("L[")) {
 959                     type = PathIterator.SEG_LINETO;
 960                     n = 2;
 961                 } else if (str.startsWith("Q[")) {
 962                     type = PathIterator.SEG_QUADTO;
 963                     n = 4;
 964                 } else if (str.startsWith("C[")) {
 965                     type = PathIterator.SEG_CUBICTO;
 966                     n = 6;
 967                 } else if (str.startsWith("E[")) {
 968                     type = PathIterator.SEG_CLOSE;
 969                     n = 0;
 970                 } else {
 971                     return null;
 972                 }
 973                 str = str.substring(2);
 974                 if (n == 0) {
 975                     if (str.startsWith("]")) {
 976                         str = str.substring(1);
 977                     } else {
 978                         return null;
 979                     }
 980                 }
 981                 for (int i = 0; i < n; i++) {
 982                     int index;
 983                     if (i < n-1) {
 984                         index = str.indexOf(",");
 985                     } else {
 986                         index = str.indexOf("]");
 987                     }
 988                     if (index < 0) {
 989                         return null;
 990                     }
 991                     String num = str.substring(0, index);
 992                     try {
 993                         coords[i] = Float.parseFloat(num);
 994                     } catch (NumberFormatException nfe) {
 995                         return null;
 996                     }
 997                     str = str.substring(index+1).trim();
 998                 }
 999                 switch (type) {
1000                 case PathIterator.SEG_MOVETO:
1001                     gp.moveTo(coords[0], coords[1]);
1002                     break;
1003                 case PathIterator.SEG_LINETO:
1004                     gp.lineTo(coords[0], coords[1]);
1005                     break;
1006                 case PathIterator.SEG_QUADTO:
1007                     gp.quadTo(coords[0], coords[1],
1008                               coords[2], coords[3]);
1009                     break;
1010                 case PathIterator.SEG_CUBICTO:
1011                     gp.curveTo(coords[0], coords[1],
1012                                coords[2], coords[3],
1013                                coords[4], coords[5]);
1014                     break;
1015                 case PathIterator.SEG_CLOSE:
1016                     gp.closePath();
1017                     break;
1018                 }
1019                 numsegs++;
1020             }
1021             if (str.length() > 0) {
1022                 return null;
1023             }
1024             if (numsegs < 2) {
1025                 return null;
1026             }
1027             return new Path(gp);
1028         }
1029 
1030         private GeneralPath path;
1031 
1032         public Path() {
1033             this.path = new GeneralPath();
1034         }
1035 
1036         private Path(GeneralPath gp) {
1037             this.path = gp;
1038         }
1039 
1040         public void randomize() {
1041             path.reset();
1042             path.moveTo(randFltCoord(), randFltCoord());
1043             for (int i = randInt(5)+3; i > 0; --i) {
1044                 switch(randInt(5)) {
1045                 case 0:
1046                     path.moveTo(randFltCoord(), randFltCoord());
1047                     break;
1048                 case 1:
1049                     path.lineTo(randFltCoord(), randFltCoord());
1050                     break;
1051                 case 2:
1052                     path.quadTo(randFltCoord(), randFltCoord(),
1053                                 randFltCoord(), randFltCoord());
1054                     break;
1055                 case 3:
1056                     path.curveTo(randFltCoord(), randFltCoord(),
1057                                  randFltCoord(), randFltCoord(),
1058                                  randFltCoord(), randFltCoord());
1059                     break;
1060                 case 4:
1061                     path.closePath();
1062                     break;
1063                 }
1064             }
1065         }
1066 
1067         public Shape getShape() {
1068             return path;
1069         }
1070 
1071         public String toString() {
1072             StringBuffer sb = new StringBuffer(100);
1073             sb.append("Path(");
1074             PathIterator pi = path.getPathIterator(null);
1075             float coords[] = new float[6];
1076             boolean first = true;
1077             while (!pi.isDone()) {
1078                 int n;
1079                 char c;
1080                 switch(pi.currentSegment(coords)) {
1081                 case PathIterator.SEG_MOVETO:
1082                     c = 'M';
1083                     n = 2;
1084                     break;
1085                 case PathIterator.SEG_LINETO:
1086                     c = 'L';
1087                     n = 2;
1088                     break;
1089                 case PathIterator.SEG_QUADTO:
1090                     c = 'Q';
1091                     n = 4;
1092                     break;
1093                 case PathIterator.SEG_CUBICTO:
1094                     c = 'C';
1095                     n = 6;
1096                     break;
1097                 case PathIterator.SEG_CLOSE:
1098                     c = 'E';
1099                     n = 0;
1100                     break;
1101                 default:
1102                     throw new InternalError("Unknown segment!");
1103                 }
1104                 sb.append(c);
1105                 sb.append("[");
1106                 for (int i = 0; i < n; i++) {
1107                     if (i != 0) {
1108                         sb.append(",");
1109                     }
1110                     sb.append(coords[i]);
1111                 }
1112                 sb.append("]");
1113                 pi.next();
1114             }
1115             sb.append(")");
1116             return sb.toString();
1117         }
1118     }
1119 
1120     public static class Rect extends AnnotatedShapeOp {
1121         public static Rect tryparse(String str) {
1122             str = str.trim();
1123             if (!str.startsWith("Rect(")) {
1124                 return null;
1125             }
1126             str = str.substring(5);
1127             double coords[] = new double[4];
1128             boolean foundparen = false;
1129             for (int i = 0; i < coords.length; i++) {
1130                 int index = str.indexOf(",");
1131                 if (index < 0) {
1132                     if (i < coords.length-1) {
1133                         return null;
1134                     }
1135                     index = str.indexOf(")");
1136                     if (index < 0) {
1137                         return null;
1138                     }
1139                     foundparen = true;
1140                 }
1141                 String num = str.substring(0, index);
1142                 try {
1143                     coords[i] = Double.parseDouble(num);
1144                 } catch (NumberFormatException nfe) {
1145                     return null;
1146                 }
1147                 str = str.substring(index+1);
1148             }
1149             if (!foundparen || str.length() > 0) {
1150                 return null;
1151             }
1152             Rect r = new Rect();
1153             r.rect.setRect(coords[0], coords[1],
1154                            coords[2], coords[3]);
1155             return r;
1156         }
1157 
1158         private Rectangle2D rect = new Rectangle2D.Double();
1159 
1160         public void randomize() {
1161             rect.setRect(randDblCoord(), randDblCoord(),
1162                          randDblCoord(), randDblCoord());
1163         }
1164 
1165         public Shape getShape() {
1166             return rect;
1167         }
1168 
1169         public String toString() {
1170             return ("Rect("+
1171                     rect.getX()+", "+
1172                     rect.getY()+", "+
1173                     rect.getWidth()+", "+
1174                     rect.getHeight()
1175                     +")");
1176         }
1177     }
1178 
1179     public static class Line extends AnnotatedShapeOp {
1180         public static Line tryparse(String str) {
1181             str = str.trim();
1182             if (!str.startsWith("Line(")) {
1183                 return null;
1184             }
1185             str = str.substring(5);
1186             double coords[] = new double[4];
1187             boolean foundparen = false;
1188             for (int i = 0; i < coords.length; i++) {
1189                 int index = str.indexOf(",");
1190                 if (index < 0) {
1191                     if (i < coords.length-1) {
1192                         return null;
1193                     }
1194                     index = str.indexOf(")");
1195                     if (index < 0) {
1196                         return null;
1197                     }
1198                     foundparen = true;
1199                 }
1200                 String num = str.substring(0, index);
1201                 try {
1202                     coords[i] = Double.parseDouble(num);
1203                 } catch (NumberFormatException nfe) {
1204                     return null;
1205                 }
1206                 str = str.substring(index+1);
1207             }
1208             if (!foundparen || str.length() > 0) {
1209                 return null;
1210             }
1211             Line l = new Line();
1212             l.line.setLine(coords[0], coords[1],
1213                            coords[2], coords[3]);
1214             return l;
1215         }
1216 
1217         private Line2D line = new Line2D.Double();
1218 
1219         public void randomize() {
1220             line.setLine(randDblCoord(), randDblCoord(),
1221                          randDblCoord(), randDblCoord());
1222         }
1223 
1224         public Shape getShape() {
1225             return line;
1226         }
1227 
1228         public String toString() {
1229             return ("Line("+
1230                     line.getX1()+", "+
1231                     line.getY1()+", "+
1232                     line.getX2()+", "+
1233                     line.getY2()
1234                     +")");
1235         }
1236     }
1237 
1238     public static class RectMethod extends AnnotatedRenderOp {
1239         public static RectMethod tryparse(String str) {
1240             str = str.trim();
1241             if (!str.startsWith("RectMethod(")) {
1242                 return null;
1243             }
1244             str = str.substring(11);
1245             int coords[] = new int[4];
1246             boolean foundparen = false;
1247             for (int i = 0; i < coords.length; i++) {
1248                 int index = str.indexOf(",");
1249                 if (index < 0) {
1250                     if (i < coords.length-1) {
1251                         return null;
1252                     }
1253                     index = str.indexOf(")");
1254                     if (index < 0) {
1255                         return null;
1256                     }
1257                     foundparen = true;
1258                 }
1259                 String num = str.substring(0, index).trim();
1260                 try {
1261                     coords[i] = Integer.parseInt(num);
1262                 } catch (NumberFormatException nfe) {
1263                     return null;
1264                 }
1265                 str = str.substring(index+1);
1266             }
1267             if (!foundparen || str.length() > 0) {
1268                 return null;
1269             }
1270             RectMethod rm = new RectMethod();
1271             rm.rect.setBounds(coords[0], coords[1],
1272                               coords[2], coords[3]);
1273             return rm;
1274         }
1275 
1276         private Rectangle rect = new Rectangle();
1277 
1278         public void randomize() {
1279             rect.setBounds(randIntCoord(), randIntCoord(),
1280                            randIntCoord(), randIntCoord());
1281         }
1282 
1283         public void fill(Graphics2D g2d) {
1284             g2d.fillRect(rect.x, rect.y, rect.width, rect.height);
1285         }
1286 
1287         public void draw(Graphics2D g2d) {
1288             g2d.drawRect(rect.x, rect.y, rect.width, rect.height);
1289         }
1290 
1291         public String toString() {
1292             return ("RectMethod("+
1293                     rect.x+", "+
1294                     rect.y+", "+
1295                     rect.width+", "+
1296                     rect.height
1297                     +")");
1298         }
1299     }
1300 
1301     public static class LineMethod extends AnnotatedRenderOp {
1302         public static LineMethod tryparse(String str) {
1303             str = str.trim();
1304             if (!str.startsWith("LineMethod(")) {
1305                 return null;
1306             }
1307             str = str.substring(11);
1308             int coords[] = new int[4];
1309             boolean foundparen = false;
1310             for (int i = 0; i < coords.length; i++) {
1311                 int index = str.indexOf(",");
1312                 if (index < 0) {
1313                     if (i < coords.length-1) {
1314                         return null;
1315                     }
1316                     index = str.indexOf(")");
1317                     if (index < 0) {
1318                         return null;
1319                     }
1320                     foundparen = true;
1321                 }
1322                 String num = str.substring(0, index).trim();
1323                 try {
1324                     coords[i] = Integer.parseInt(num);
1325                 } catch (NumberFormatException nfe) {
1326                     return null;
1327                 }
1328                 str = str.substring(index+1);
1329             }
1330             if (!foundparen || str.length() > 0) {
1331                 return null;
1332             }
1333             LineMethod lm = new LineMethod();
1334             lm.line = coords;
1335             return lm;
1336         }
1337 
1338         private int line[] = new int[4];
1339 
1340         public void randomize() {
1341             line[0] = randIntCoord();
1342             line[1] = randIntCoord();
1343             line[2] = randIntCoord();
1344             line[3] = randIntCoord();
1345         }
1346 
1347         public void fill(Graphics2D g2d) {
1348         }
1349 
1350         public void draw(Graphics2D g2d) {
1351             g2d.drawLine(line[0], line[1], line[2], line[3]);
1352         }
1353 
1354         public String toString() {
1355             return ("LineMethod("+
1356                     line[0]+", "+
1357                     line[1]+", "+
1358                     line[2]+", "+
1359                     line[3]
1360                     +")");
1361         }
1362     }
1363 
1364     public static class ErrorWindow extends Frame {
1365         ImageCanvas unclipped;
1366         ImageCanvas reference;
1367         ImageCanvas actual;
1368         ImageCanvas diff;
1369 
1370         public ErrorWindow() {
1371             super("Error Comparison Window");
1372 
1373             unclipped = new ImageCanvas();
1374             reference = new ImageCanvas();
1375             actual = new ImageCanvas();
1376             diff = new ImageCanvas();
1377 
1378             setLayout(new SmartGridLayout(0, 2, 5, 5));
1379             addImagePanel(unclipped, "Unclipped rendering");
1380             addImagePanel(reference, "Clipped reference");
1381             addImagePanel(actual, "Actual clipped");
1382             addImagePanel(diff, "Difference");
1383 
1384             addWindowListener(new WindowAdapter() {
1385                 public void windowClosing(WindowEvent e) {
1386                     setVisible(false);
1387                 }
1388             });
1389         }
1390 
1391         public void addImagePanel(ImageCanvas ic, String label) {
1392             add(ic);
1393             add(new Label(label));
1394         }
1395 
1396         public void setImages(BufferedImage imgref, BufferedImage imgtst) {
1397             unclipped.setImage(imgref);
1398             reference.setReference(imgref);
1399             actual.setImage(imgtst);
1400             diff.setDiff(reference.getImage(), imgtst);
1401             invalidate();
1402             pack();
1403             repaint();
1404         }
1405 
1406         public void setVisible(boolean vis) {
1407             super.setVisible(vis);
1408             synchronized (this) {
1409                 notifyAll();
1410             }
1411         }
1412 
1413         public synchronized void waitForHide() {
1414             while (isShowing()) {
1415                 try {
1416                     wait();
1417                 } catch (InterruptedException e) {
1418                     System.exit(2);
1419                 }
1420             }
1421         }
1422     }
1423 
1424     public static class SmartGridLayout implements LayoutManager {
1425         int rows;
1426         int cols;
1427         int hgap;
1428         int vgap;
1429 
1430         public SmartGridLayout(int r, int c, int h, int v) {
1431             this.rows = r;
1432             this.cols = c;
1433             this.hgap = h;
1434             this.vgap = v;
1435         }
1436 
1437         public void addLayoutComponent(String name, Component comp) {
1438         }
1439 
1440         public void removeLayoutComponent(Component comp) {
1441         }
1442 
1443         public int[][] getGridSizes(Container parent, boolean min) {
1444             int ncomponents = parent.getComponentCount();
1445             int nrows = rows;
1446             int ncols = cols;
1447 
1448             if (nrows > 0) {
1449                 ncols = (ncomponents + nrows - 1) / nrows;
1450             } else {
1451                 nrows = (ncomponents + ncols - 1) / ncols;
1452             }
1453             int widths[] = new int[ncols+1];
1454             int heights[] = new int[nrows+1];
1455             int x = 0;
1456             int y = 0;
1457             for (int i = 0 ; i < ncomponents ; i++) {
1458                 Component comp = parent.getComponent(i);
1459                 Dimension d = (min
1460                                ? comp.getMinimumSize()
1461                                : comp.getPreferredSize());
1462                 if (widths[x] < d.width) {
1463                     widths[x] = d.width;
1464                 }
1465                 if (heights[y] < d.height) {
1466                     heights[y] = d.height;
1467                 }
1468                 x++;
1469                 if (x >= ncols) {
1470                     x = 0;
1471                     y++;
1472                 }
1473             }
1474             for (int i = 0; i < ncols; i++) {
1475                 widths[ncols] += widths[i];
1476             }
1477             for (int i = 0; i < nrows; i++) {
1478                 heights[nrows] += heights[i];
1479             }
1480             return new int[][] { widths, heights };
1481         }
1482 
1483         public Dimension getSize(Container parent, boolean min) {
1484             int sizes[][] = getGridSizes(parent, min);
1485             int widths[] = sizes[0];
1486             int heights[] = sizes[1];
1487             int nrows = heights.length-1;
1488             int ncols = widths.length-1;
1489             int w = widths[ncols];
1490             int h = heights[nrows];
1491             Insets insets = parent.getInsets();
1492             return new Dimension(insets.left+insets.right + w+(ncols+1)*hgap,
1493                                  insets.top+insets.bottom + h+(nrows+1)*vgap);
1494         }
1495 
1496         public Dimension preferredLayoutSize(Container parent) {
1497             return getSize(parent, false);
1498         }
1499 
1500         public Dimension minimumLayoutSize(Container parent) {
1501             return getSize(parent, true);
1502         }
1503 
1504         public void layoutContainer(Container parent) {
1505             int pref[][] = getGridSizes(parent, false);
1506             int min[][] = getGridSizes(parent, true);
1507             int minwidths[] = min[0];
1508             int minheights[] = min[1];
1509             int prefwidths[] = pref[0];
1510             int prefheights[] = pref[1];
1511             int nrows = minheights.length - 1;
1512             int ncols = minwidths.length - 1;
1513             Insets insets = parent.getInsets();
1514             int w = parent.getWidth() - insets.left - insets.right;
1515             int h = parent.getHeight() - insets.top - insets.bottom;
1516             w = w - (ncols+1)*hgap;
1517             h = h - (nrows+1)*vgap;
1518             int widths[] = calculateSizes(w, ncols, minwidths, prefwidths);
1519             int heights[] = calculateSizes(h, nrows, minheights, prefheights);
1520             int ncomponents = parent.getComponentCount();
1521             int x = insets.left + hgap;
1522             int y = insets.top + vgap;
1523             int r = 0;
1524             int c = 0;
1525             for (int i = 0; i < ncomponents; i++) {
1526                 parent.getComponent(i).setBounds(x, y, widths[c], heights[r]);
1527                 x += widths[c++] + hgap;
1528                 if (c >= ncols) {
1529                     c = 0;
1530                     x = insets.left + hgap;
1531                     y += heights[r++] + vgap;
1532                     if (r >= nrows) {
1533                         // just in case
1534                         break;
1535                     }
1536                 }
1537             }
1538         }
1539 
1540         public static int[] calculateSizes(int total, int num,
1541                                            int minsizes[], int prefsizes[])
1542         {
1543             if (total <= minsizes[num]) {
1544                 return minsizes;
1545             }
1546             if (total >= prefsizes[num]) {
1547                 return prefsizes;
1548             }
1549             int sizes[] = new int[total];
1550             int prevhappy = 0;
1551             int nhappy = 0;
1552             int happysize = 0;
1553             do {
1554                 int addsize = (total - happysize) / (num - nhappy);
1555                 happysize = 0;
1556                 for (int i = 0; i < num; i++) {
1557                     if (sizes[i] >= prefsizes[i] ||
1558                         minsizes[i] + addsize > prefsizes[i])
1559                     {
1560                         happysize += (sizes[i] = prefsizes[i]);
1561                         nhappy++;
1562                     } else {
1563                         sizes[i] = minsizes[i] + addsize;
1564                     }
1565                 }
1566             } while (nhappy < num && nhappy > prevhappy);
1567             return sizes;
1568         }
1569     }
1570 
1571     public static class ImageCanvas extends Canvas {
1572         BufferedImage image;
1573 
1574         public void setImage(BufferedImage img) {
1575             this.image = img;
1576         }
1577 
1578         public BufferedImage getImage() {
1579             return image;
1580         }
1581 
1582         public void checkImage(int w, int h) {
1583             if (image == null ||
1584                 image.getWidth() < w ||
1585                 image.getHeight() < h)
1586             {
1587                 image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
1588             }
1589         }
1590 
1591         public void setReference(BufferedImage img) {
1592             checkImage(img.getWidth(), img.getHeight());
1593             Graphics g = image.createGraphics();
1594             g.drawImage(img, 0, 0, null);
1595             g.setColor(Color.white);
1596             g.fillRect(0, 0, 30, 10);
1597             g.fillRect(30, 0, 10, 30);
1598             g.fillRect(10, 30, 30, 10);
1599             g.fillRect(0, 10, 10, 30);
1600             g.dispose();
1601         }
1602 
1603         public void setDiff(BufferedImage imgref, BufferedImage imgtst) {
1604             int w = Math.max(imgref.getWidth(), imgtst.getWidth());
1605             int h = Math.max(imgref.getHeight(), imgtst.getHeight());
1606             checkImage(w, h);
1607             Graphics g = image.createGraphics();
1608             g.drawImage(imgref, 0, 0, null);
1609             g.setXORMode(Color.white);
1610             g.drawImage(imgtst, 0, 0, null);
1611             g.setPaintMode();
1612             g.setColor(new Color(1f, 1f, 0f, 0.25f));
1613             g.fillRect(10, 10, 20, 20);
1614             g.setColor(new Color(1f, 0f, 0f, 0.25f));
1615             g.fillRect(0, 0, 30, 10);
1616             g.fillRect(30, 0, 10, 30);
1617             g.fillRect(10, 30, 30, 10);
1618             g.fillRect(0, 10, 10, 30);
1619             g.dispose();
1620         }
1621 
1622         public Dimension getPreferredSize() {
1623             if (image == null) {
1624                 return new Dimension();
1625             } else {
1626                 return new Dimension(image.getWidth(), image.getHeight());
1627             }
1628         }
1629 
1630         public void paint(Graphics g) {
1631             g.drawImage(image, 0, 0, null);
1632         }
1633     }
1634 }