001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package java.awt.geom;
018:
019: import java.io.File;
020: import java.io.FilenameFilter;
021: import java.io.IOException;
022: import java.io.StreamTokenizer;
023: import java.io.StringReader;
024: import java.net.URL;
025: import java.util.HashMap;
026: import java.util.Random;
027: import java.util.regex.Pattern;
028: import java.awt.Color;
029: import java.awt.Graphics;
030: import java.awt.Polygon;
031: import java.awt.Shape;
032: import java.awt.geom.Arc2D;
033: import java.awt.geom.Ellipse2D;
034: import java.awt.geom.Rectangle2D;
035: import java.awt.geom.RoundRectangle2D;
036: import java.awt.image.BufferedImage;
037:
038: import java.awt.Tools;
039:
040: public abstract class ShapeTestCase extends PathIteratorTestCase {
041:
042: final static boolean OUTPUT = System.getProperty("OUTPUT") != null;
043:
044: final static double SHAPE_DELTA = 0.01;
045: final static int RECT_WIDTH = 46;
046: final static int RECT_HEIGHT = 34;
047: final static int POINT_MIN_COUNT = 10;
048: final static int ERROR_MAX_COUNT = 5;
049: final static int CHECK_STEP = 8;
050:
051: final static Color colorInside = Color.lightGray;
052: final static Color colorOutside = Color.white;
053: final static Color colorShape = Color.gray;
054: final static Color colorGrid = new Color(0xA0, 0xA0, 0xA0);
055:
056: final static int cInside = colorInside.getRGB();
057: final static int cOutside = colorOutside.getRGB();
058: final static int cShape = colorShape.getRGB();
059: final static int cGrid = colorGrid.getRGB();
060:
061: final static Color errorColor = Color.red;
062: final static Color successColor = Color.green;
063:
064: final static HashMap<String, Integer> arcTypes = new HashMap<String, Integer>();
065: static {
066: arcTypes.put("PIE", Arc2D.PIE);
067: arcTypes.put("CHORD", Arc2D.CHORD);
068: arcTypes.put("OPEN", Arc2D.OPEN);
069: }
070:
071: static String shapePath = null;
072: static String outputPath = null;
073:
074: protected FilenameFilter filterImage, filterShape;
075:
076: abstract static class Runner {
077:
078: class TextTokenizer {
079:
080: StreamTokenizer t;
081: String buf;
082:
083: TextTokenizer(String text) {
084: t = new StreamTokenizer(new StringReader(text));
085: buf = text;
086: }
087:
088: double getDouble() throws IOException {
089: while (t.nextToken() != StreamTokenizer.TT_EOF) {
090: if (t.ttype == StreamTokenizer.TT_NUMBER) {
091: return t.nval;
092: }
093: }
094: throw new IOException("Double not found");
095: }
096:
097: String getString() throws IOException {
098: while (t.nextToken() != StreamTokenizer.TT_EOF) {
099: if (t.ttype == StreamTokenizer.TT_WORD) {
100: return t.sval;
101: }
102: }
103: throw new IOException("String not found");
104: }
105:
106: boolean findString(String substr) {
107: int pos = buf.indexOf(substr);
108: if (pos != -1) {
109: t = new StreamTokenizer(new StringReader(buf
110: .substring(pos + substr.length())));
111: }
112: return pos != -1;
113: }
114:
115: }
116:
117: abstract static class Rectangle extends Runner {
118:
119: static String outputPrefix = "undefined";
120:
121: static class Contains extends Rectangle {
122:
123: static {
124: outputPrefix = "cr_";
125: }
126:
127: static boolean[] result = new boolean[] { false, false,
128: true, false };
129:
130: boolean execute(Shape shape, int x, int y, int width,
131: int height, int expected) {
132: return result[expected] == shape.contains(x, y,
133: width, height);
134: }
135:
136: }
137:
138: static class Intersects extends Rectangle {
139:
140: static {
141: outputPrefix = "ir_";
142: }
143:
144: static boolean[] result = new boolean[] { false, false,
145: true, true };
146:
147: boolean execute(Shape shape, int x, int y, int width,
148: int height, int expected) {
149: return result[expected] == shape.intersects(x, y,
150: width, height);
151: }
152:
153: }
154:
155: int[] prevRect;
156: int[] count;
157:
158: abstract boolean execute(Shape shape, int x, int y,
159: int width, int height, int expected);
160:
161: public boolean run(String fileName) {
162: boolean error = false;
163: try {
164: Shape shape = createShape(fileName);
165: BufferedImage img = Tools.BufferedImage
166: .loadIcon(fileName);
167: int buf[][] = createImageBuffer(img);
168: Graphics g = img.getGraphics();
169: g.setColor(errorColor);
170:
171: count = new int[] { 0, 0, 0 };
172: prevRect = null;
173:
174: for (int x = 0; x < img.getWidth() - RECT_WIDTH; x++)
175: for (int y = 0; y < img.getHeight()
176: - RECT_HEIGHT; y++) {
177: int rectType = getRectType(null, buf, x, y,
178: RECT_WIDTH, RECT_HEIGHT, true);
179: if (rectType == 0) {
180: // Invalid rectangle
181: continue;
182: }
183: if (!execute(shape, x, y, RECT_WIDTH,
184: RECT_HEIGHT, rectType)) {
185: g.drawRect(x, y, RECT_WIDTH,
186: RECT_HEIGHT);
187: error = true;
188: }
189: }
190:
191: int errCount = 0;
192: Random rnd = new Random();
193: for (int i = 0; i < 1000; i++) {
194: int rx = (int) (rnd.nextDouble() * (img
195: .getWidth() - RECT_WIDTH));
196: int ry = (int) (rnd.nextDouble() * (img
197: .getHeight() - RECT_HEIGHT));
198: int rw = (int) (rnd.nextDouble() * (img
199: .getWidth()
200: - rx - 1)) + 1;
201: int rh = (int) (rnd.nextDouble() * (img
202: .getHeight()
203: - ry - 1)) + 1;
204:
205: int rectType = getRectType(img, buf, rx, ry,
206: rw, rh, false);
207: if (rectType == 0) {
208: // Invalid rectangle
209: continue;
210: }
211: if (!execute(shape, rx, ry, rw, rh, rectType)) {
212: g.drawRect(rx, ry, rw, rh);
213: error = true;
214: errCount++;
215: }
216: if (errCount > ERROR_MAX_COUNT) {
217: break;
218: }
219: }
220:
221: if (OUTPUT) {
222: Tools.BufferedImage.saveIcon(img, outputPath
223: + outputPrefix
224: + Tools.File.extractFileName(fileName));
225: }
226: } catch (Exception e) {
227: e.printStackTrace();
228: fail(e.toString());
229: }
230: return !error;
231: }
232:
233: int getRectType(BufferedImage img, int buf[][], int rx,
234: int ry, int rw, int rh, boolean usePrev) {
235:
236: if ((rx + ry) % 2 == 0) {
237: return 0;
238: }
239:
240: if (!usePrev) {
241: prevRect = null;
242: count = new int[3];
243: }
244:
245: int[] newRect = new int[] { rx, ry, rx + rw, ry + rh };
246: countRect(buf, prevRect, newRect, count);
247: prevRect = newRect;
248:
249: if (count[0] > POINT_MIN_COUNT && count[1] == 0
250: && count[2] == 0) {
251: return 1; // Outside
252: }
253: if (count[1] > POINT_MIN_COUNT && count[0] == 0
254: && count[2] == 0) {
255: return 2; // Inside
256: }
257: if (count[0] > POINT_MIN_COUNT
258: && count[1] > POINT_MIN_COUNT) {
259: return 3; // Both
260: }
261: return 0; // Invalid rectangle
262: }
263:
264: void countRect(int[][] buf, int[] r1, int[] r2, int[] count) {
265: if (r1 != null
266: && (r1[0] > r2[2] || r1[2] < r2[0]
267: || r1[1] > r2[3] || r1[3] < r2[1])) {
268: count[0] = count[1] = count[2] = 0;
269: countRect(buf, null, r2, count);
270: return;
271: }
272:
273: int x1, y1, x2, y2;
274: if (r1 == null) {
275: x1 = r2[0];
276: y1 = r2[1];
277: x2 = r2[2];
278: y2 = r2[3];
279: } else {
280: x1 = Math.min(r1[0], r2[0]);
281: y1 = Math.min(r1[1], r2[1]);
282: x2 = Math.max(r1[2], r2[2]);
283: y2 = Math.max(r1[3], r2[3]);
284: }
285: for (int x = x1; x <= x2; x++)
286: for (int y = y1; y <= y2; y++) {
287: boolean inside1 = r1 != null && r1[0] <= x
288: && x <= r1[2] && r1[1] <= y
289: && y <= r1[3];
290: boolean inside2 = r2 != null && r2[0] <= x
291: && x <= r2[2] && r2[1] <= y
292: && y <= r2[3];
293:
294: if (inside1 ^ inside2) {
295: int index = 3;
296: int color = getColor(buf[x][y]);
297: if (color == colorOutside.getRGB()) {
298: index = 0;
299: } else if (color == colorInside.getRGB()) {
300: index = 1;
301: } else if (color == colorShape.getRGB()) {
302: index = 2;
303: }
304: if (inside1) {
305: count[index]--;
306: } else {
307: count[index]++;
308: }
309: }
310: }
311: }
312:
313: }
314:
315: static class Point extends Runner {
316:
317: public boolean run(String fileName) {
318: boolean error = false;
319: Shape shape = createShape(fileName);
320: BufferedImage img = Tools.BufferedImage
321: .loadIcon(fileName);
322: for (int x = 0; x < img.getWidth(); x++)
323: for (int y = 0; y < img.getHeight(); y++) {
324: int color = getColor(img.getRGB(x, y));
325: boolean res = shape.contains(x, y);
326: if ((color == colorInside.getRGB() && !res)
327: || (color == colorOutside.getRGB() && res)) {
328: img.setRGB(x, y, errorColor.getRGB());
329: error = true;
330: }
331: }
332: if (OUTPUT) {
333: Tools.BufferedImage.saveIcon(img, outputPath
334: + "cp_"
335: + Tools.File.extractFileName(fileName));
336: }
337: return !error;
338: }
339:
340: }
341:
342: static class PathIterator extends Runner {
343:
344: public boolean run(String fileName) {
345: double flatness = getFlatness(fileName);
346: AffineTransform at = createTransform(fileName);
347: Shape shape1 = createShape(fileName);
348: Shape shape2 = Tools.Shape.load(fileName);
349: GeneralPath path = new GeneralPath();
350: path.append(flatness < 0.0 ? shape1.getPathIterator(at)
351: : shape1.getPathIterator(at, flatness), false);
352: if (OUTPUT) {
353: Tools.Shape.save(path, outputPath
354: + (at == null ? "pi_" : "pia_")
355: + Tools.File.extractFileName(fileName));
356: }
357: return Tools.PathIterator.equals(path
358: .getPathIterator(null), shape2
359: .getPathIterator(null), SHAPE_DELTA);
360: }
361:
362: double getFlatness(String fileName) {
363: try {
364: TextTokenizer t = new TextTokenizer(Tools.File
365: .extractFileName(fileName));
366: if (t.findString("flat(")) {
367: return t.getDouble();
368: }
369: } catch (IOException e) {
370: fail("Can't read flatness " + fileName);
371: }
372: return -1.0;
373: }
374:
375: AffineTransform createTransform(String fileName) {
376: AffineTransform at = null;
377: try {
378: String fname = Tools.File.extractFileName(fileName);
379: TextTokenizer t = new TextTokenizer(fname);
380:
381: if (t.findString("affine(")) {
382: /*
383: String ttype = t.getString();
384: if (ttype.equals("M")) {
385: at.setTransform(AffineTransform.getTranslateInstance(t.getDouble(), t.getDouble()));
386: } else
387: if (ttype.equals("R")) {
388: at.setTransform(AffineTransform.getRotateInstance(t.getDouble()));
389: } else
390: if (ttype.equals("SC")) {
391: at.setTransform(AffineTransform.getScaleInstance(t.getDouble(), t.getDouble()));
392: } else
393: if (ttype.equals("SH")) {
394: at.setTransform(AffineTransform.getShearInstance(t.getDouble(), t.getDouble()));
395: } else
396: if (ttype.equals("F")) {
397: at.setTransform(AffineTransform.getShearInstance(t.getDouble(), t.getDouble()));
398: }
399: */
400: at = new AffineTransform(t.getDouble(), t
401: .getDouble(), t.getDouble(), t
402: .getDouble(), t.getDouble(), t
403: .getDouble());
404: }
405:
406: } catch (IOException e) {
407: fail("Can't read transform " + fileName);
408: }
409: return at;
410: }
411:
412: }
413:
414: abstract boolean run(String fileName);
415:
416: Shape createShape(String fileName) {
417: Shape shape = null;
418: try {
419: String fname = Tools.File.extractFileName(fileName);
420: TextTokenizer t = new TextTokenizer(fname);
421: if (t.findString("rect(")) {
422: shape = new Rectangle2D.Double(t.getDouble(), t
423: .getDouble(), t.getDouble(), t.getDouble());
424: } else if (t.findString("ellipse(")) {
425: shape = new Ellipse2D.Double(t.getDouble(), t
426: .getDouble(), t.getDouble(), t.getDouble());
427: } else if (t.findString("round(")) {
428: shape = new RoundRectangle2D.Double(t.getDouble(),
429: t.getDouble(), t.getDouble(),
430: t.getDouble(), t.getDouble(), t.getDouble());
431: } else if (t.findString("arc(")) {
432: shape = new Arc2D.Double(t.getDouble(), t
433: .getDouble(), t.getDouble(), t.getDouble(),
434: t.getDouble(), t.getDouble(), arcTypes
435: .get(t.getString()));
436: } else if (t.findString("line(")) {
437: shape = new Line2D.Double(t.getDouble(), t
438: .getDouble(), t.getDouble(), t.getDouble());
439: } else if (t.findString("quad(")) {
440: shape = new QuadCurve2D.Double(t.getDouble(), t
441: .getDouble(), t.getDouble(), t.getDouble(),
442: t.getDouble(), t.getDouble());
443: } else if (t.findString("cubic(")) {
444: shape = new CubicCurve2D.Double(t.getDouble(), t
445: .getDouble(), t.getDouble(), t.getDouble(),
446: t.getDouble(), t.getDouble(),
447: t.getDouble(), t.getDouble());
448: } else if (t.findString("polygon(")) {
449: shape = new Polygon();
450: try {
451: while (true) {
452: ((Polygon) shape).addPoint((int) t
453: .getDouble(), (int) t.getDouble());
454: }
455: } catch (IOException e) {
456: }
457: } else {
458: // GeneralPath
459: shape = Tools.Shape.load(Tools.File.changeExt(
460: fileName, ".shape"));
461: }
462:
463: } catch (IOException e) {
464: fail("Can't read shape " + fileName);
465: }
466: return shape;
467: }
468:
469: int[][] createImageBuffer(BufferedImage img) {
470: int buf[][] = new int[img.getWidth()][img.getHeight()];
471: for (int x = 0; x < img.getWidth(); x++)
472: for (int y = 0; y < img.getHeight(); y++) {
473: buf[x][y] = img.getRGB(x, y);
474: }
475: return buf;
476: }
477:
478: static int getColor(int color) {
479: if (color == cInside || color == cOutside
480: || color == cShape) {
481: return color;
482: }
483: int xored = (color ^ cGrid ^ 0xFF808080);
484: if (xored == cInside || xored == cOutside
485: || xored == cShape) {
486: return xored;
487: }
488: return color;
489: }
490:
491: }
492:
493: public ShapeTestCase(String name) {
494: super (name);
495: String classPath = "../resources/shapes/"
496: + Tools.getClasstPath(this .getClass());
497: URL url = ClassLoader.getSystemClassLoader().getResource(
498: classPath);
499: assertNotNull("Path not found " + classPath, url);
500: shapePath = url.getPath();
501: outputPath = shapePath + "output/";
502: }
503:
504: void print(String text) {
505: if (OUTPUT) {
506: System.out.println(text);
507: }
508: }
509:
510: public FilenameFilter createFilter(final String include,
511: final String exclude) {
512: return new FilenameFilter() {
513: public boolean accept(File dir, String name) {
514: return (include == null || Pattern.matches(include,
515: name))
516: && (exclude == null || !Pattern.matches(
517: exclude, name));
518: }
519: };
520: }
521:
522: String[] getTestList(String path, FilenameFilter filter) {
523: File folder = new File(path);
524: String list[] = folder.list(filter);
525: if (list != null) {
526: for (int i = 0; i < list.length; i++) {
527: list[i] = folder.getAbsolutePath() + File.separator
528: + list[i];
529: }
530: }
531: return list;
532: }
533:
534: void iterator(String name, FilenameFilter filter, Runner runner) {
535: if (filter == null) {
536: return; // skip test
537: }
538: if (OUTPUT) {
539: new File(outputPath).mkdirs();
540: }
541: String tests[] = getTestList(shapePath, filter);
542: assertTrue("Shapes not found " + shapePath, tests != null
543: && tests.length > 0);
544: for (int i = 0; i < tests.length; i++) {
545: boolean result = runner.run(tests[i]);
546: assertTrue(tests[i] + " " + Tools.PathIterator.equalsError,
547: result);
548: }
549: }
550:
551: public void testGetPathIterator() {
552: iterator("getPathIterator()", filterShape,
553: new Runner.PathIterator());
554: }
555:
556: public void testContainsPoint() {
557: iterator("contains(double,double)", filterImage,
558: new Runner.Point());
559: }
560:
561: public void testContainsRect() {
562: iterator("contains(double,double,double,double)", filterImage,
563: new Runner.Rectangle.Contains());
564: }
565:
566: public void testIntersectsRect() {
567: iterator("intersects(double,double,double,double)",
568: filterImage, new Runner.Rectangle.Intersects());
569: }
570:
571: }
|