/* * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * @test * @bug 6766342 * @summary Tests clipping invariance for AA rectangle and line primitives * @run main RenderClipTest -strict -readfile 6766342.tests * @run main RenderClipTest -rectsuite -count 10 */ import java.awt.*; import java.awt.geom.*; import java.awt.image.*; import java.awt.event.*; import java.util.Vector; import java.io.*; public class RenderClipTest { public static double randDblCoord() { return Math.random()*60 - 10; } public static float randFltCoord() { return (float) randDblCoord(); } public static int randIntCoord() { return (int) Math.round(randDblCoord()); } public static int randInt(int n) { return ((int) (Math.random() * (n*4))) >> 2; } static int numtests; static int numerrors; static int numfillfailures; static int numstrokefailures; static int maxerr; static boolean useAA; static boolean strokePure; static boolean testFill; static boolean testDraw; static boolean silent; static boolean verbose; static boolean strict; static boolean showErrors; static float lw; static double rot; static BufferedImage imgref; static BufferedImage imgtst; static Graphics2D grefclear; static Graphics2D gtstclear; static Graphics2D grefrender; static Graphics2D gtstrender; public static abstract class AnnotatedRenderOp { public static AnnotatedRenderOp parse(String str) { AnnotatedRenderOp ar; if (((ar = Cubic.tryparse(str)) != null) || ((ar = Quad.tryparse(str)) != null) || ((ar = Poly.tryparse(str)) != null) || ((ar = Path.tryparse(str)) != null) || ((ar = Rect.tryparse(str)) != null) || ((ar = Line.tryparse(str)) != null) || ((ar = RectMethod.tryparse(str)) != null) || ((ar = LineMethod.tryparse(str)) != null)) { return ar; } System.err.println("Unable to parse shape: "+str); return null; } public abstract void randomize(); public abstract void fill(Graphics2D g2d); public abstract void draw(Graphics2D g2d); } public static abstract class AnnotatedShapeOp extends AnnotatedRenderOp { public abstract Shape getShape(); public void fill(Graphics2D g2d) { g2d.fill(getShape()); } public void draw(Graphics2D g2d) { g2d.draw(getShape()); } } public static void usage(String err) { if (err != null) { System.err.println(err); } System.err.println("usage: java RenderClipTest "+ "[-read[file F]] [-rectsuite] [-fill] [-draw]"); System.err.println(" "+ "[-aa] [-pure] [-lw N] [-rot N]"); System.err.println(" "+ "[-rectmethod] [-linemethod] [-rect] [-line]"); System.err.println(" "+ "[-cubic] [-quad] [-poly] [-path]"); System.err.println(" "+ "[-silent] [-verbose] [-showerr] [-count N]"); System.err.println(" "+ "[-strict] [-usage]"); System.err.println(" -read Read test data from stdin"); System.err.println(" -readfile F Read test data from file F"); System.err.println(" -rectsuite Run a suite of rect/line tests"); System.err.println(" -fill Test g.fill*(...)"); System.err.println(" -draw Test g.draw*(...)"); System.err.println(" -aa Use antialiased rendering"); System.err.println(" -pure Use STROKE_PURE hint"); System.err.println(" -lw N Test line widths of N "+ "(default 1.0)"); System.err.println(" -rot N Test rotation by N degrees "+ "(default 0.0)"); System.err.println(" -rectmethod Test fillRect/drawRect methods"); System.err.println(" -linemethod Test drawLine method"); System.err.println(" -rect Test Rectangle2D shapes"); System.err.println(" -line Test Line2D shapes"); System.err.println(" -cubic Test CubicCurve2D shapes"); System.err.println(" -quad Test QuadCurve2D shapes"); System.err.println(" -poly Test Polygon shapes"); System.err.println(" -path Test GeneralPath shapes"); System.err.println(" -silent Do not print out error curves"); System.err.println(" -verbose Print out progress info"); System.err.println(" -showerr Display errors on screen"); System.err.println(" -count N N tests per shape, then exit "+ "(default 1000)"); System.err.println(" -strict All failures are important"); System.err.println(" -usage Print this help, then exit"); System.exit((err != null) ? -1 : 0); } public static void main(String argv[]) { boolean readTests = false; String readFile = null; boolean rectsuite = false; int count = 1000; lw = 1.0f; rot = 0.0; Vector testOps = new Vector(); for (int i = 0; i < argv.length; i++) { String arg = argv[i].toLowerCase(); if (arg.equals("-aa")) { useAA = true; } else if (arg.equals("-pure")) { strokePure = true; } else if (arg.equals("-fill")) { testFill = true; } else if (arg.equals("-draw")) { testDraw = true; } else if (arg.equals("-lw")) { if (i+1 >= argv.length) { usage("Missing argument: "+argv[i]); } lw = Float.parseFloat(argv[++i]); } else if (arg.equals("-rot")) { if (i+1 >= argv.length) { usage("Missing argument: "+argv[i]); } rot = Double.parseDouble(argv[++i]); } else if (arg.equals("-cubic")) { testOps.add(new Cubic()); } else if (arg.equals("-quad")) { testOps.add(new Quad()); } else if (arg.equals("-poly")) { testOps.add(new Poly()); } else if (arg.equals("-path")) { testOps.add(new Path()); } else if (arg.equals("-rect")) { testOps.add(new Rect()); } else if (arg.equals("-line")) { testOps.add(new Line()); } else if (arg.equals("-rectmethod")) { testOps.add(new RectMethod()); } else if (arg.equals("-linemethod")) { testOps.add(new LineMethod()); } else if (arg.equals("-verbose")) { verbose = true; } else if (arg.equals("-strict")) { strict = true; } else if (arg.equals("-silent")) { silent = true; } else if (arg.equals("-showerr")) { showErrors = true; } else if (arg.equals("-readfile")) { if (i+1 >= argv.length) { usage("Missing argument: "+argv[i]); } readTests = true; readFile = argv[++i]; } else if (arg.equals("-read")) { readTests = true; readFile = null; } else if (arg.equals("-rectsuite")) { rectsuite = true; } else if (arg.equals("-count")) { if (i+1 >= argv.length) { usage("Missing argument: "+argv[i]); } count = Integer.parseInt(argv[++i]); } else if (arg.equals("-usage")) { usage(null); } else { usage("Unknown argument: "+argv[i]); } } if (readTests) { if (rectsuite || testDraw || testFill || useAA || strokePure || lw != 1.0f || rot != 0.0 || testOps.size() > 0) { usage("Should not specify test types with -read options"); } } else if (rectsuite) { if (testDraw || testFill || useAA || strokePure || lw != 1.0f || rot != 0.0 || testOps.size() > 0) { usage("Should not specify test types with -rectsuite option"); } } else { if (!testDraw && !testFill) { usage("No work: Must specify one or both of "+ "-fill or -draw"); } if (testOps.size() == 0) { usage("No work: Must specify one or more of "+ "-rect[method], -line[method], "+ "-cubic, -quad, -poly, or -path"); } } initImages(); if (readTests) { try { InputStream is; if (readFile == null) { is = System.in; } else { File f = new File(System.getProperty("test.src", "."), readFile); is = new FileInputStream(f); } parseAndRun(is); } catch (IOException e) { throw new RuntimeException(e); } } else if (rectsuite) { runRectSuite(count); } else { initGCs(); for (int k = 0; k < testOps.size(); k++) { AnnotatedRenderOp ar = testOps.get(k); runRandomTests(ar, count); } disposeGCs(); } grefclear.dispose(); gtstclear.dispose(); grefclear = gtstclear = null; reportStatistics(); } public static int reportStatistics() { String connector = ""; if (numfillfailures > 0) { System.out.print(numfillfailures+" fills "); connector = "and "; } if (numstrokefailures > 0) { System.out.print(connector+numstrokefailures+" strokes "); } int totalfailures = numfillfailures + numstrokefailures; if (totalfailures == 0) { System.out.print("0 "); } System.out.println("out of "+numtests+" tests failed..."); int critical = numerrors; if (strict) { critical += totalfailures; } if (critical > 0) { throw new RuntimeException(critical+" tests had critical errors"); } System.out.println("No tests had critical errors"); return (numerrors+totalfailures); } public static void runRectSuite(int count) { AnnotatedRenderOp ops[] = { new Rect(), new RectMethod(), new Line(), new LineMethod(), }; // Sometimes different fill algorithms are chosen for // thin and wide line modes, make sure we test both... float filllinewidths[] = { 0.0f, 2.0f }; float drawlinewidths[] = { 0.0f, 0.5f, 1.0f, 2.0f, 2.5f, 5.0f, 5.3f }; double rotations[] = { 0.0, 15.0, 90.0, 135.0, 180.0, 200.0, 270.0, 300.0}; for (AnnotatedRenderOp ar: ops) { for (double r: rotations) { rot = r; for (int i = 0; i < 8; i++) { float linewidths[]; if ((i & 1) == 0) { if ((ar instanceof Line) || (ar instanceof LineMethod)) { continue; } testFill = true; testDraw = false; linewidths = filllinewidths; } else { testFill = false; testDraw = true; linewidths = drawlinewidths; } useAA = ((i & 2) != 0); strokePure = ((i & 4) != 0); for (float w : linewidths) { lw = w; runSuiteTests(ar, count); } } } } } public static void runSuiteTests(AnnotatedRenderOp ar, int count) { if (verbose) { System.out.print("Running "); System.out.print(testFill ? "Fill " : "Draw "); System.out.print(BaseName(ar)); if (useAA) { System.out.print(" AA"); } if (strokePure) { System.out.print(" Pure"); } if (lw != 1.0f) { System.out.print(" lw="+lw); } if (rot != 0.0f) { System.out.print(" rot="+rot); } System.out.println(); } initGCs(); runRandomTests(ar, count); disposeGCs(); } public static String BaseName(AnnotatedRenderOp ar) { String s = ar.toString(); int leftparen = s.indexOf('('); if (leftparen >= 0) { s = s.substring(0, leftparen); } return s; } public static void runRandomTests(AnnotatedRenderOp ar, int count) { for (int i = 0; i < count; i++) { ar.randomize(); if (testDraw) { test(ar, false); } if (testFill) { test(ar, true); } } } public static void initImages() { imgref = new BufferedImage(40, 40, BufferedImage.TYPE_INT_RGB); imgtst = new BufferedImage(40, 40, BufferedImage.TYPE_INT_RGB); grefclear = imgref.createGraphics(); gtstclear = imgtst.createGraphics(); grefclear.setColor(Color.white); gtstclear.setColor(Color.white); } public static void initGCs() { grefrender = imgref.createGraphics(); gtstrender = imgtst.createGraphics(); gtstrender.clipRect(10, 10, 20, 20); grefrender.setColor(Color.blue); gtstrender.setColor(Color.blue); if (lw != 1.0f) { BasicStroke bs = new BasicStroke(lw); grefrender.setStroke(bs); gtstrender.setStroke(bs); } if (rot != 0.0) { double rotrad = Math.toRadians(rot); grefrender.rotate(rotrad, 20, 20); gtstrender.rotate(rotrad, 20, 20); } if (strokePure) { grefrender.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); gtstrender.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); } if (useAA) { grefrender.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); gtstrender.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); maxerr = 1; } } public static void disposeGCs() { grefrender.dispose(); gtstrender.dispose(); grefrender = gtstrender = null; } public static void parseAndRun(InputStream in) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(in)); String str; while ((str = br.readLine()) != null) { if (str.startsWith("Stroked ") || str.startsWith("Filled ")) { parseTest(str); continue; } if (str.startsWith("Running ")) { continue; } if (str.startsWith("Failed: ")) { continue; } if (str.indexOf(" out of ") > 0 && str.indexOf(" tests failed...") > 0) { continue; } if (str.indexOf(" tests had critical errors") > 0) { continue; } System.err.println("Unparseable line: "+str); } } public static void parseTest(String origstr) { String str = origstr; boolean isfill = false; useAA = strokePure = false; lw = 1.0f; rot = 0.0; if (str.startsWith("Stroked ")) { str = str.substring(8); isfill = false; } else if (str.startsWith("Filled ")) { str = str.substring(7); isfill = true; } else { System.err.println("Unparseable test line: "+origstr); } if (str.startsWith("AA ")) { str = str.substring(3); useAA = true; } if (str.startsWith("Pure ")) { str = str.substring(5); strokePure = true; } if (str.startsWith("Lw=")) { int index = str.indexOf(' ', 3); if (index > 0) { lw = Float.parseFloat(str.substring(3, index)); str = str.substring(index+1); } } if (str.startsWith("Rot=")) { int index = str.indexOf(' ', 4); if (index > 0) { rot = Double.parseDouble(str.substring(4, index)); str = str.substring(index+1); } } AnnotatedRenderOp ar = AnnotatedRenderOp.parse(str); if (ar != null) { initGCs(); test(ar, isfill); disposeGCs(); } else { System.err.println("Unparseable test line: "+origstr); } } public static void test(AnnotatedRenderOp ar, boolean isfill) { grefclear.fillRect(0, 0, 40, 40); gtstclear.fillRect(0, 0, 40, 40); if (isfill) { ar.fill(grefrender); ar.fill(gtstrender); } else { ar.draw(grefrender); ar.draw(gtstrender); } check(imgref, imgtst, ar, isfill); } public static int[] getData(BufferedImage img) { Raster r = img.getRaster(); DataBufferInt dbi = (DataBufferInt) r.getDataBuffer(); return dbi.getData(); } public static int getScan(BufferedImage img) { Raster r = img.getRaster(); SinglePixelPackedSampleModel sppsm = (SinglePixelPackedSampleModel) r.getSampleModel(); return sppsm.getScanlineStride(); } public static int getOffset(BufferedImage img) { Raster r = img.getRaster(); SinglePixelPackedSampleModel sppsm = (SinglePixelPackedSampleModel) r.getSampleModel(); return sppsm.getOffset(-r.getSampleModelTranslateX(), -r.getSampleModelTranslateY()); } final static int opaque = 0xff000000; final static int whitergb = Color.white.getRGB(); public static final int maxdiff(int rgb1, int rgb2) { int maxd = 0; for (int i = 0; i < 32; i += 8) { int c1 = (rgb1 >> i) & 0xff; int c2 = (rgb2 >> i) & 0xff; int d = Math.abs(c1-c2); if (maxd < d) { maxd = d; } } return maxd; } public static void check(BufferedImage imgref, BufferedImage imgtst, AnnotatedRenderOp ar, boolean wasfill) { numtests++; int dataref[] = getData(imgref); int datatst[] = getData(imgtst); int scanref = getScan(imgref); int scantst = getScan(imgtst); int offref = getOffset(imgref); int offtst = getOffset(imgtst); // We want to check for errors outside the clip at a higher // priority than errors involving different pixels touched // inside the clip. // Check above clip if (check(ar, wasfill, null, 0, 0, datatst, scantst, offtst, 0, 0, 40, 10)) { return; } // Check below clip if (check(ar, wasfill, null, 0, 0, datatst, scantst, offtst, 0, 30, 40, 40)) { return; } // Check left of clip if (check(ar, wasfill, null, 0, 0, datatst, scantst, offtst, 0, 10, 10, 30)) { return; } // Check right of clip if (check(ar, wasfill, null, 0, 0, datatst, scantst, offtst, 30, 10, 40, 30)) { return; } // Check inside clip check(ar, wasfill, dataref, scanref, offref, datatst, scantst, offtst, 10, 10, 30, 30); } public static boolean 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) { offref += scanref * y0; offtst += scantst * y0; for (int y = y0; y < y1; y++) { for (int x = x0; x < x1; x++) { boolean failed; String reason; int rgbref; int rgbtst; rgbtst = datatst[offtst+x] | opaque; if (dataref == null) { /* Outside of clip, must be white, no error tolerance */ rgbref = whitergb; failed = (rgbtst != rgbref); reason = "stray pixel rendered outside of clip"; } else { /* Inside of clip, check for maxerr delta in components */ rgbref = dataref[offref+x] | opaque; failed = (rgbref != rgbtst && maxdiff(rgbref, rgbtst) > maxerr); reason = "different pixel rendered inside clip"; } if (failed) { if (dataref == null) { numerrors++; } if (wasfill) { numfillfailures++; } else { numstrokefailures++; } if (!silent) { System.out.println("Failed: "+reason+" at "+x+", "+y+ " ["+Integer.toHexString(rgbref)+ " != "+Integer.toHexString(rgbtst)+ "]"); System.out.print(wasfill ? "Filled " : "Stroked "); if (useAA) System.out.print("AA "); if (strokePure) System.out.print("Pure "); if (lw != 1) System.out.print("Lw="+lw+" "); if (rot != 0) System.out.print("Rot="+rot+" "); System.out.println(ar); } if (showErrors) { show(imgref, imgtst); } return true; } } offref += scanref; offtst += scantst; } return false; } static ErrorWindow errw; public static void show(BufferedImage imgref, BufferedImage imgtst) { ErrorWindow errw = new ErrorWindow(); errw.setImages(imgref, imgtst); errw.setVisible(true); errw.waitForHide(); errw.dispose(); } public static class Cubic extends AnnotatedShapeOp { public static Cubic tryparse(String str) { str = str.trim(); if (!str.startsWith("Cubic(")) { return null; } str = str.substring(6); double coords[] = new double[8]; boolean foundparen = false; for (int i = 0; i < coords.length; i++) { int index = str.indexOf(","); if (index < 0) { if (i < coords.length-1) { return null; } index = str.indexOf(")"); if (index < 0) { return null; } foundparen = true; } String num = str.substring(0, index); try { coords[i] = Double.parseDouble(num); } catch (NumberFormatException nfe) { return null; } str = str.substring(index+1); } if (!foundparen || str.length() > 0) { return null; } Cubic c = new Cubic(); c.cubic.setCurve(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5], coords[6], coords[7]); return c; } private CubicCurve2D cubic = new CubicCurve2D.Double(); public void randomize() { cubic.setCurve(randDblCoord(), randDblCoord(), randDblCoord(), randDblCoord(), randDblCoord(), randDblCoord(), randDblCoord(), randDblCoord()); } public Shape getShape() { return cubic; } public String toString() { return ("Cubic("+ cubic.getX1()+", "+ cubic.getY1()+", "+ cubic.getCtrlX1()+", "+ cubic.getCtrlY1()+", "+ cubic.getCtrlX2()+", "+ cubic.getCtrlY2()+", "+ cubic.getX2()+", "+ cubic.getY2() +")"); } } public static class Quad extends AnnotatedShapeOp { public static Quad tryparse(String str) { str = str.trim(); if (!str.startsWith("Quad(")) { return null; } str = str.substring(5); double coords[] = new double[6]; boolean foundparen = false; for (int i = 0; i < coords.length; i++) { int index = str.indexOf(","); if (index < 0) { if (i < coords.length-1) { return null; } index = str.indexOf(")"); if (index < 0) { return null; } foundparen = true; } String num = str.substring(0, index); try { coords[i] = Double.parseDouble(num); } catch (NumberFormatException nfe) { return null; } str = str.substring(index+1); } if (!foundparen || str.length() > 0) { return null; } Quad c = new Quad(); c.quad.setCurve(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); return c; } private QuadCurve2D quad = new QuadCurve2D.Double(); public void randomize() { quad.setCurve(randDblCoord(), randDblCoord(), randDblCoord(), randDblCoord(), randDblCoord(), randDblCoord()); } public Shape getShape() { return quad; } public String toString() { return ("Quad("+ quad.getX1()+", "+ quad.getY1()+", "+ quad.getCtrlX()+", "+ quad.getCtrlY()+", "+ quad.getX2()+", "+ quad.getY2() +")"); } } public static class Poly extends AnnotatedShapeOp { public static Poly tryparse(String str) { str = str.trim(); if (!str.startsWith("Poly(")) { return null; } str = str.substring(5); Polygon p = new Polygon(); while (true) { int x, y; str = str.trim(); if (str.startsWith(")")) { str = str.substring(1); break; } if (p.npoints > 0) { if (str.startsWith(",")) { str = str.substring(2).trim(); } else { return null; } } if (str.startsWith("[")) { str = str.substring(1); } else { return null; } int index = str.indexOf(","); if (index < 0) { return null; } String num = str.substring(0, index); try { x = Integer.parseInt(num); } catch (NumberFormatException nfe) { return null; } str = str.substring(index+1); index = str.indexOf("]"); if (index < 0) { return null; } num = str.substring(0, index).trim(); try { y = Integer.parseInt(num); } catch (NumberFormatException nfe) { return null; } str = str.substring(index+1); p.addPoint(x, y); } if (str.length() > 0) { return null; } if (p.npoints < 3) { return null; } return new Poly(p); } private Polygon poly; public Poly() { this.poly = new Polygon(); } private Poly(Polygon p) { this.poly = p; } public void randomize() { poly.reset(); poly.addPoint(randIntCoord(), randIntCoord()); poly.addPoint(randIntCoord(), randIntCoord()); poly.addPoint(randIntCoord(), randIntCoord()); poly.addPoint(randIntCoord(), randIntCoord()); poly.addPoint(randIntCoord(), randIntCoord()); } public Shape getShape() { return poly; } public String toString() { StringBuffer sb = new StringBuffer(100); sb.append("Poly("); for (int i = 0; i < poly.npoints; i++) { if (i != 0) { sb.append(", "); } sb.append("["); sb.append(poly.xpoints[i]); sb.append(", "); sb.append(poly.ypoints[i]); sb.append("]"); } sb.append(")"); return sb.toString(); } } public static class Path extends AnnotatedShapeOp { public static Path tryparse(String str) { str = str.trim(); if (!str.startsWith("Path(")) { return null; } str = str.substring(5); GeneralPath gp = new GeneralPath(); float coords[] = new float[6]; int numsegs = 0; while (true) { int type; int n; str = str.trim(); if (str.startsWith(")")) { str = str.substring(1); break; } if (str.startsWith("M[")) { type = PathIterator.SEG_MOVETO; n = 2; } else if (str.startsWith("L[")) { type = PathIterator.SEG_LINETO; n = 2; } else if (str.startsWith("Q[")) { type = PathIterator.SEG_QUADTO; n = 4; } else if (str.startsWith("C[")) { type = PathIterator.SEG_CUBICTO; n = 6; } else if (str.startsWith("E[")) { type = PathIterator.SEG_CLOSE; n = 0; } else { return null; } str = str.substring(2); if (n == 0) { if (str.startsWith("]")) { str = str.substring(1); } else { return null; } } for (int i = 0; i < n; i++) { int index; if (i < n-1) { index = str.indexOf(","); } else { index = str.indexOf("]"); } if (index < 0) { return null; } String num = str.substring(0, index); try { coords[i] = Float.parseFloat(num); } catch (NumberFormatException nfe) { return null; } str = str.substring(index+1).trim(); } switch (type) { case PathIterator.SEG_MOVETO: gp.moveTo(coords[0], coords[1]); break; case PathIterator.SEG_LINETO: gp.lineTo(coords[0], coords[1]); break; case PathIterator.SEG_QUADTO: gp.quadTo(coords[0], coords[1], coords[2], coords[3]); break; case PathIterator.SEG_CUBICTO: gp.curveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); break; case PathIterator.SEG_CLOSE: gp.closePath(); break; } numsegs++; } if (str.length() > 0) { return null; } if (numsegs < 2) { return null; } return new Path(gp); } private GeneralPath path; public Path() { this.path = new GeneralPath(); } private Path(GeneralPath gp) { this.path = gp; } public void randomize() { path.reset(); path.moveTo(randFltCoord(), randFltCoord()); for (int i = randInt(5)+3; i > 0; --i) { switch(randInt(5)) { case 0: path.moveTo(randFltCoord(), randFltCoord()); break; case 1: path.lineTo(randFltCoord(), randFltCoord()); break; case 2: path.quadTo(randFltCoord(), randFltCoord(), randFltCoord(), randFltCoord()); break; case 3: path.curveTo(randFltCoord(), randFltCoord(), randFltCoord(), randFltCoord(), randFltCoord(), randFltCoord()); break; case 4: path.closePath(); break; } } } public Shape getShape() { return path; } public String toString() { StringBuffer sb = new StringBuffer(100); sb.append("Path("); PathIterator pi = path.getPathIterator(null); float coords[] = new float[6]; boolean first = true; while (!pi.isDone()) { int n; char c; switch(pi.currentSegment(coords)) { case PathIterator.SEG_MOVETO: c = 'M'; n = 2; break; case PathIterator.SEG_LINETO: c = 'L'; n = 2; break; case PathIterator.SEG_QUADTO: c = 'Q'; n = 4; break; case PathIterator.SEG_CUBICTO: c = 'C'; n = 6; break; case PathIterator.SEG_CLOSE: c = 'E'; n = 0; break; default: throw new InternalError("Unknown segment!"); } sb.append(c); sb.append("["); for (int i = 0; i < n; i++) { if (i != 0) { sb.append(","); } sb.append(coords[i]); } sb.append("]"); pi.next(); } sb.append(")"); return sb.toString(); } } public static class Rect extends AnnotatedShapeOp { public static Rect tryparse(String str) { str = str.trim(); if (!str.startsWith("Rect(")) { return null; } str = str.substring(5); double coords[] = new double[4]; boolean foundparen = false; for (int i = 0; i < coords.length; i++) { int index = str.indexOf(","); if (index < 0) { if (i < coords.length-1) { return null; } index = str.indexOf(")"); if (index < 0) { return null; } foundparen = true; } String num = str.substring(0, index); try { coords[i] = Double.parseDouble(num); } catch (NumberFormatException nfe) { return null; } str = str.substring(index+1); } if (!foundparen || str.length() > 0) { return null; } Rect r = new Rect(); r.rect.setRect(coords[0], coords[1], coords[2], coords[3]); return r; } private Rectangle2D rect = new Rectangle2D.Double(); public void randomize() { rect.setRect(randDblCoord(), randDblCoord(), randDblCoord(), randDblCoord()); } public Shape getShape() { return rect; } public String toString() { return ("Rect("+ rect.getX()+", "+ rect.getY()+", "+ rect.getWidth()+", "+ rect.getHeight() +")"); } } public static class Line extends AnnotatedShapeOp { public static Line tryparse(String str) { str = str.trim(); if (!str.startsWith("Line(")) { return null; } str = str.substring(5); double coords[] = new double[4]; boolean foundparen = false; for (int i = 0; i < coords.length; i++) { int index = str.indexOf(","); if (index < 0) { if (i < coords.length-1) { return null; } index = str.indexOf(")"); if (index < 0) { return null; } foundparen = true; } String num = str.substring(0, index); try { coords[i] = Double.parseDouble(num); } catch (NumberFormatException nfe) { return null; } str = str.substring(index+1); } if (!foundparen || str.length() > 0) { return null; } Line l = new Line(); l.line.setLine(coords[0], coords[1], coords[2], coords[3]); return l; } private Line2D line = new Line2D.Double(); public void randomize() { line.setLine(randDblCoord(), randDblCoord(), randDblCoord(), randDblCoord()); } public Shape getShape() { return line; } public String toString() { return ("Line("+ line.getX1()+", "+ line.getY1()+", "+ line.getX2()+", "+ line.getY2() +")"); } } public static class RectMethod extends AnnotatedRenderOp { public static RectMethod tryparse(String str) { str = str.trim(); if (!str.startsWith("RectMethod(")) { return null; } str = str.substring(11); int coords[] = new int[4]; boolean foundparen = false; for (int i = 0; i < coords.length; i++) { int index = str.indexOf(","); if (index < 0) { if (i < coords.length-1) { return null; } index = str.indexOf(")"); if (index < 0) { return null; } foundparen = true; } String num = str.substring(0, index).trim(); try { coords[i] = Integer.parseInt(num); } catch (NumberFormatException nfe) { return null; } str = str.substring(index+1); } if (!foundparen || str.length() > 0) { return null; } RectMethod rm = new RectMethod(); rm.rect.setBounds(coords[0], coords[1], coords[2], coords[3]); return rm; } private Rectangle rect = new Rectangle(); public void randomize() { rect.setBounds(randIntCoord(), randIntCoord(), randIntCoord(), randIntCoord()); } public void fill(Graphics2D g2d) { g2d.fillRect(rect.x, rect.y, rect.width, rect.height); } public void draw(Graphics2D g2d) { g2d.drawRect(rect.x, rect.y, rect.width, rect.height); } public String toString() { return ("RectMethod("+ rect.x+", "+ rect.y+", "+ rect.width+", "+ rect.height +")"); } } public static class LineMethod extends AnnotatedRenderOp { public static LineMethod tryparse(String str) { str = str.trim(); if (!str.startsWith("LineMethod(")) { return null; } str = str.substring(11); int coords[] = new int[4]; boolean foundparen = false; for (int i = 0; i < coords.length; i++) { int index = str.indexOf(","); if (index < 0) { if (i < coords.length-1) { return null; } index = str.indexOf(")"); if (index < 0) { return null; } foundparen = true; } String num = str.substring(0, index).trim(); try { coords[i] = Integer.parseInt(num); } catch (NumberFormatException nfe) { return null; } str = str.substring(index+1); } if (!foundparen || str.length() > 0) { return null; } LineMethod lm = new LineMethod(); lm.line = coords; return lm; } private int line[] = new int[4]; public void randomize() { line[0] = randIntCoord(); line[1] = randIntCoord(); line[2] = randIntCoord(); line[3] = randIntCoord(); } public void fill(Graphics2D g2d) { } public void draw(Graphics2D g2d) { g2d.drawLine(line[0], line[1], line[2], line[3]); } public String toString() { return ("LineMethod("+ line[0]+", "+ line[1]+", "+ line[2]+", "+ line[3] +")"); } } public static class ErrorWindow extends Frame { ImageCanvas unclipped; ImageCanvas reference; ImageCanvas actual; ImageCanvas diff; public ErrorWindow() { super("Error Comparison Window"); unclipped = new ImageCanvas(); reference = new ImageCanvas(); actual = new ImageCanvas(); diff = new ImageCanvas(); setLayout(new SmartGridLayout(0, 2, 5, 5)); addImagePanel(unclipped, "Unclipped rendering"); addImagePanel(reference, "Clipped reference"); addImagePanel(actual, "Actual clipped"); addImagePanel(diff, "Difference"); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { setVisible(false); } }); } public void addImagePanel(ImageCanvas ic, String label) { add(ic); add(new Label(label)); } public void setImages(BufferedImage imgref, BufferedImage imgtst) { unclipped.setImage(imgref); reference.setReference(imgref); actual.setImage(imgtst); diff.setDiff(reference.getImage(), imgtst); invalidate(); pack(); repaint(); } public void setVisible(boolean vis) { super.setVisible(vis); synchronized (this) { notifyAll(); } } public synchronized void waitForHide() { while (isShowing()) { try { wait(); } catch (InterruptedException e) { System.exit(2); } } } } public static class SmartGridLayout implements LayoutManager { int rows; int cols; int hgap; int vgap; public SmartGridLayout(int r, int c, int h, int v) { this.rows = r; this.cols = c; this.hgap = h; this.vgap = v; } public void addLayoutComponent(String name, Component comp) { } public void removeLayoutComponent(Component comp) { } public int[][] getGridSizes(Container parent, boolean min) { int ncomponents = parent.getComponentCount(); int nrows = rows; int ncols = cols; if (nrows > 0) { ncols = (ncomponents + nrows - 1) / nrows; } else { nrows = (ncomponents + ncols - 1) / ncols; } int widths[] = new int[ncols+1]; int heights[] = new int[nrows+1]; int x = 0; int y = 0; for (int i = 0 ; i < ncomponents ; i++) { Component comp = parent.getComponent(i); Dimension d = (min ? comp.getMinimumSize() : comp.getPreferredSize()); if (widths[x] < d.width) { widths[x] = d.width; } if (heights[y] < d.height) { heights[y] = d.height; } x++; if (x >= ncols) { x = 0; y++; } } for (int i = 0; i < ncols; i++) { widths[ncols] += widths[i]; } for (int i = 0; i < nrows; i++) { heights[nrows] += heights[i]; } return new int[][] { widths, heights }; } public Dimension getSize(Container parent, boolean min) { int sizes[][] = getGridSizes(parent, min); int widths[] = sizes[0]; int heights[] = sizes[1]; int nrows = heights.length-1; int ncols = widths.length-1; int w = widths[ncols]; int h = heights[nrows]; Insets insets = parent.getInsets(); return new Dimension(insets.left+insets.right + w+(ncols+1)*hgap, insets.top+insets.bottom + h+(nrows+1)*vgap); } public Dimension preferredLayoutSize(Container parent) { return getSize(parent, false); } public Dimension minimumLayoutSize(Container parent) { return getSize(parent, true); } public void layoutContainer(Container parent) { int pref[][] = getGridSizes(parent, false); int min[][] = getGridSizes(parent, true); int minwidths[] = min[0]; int minheights[] = min[1]; int prefwidths[] = pref[0]; int prefheights[] = pref[1]; int nrows = minheights.length - 1; int ncols = minwidths.length - 1; Insets insets = parent.getInsets(); int w = parent.getWidth() - insets.left - insets.right; int h = parent.getHeight() - insets.top - insets.bottom; w = w - (ncols+1)*hgap; h = h - (nrows+1)*vgap; int widths[] = calculateSizes(w, ncols, minwidths, prefwidths); int heights[] = calculateSizes(h, nrows, minheights, prefheights); int ncomponents = parent.getComponentCount(); int x = insets.left + hgap; int y = insets.top + vgap; int r = 0; int c = 0; for (int i = 0; i < ncomponents; i++) { parent.getComponent(i).setBounds(x, y, widths[c], heights[r]); x += widths[c++] + hgap; if (c >= ncols) { c = 0; x = insets.left + hgap; y += heights[r++] + vgap; if (r >= nrows) { // just in case break; } } } } public static int[] calculateSizes(int total, int num, int minsizes[], int prefsizes[]) { if (total <= minsizes[num]) { return minsizes; } if (total >= prefsizes[num]) { return prefsizes; } int sizes[] = new int[total]; int prevhappy = 0; int nhappy = 0; int happysize = 0; do { int addsize = (total - happysize) / (num - nhappy); happysize = 0; for (int i = 0; i < num; i++) { if (sizes[i] >= prefsizes[i] || minsizes[i] + addsize > prefsizes[i]) { happysize += (sizes[i] = prefsizes[i]); nhappy++; } else { sizes[i] = minsizes[i] + addsize; } } } while (nhappy < num && nhappy > prevhappy); return sizes; } } public static class ImageCanvas extends Canvas { BufferedImage image; public void setImage(BufferedImage img) { this.image = img; } public BufferedImage getImage() { return image; } public void checkImage(int w, int h) { if (image == null || image.getWidth() < w || image.getHeight() < h) { image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); } } public void setReference(BufferedImage img) { checkImage(img.getWidth(), img.getHeight()); Graphics g = image.createGraphics(); g.drawImage(img, 0, 0, null); g.setColor(Color.white); g.fillRect(0, 0, 30, 10); g.fillRect(30, 0, 10, 30); g.fillRect(10, 30, 30, 10); g.fillRect(0, 10, 10, 30); g.dispose(); } public void setDiff(BufferedImage imgref, BufferedImage imgtst) { int w = Math.max(imgref.getWidth(), imgtst.getWidth()); int h = Math.max(imgref.getHeight(), imgtst.getHeight()); checkImage(w, h); Graphics g = image.createGraphics(); g.drawImage(imgref, 0, 0, null); g.setXORMode(Color.white); g.drawImage(imgtst, 0, 0, null); g.setPaintMode(); g.setColor(new Color(1f, 1f, 0f, 0.25f)); g.fillRect(10, 10, 20, 20); g.setColor(new Color(1f, 0f, 0f, 0.25f)); g.fillRect(0, 0, 30, 10); g.fillRect(30, 0, 10, 30); g.fillRect(10, 30, 30, 10); g.fillRect(0, 10, 10, 30); g.dispose(); } public Dimension getPreferredSize() { if (image == null) { return new Dimension(); } else { return new Dimension(image.getWidth(), image.getHeight()); } } public void paint(Graphics g) { g.drawImage(image, 0, 0, null); } } }