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