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