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  */
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 {
randDblCoord()40     public static double randDblCoord() {
41         return Math.random()*60 - 10;
42     }
43 
randFltCoord()44     public static float randFltCoord() {
45         return (float) randDblCoord();
46     }
47 
randIntCoord()48     public static int randIntCoord() {
49         return (int) Math.round(randDblCoord());
50     }
51 
randInt(int n)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 {
parse(String str)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 
randomize()99         public abstract void randomize();
100 
fill(Graphics2D g2d)101         public abstract void fill(Graphics2D g2d);
102 
draw(Graphics2D g2d)103         public abstract void draw(Graphics2D g2d);
104     }
105 
106     public static abstract class AnnotatedShapeOp extends AnnotatedRenderOp {
getShape()107         public abstract Shape getShape();
108 
fill(Graphics2D g2d)109         public void fill(Graphics2D g2d) {
110             g2d.fill(getShape());
111         }
112 
draw(Graphics2D g2d)113         public void draw(Graphics2D g2d) {
114             g2d.draw(getShape());
115         }
116     }
117 
usage(String err)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 
main(String argv[])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 
reportStatistics()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 
runRectSuite(int count)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 
runSuiteTests(AnnotatedRenderOp ar, int count)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 
BaseName(AnnotatedRenderOp ar)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 
runRandomTests(AnnotatedRenderOp ar, int count)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 
initImages()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 
initGCs()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 
disposeGCs()453     public static void disposeGCs() {
454         grefrender.dispose();
455         gtstrender.dispose();
456         grefrender = gtstrender = null;
457     }
458 
parseAndRun(InputStream in)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 
parseTest(String origstr)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 
test(AnnotatedRenderOp ar, boolean isfill)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 
getData(BufferedImage img)545     public static int[] getData(BufferedImage img) {
546         Raster r = img.getRaster();
547         DataBufferInt dbi = (DataBufferInt) r.getDataBuffer();
548         return dbi.getData();
549     }
550 
getScan(BufferedImage img)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 
getOffset(BufferedImage img)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 
maxdiff(int rgb1, int rgb2)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 
check(BufferedImage imgref, BufferedImage imgtst, AnnotatedRenderOp ar, boolean wasfill)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 
check(AnnotatedRenderOp ar, boolean wasfill, int dataref[], int scanref, int offref, int datatst[], int scantst, int offtst, int x0, int y0, int x1, int y1)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 
show(BufferedImage imgref, BufferedImage imgtst)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 {
tryparse(String str)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 
randomize()748         public void randomize() {
749             cubic.setCurve(randDblCoord(), randDblCoord(),
750                            randDblCoord(), randDblCoord(),
751                            randDblCoord(), randDblCoord(),
752                            randDblCoord(), randDblCoord());
753         }
754 
getShape()755         public Shape getShape() {
756             return cubic;
757         }
758 
toString()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 {
tryparse(String str)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 
randomize()814         public void randomize() {
815             quad.setCurve(randDblCoord(), randDblCoord(),
816                           randDblCoord(), randDblCoord(),
817                           randDblCoord(), randDblCoord());
818         }
819 
getShape()820         public Shape getShape() {
821             return quad;
822         }
823 
toString()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 {
tryparse(String str)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 
Poly()898         public Poly() {
899             this.poly = new Polygon();
900         }
901 
Poly(Polygon p)902         private Poly(Polygon p) {
903             this.poly = p;
904         }
905 
randomize()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 
getShape()915         public Shape getShape() {
916             return poly;
917         }
918 
toString()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 {
tryparse(String str)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 
Path()1032         public Path() {
1033             this.path = new GeneralPath();
1034         }
1035 
Path(GeneralPath gp)1036         private Path(GeneralPath gp) {
1037             this.path = gp;
1038         }
1039 
randomize()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 
getShape()1067         public Shape getShape() {
1068             return path;
1069         }
1070 
toString()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 {
tryparse(String str)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 
randomize()1160         public void randomize() {
1161             rect.setRect(randDblCoord(), randDblCoord(),
1162                          randDblCoord(), randDblCoord());
1163         }
1164 
getShape()1165         public Shape getShape() {
1166             return rect;
1167         }
1168 
toString()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 {
tryparse(String str)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 
randomize()1219         public void randomize() {
1220             line.setLine(randDblCoord(), randDblCoord(),
1221                          randDblCoord(), randDblCoord());
1222         }
1223 
getShape()1224         public Shape getShape() {
1225             return line;
1226         }
1227 
toString()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 {
tryparse(String str)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 
randomize()1278         public void randomize() {
1279             rect.setBounds(randIntCoord(), randIntCoord(),
1280                            randIntCoord(), randIntCoord());
1281         }
1282 
fill(Graphics2D g2d)1283         public void fill(Graphics2D g2d) {
1284             g2d.fillRect(rect.x, rect.y, rect.width, rect.height);
1285         }
1286 
draw(Graphics2D g2d)1287         public void draw(Graphics2D g2d) {
1288             g2d.drawRect(rect.x, rect.y, rect.width, rect.height);
1289         }
1290 
toString()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 {
tryparse(String str)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 
randomize()1340         public void randomize() {
1341             line[0] = randIntCoord();
1342             line[1] = randIntCoord();
1343             line[2] = randIntCoord();
1344             line[3] = randIntCoord();
1345         }
1346 
fill(Graphics2D g2d)1347         public void fill(Graphics2D g2d) {
1348         }
1349 
draw(Graphics2D g2d)1350         public void draw(Graphics2D g2d) {
1351             g2d.drawLine(line[0], line[1], line[2], line[3]);
1352         }
1353 
toString()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 
ErrorWindow()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 
addImagePanel(ImageCanvas ic, String label)1391         public void addImagePanel(ImageCanvas ic, String label) {
1392             add(ic);
1393             add(new Label(label));
1394         }
1395 
setImages(BufferedImage imgref, BufferedImage imgtst)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 
setVisible(boolean vis)1406         public void setVisible(boolean vis) {
1407             super.setVisible(vis);
1408             synchronized (this) {
1409                 notifyAll();
1410             }
1411         }
1412 
waitForHide()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 
SmartGridLayout(int r, int c, int h, int v)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 
addLayoutComponent(String name, Component comp)1437         public void addLayoutComponent(String name, Component comp) {
1438         }
1439 
removeLayoutComponent(Component comp)1440         public void removeLayoutComponent(Component comp) {
1441         }
1442 
getGridSizes(Container parent, boolean min)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 
getSize(Container parent, boolean min)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 
preferredLayoutSize(Container parent)1496         public Dimension preferredLayoutSize(Container parent) {
1497             return getSize(parent, false);
1498         }
1499 
minimumLayoutSize(Container parent)1500         public Dimension minimumLayoutSize(Container parent) {
1501             return getSize(parent, true);
1502         }
1503 
layoutContainer(Container parent)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 
calculateSizes(int total, int num, int minsizes[], int prefsizes[])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 
setImage(BufferedImage img)1574         public void setImage(BufferedImage img) {
1575             this.image = img;
1576         }
1577 
getImage()1578         public BufferedImage getImage() {
1579             return image;
1580         }
1581 
checkImage(int w, int h)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 
setReference(BufferedImage img)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 
setDiff(BufferedImage imgref, BufferedImage imgtst)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 
getPreferredSize()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 
paint(Graphics g)1630         public void paint(Graphics g) {
1631             g.drawImage(image, 0, 0, null);
1632         }
1633     }
1634 }
1635