| /* | |
| * Copyright (c) 1999, 2024, 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. | |
| */ | |
| import java.awt.BorderLayout; | |
| import java.awt.Canvas; | |
| import java.awt.Choice; | |
| import java.awt.Color; | |
| import java.awt.Dimension; | |
| import java.awt.EventQueue; | |
| import java.awt.Frame; | |
| import java.awt.Graphics; | |
| import java.awt.Graphics2D; | |
| import java.awt.Panel; | |
| import java.awt.Polygon; | |
| import java.awt.Shape; | |
| import java.awt.geom.AffineTransform; | |
| import java.awt.geom.Area; | |
| import java.awt.geom.Ellipse2D; | |
| import java.awt.geom.GeneralPath; | |
| import java.awt.geom.Point2D; | |
| /* | |
| * @test | |
| * @bug 4210936 4214524 | |
| * @summary Tests the results of the hit test methods on 3 different | |
| * Shape objects - Polygon, Area, and GeneralPath. Both an | |
| * automatic test for constraint compliance and a manual | |
| * test for correctness are included in this one class. | |
| * @library /java/awt/regtesthelpers | |
| * @build PassFailJFrame | |
| * @run main PathHitTest | |
| */ | |
| /* | |
| * @test | |
| * @bug 4210936 4214524 | |
| * @summary Tests the results of the hit test methods on 3 different | |
| * Shape objects - Polygon, Area, and GeneralPath. Both an | |
| * automatic test for constraint compliance and a manual | |
| * test for correctness are included in this one class. | |
| * @library /java/awt/regtesthelpers | |
| * @build PassFailJFrame | |
| * @run main/manual PathHitTest manual | |
| */ | |
| public class PathHitTest { | |
| public static final int BOXSIZE = 5; | |
| public static final int BOXCENTER = 2; | |
| public static final int TESTSIZE = 400; | |
| public static final int NUMTESTS = (TESTSIZE + BOXSIZE - 1) / BOXSIZE; | |
| public static Shape[] testShapes = new Shape[5]; | |
| public static String[] testNames = { | |
| "Polygon", | |
| "EvenOdd GeneralPath", | |
| "NonZero GeneralPath", | |
| "Area from EO GeneralPath", | |
| "Area from NZ GeneralPath", | |
| }; | |
| static { | |
| GeneralPath gpeo = new GeneralPath(GeneralPath.WIND_EVEN_ODD); | |
| Ellipse2D ell = new Ellipse2D.Float(); | |
| Point2D center = new Point2D.Float(); | |
| AffineTransform at = new AffineTransform(); | |
| for (int i = 0; i < 360; i += 30) { | |
| center.setLocation(100, 0); | |
| at.setToTranslation(200, 200); | |
| at.rotate(i * Math.PI / 180); | |
| at.transform(center, center); | |
| ell.setFrame(center.getX() - 50, center.getY() - 50, 100, 100); | |
| gpeo.append(ell, false); | |
| } | |
| GeneralPath side = new GeneralPath(); | |
| side.moveTo(0, 0); | |
| side.lineTo(15, 10); | |
| side.lineTo(30, 0); | |
| side.lineTo(45, -10); | |
| side.lineTo(60, 0); | |
| append4sides(gpeo, side, 20, 20); | |
| side.reset(); | |
| side.moveTo(0, 0); | |
| side.quadTo(15, 10, 30, 0); | |
| side.quadTo(45, -10, 60, 0); | |
| append4sides(gpeo, side, 320, 20); | |
| side.reset(); | |
| side.moveTo(0, 0); | |
| side.curveTo(15, 10, 45, -10, 60, 0); | |
| append4sides(gpeo, side, 20, 320); | |
| GeneralPath gpnz = new GeneralPath(GeneralPath.WIND_NON_ZERO); | |
| gpnz.append(gpeo, false); | |
| Polygon p = new Polygon(); | |
| p.addPoint( 50, 50); | |
| p.addPoint( 60, 350); | |
| p.addPoint(250, 340); | |
| p.addPoint(260, 150); | |
| p.addPoint(140, 140); | |
| p.addPoint(150, 260); | |
| p.addPoint(340, 250); | |
| p.addPoint(350, 60); | |
| testShapes[0] = p; | |
| testShapes[1] = gpeo; | |
| testShapes[2] = gpnz; | |
| testShapes[3] = new Area(gpeo); | |
| testShapes[3].getPathIterator(null); | |
| testShapes[4] = new Area(gpnz); | |
| testShapes[4].getPathIterator(null); | |
| } | |
| private static void append4sides(GeneralPath path, GeneralPath side, | |
| double xoff, double yoff) { | |
| AffineTransform at = new AffineTransform(); | |
| at.setToTranslation(xoff, yoff); | |
| for (int i = 0; i < 4; i++) { | |
| path.append(side.getPathIterator(at), i != 0); | |
| at.rotate(Math.toRadians(90), 30, 30); | |
| } | |
| } | |
| public static void main(String[] argv) throws Exception { | |
| if (argv.length > 0 && argv[0].equals("manual")) { | |
| PathHitTestManual.doManual(); | |
| } else { | |
| int totalerrs = 0; | |
| for (int i = 0; i < testShapes.length; i++) { | |
| totalerrs += testshape(testShapes[i], testNames[i]); | |
| } | |
| if (totalerrs != 0) { | |
| throw new RuntimeException(totalerrs + | |
| " constraint conditions violated!"); | |
| } | |
| } | |
| } | |
| public static int testshape(Shape s, String name) { | |
| int numerrs = 0; | |
| long start = System.currentTimeMillis(); | |
| for (int y = 0; y < TESTSIZE; y += BOXSIZE) { | |
| for (int x = 0; x < TESTSIZE; x += BOXSIZE) { | |
| boolean rectintersects = s.intersects(x, y, BOXSIZE, BOXSIZE); | |
| boolean rectcontains = s.contains(x, y, BOXSIZE, BOXSIZE); | |
| boolean pointcontains = s.contains(x + BOXCENTER, y + BOXCENTER); | |
| if (rectcontains && !rectintersects) { | |
| System.err.println("rect is contained " + | |
| "but does not intersect!"); | |
| numerrs++; | |
| } | |
| if (rectcontains && !pointcontains) { | |
| System.err.println("rect is contained " + | |
| "but center is not contained!"); | |
| numerrs++; | |
| } | |
| if (pointcontains && !rectintersects) { | |
| System.err.println("center is contained " + | |
| "but rect does not intersect!"); | |
| numerrs++; | |
| } | |
| } | |
| } | |
| long end = System.currentTimeMillis(); | |
| System.out.println(name + " completed in " + | |
| (end - start) + "ms with " + | |
| numerrs + " errors"); | |
| return numerrs; | |
| } | |
| static class PathHitTestManual extends Panel { | |
| private static final String INSTRUCTIONS = """ | |
| This test displays the results of hit testing 5 different Shape | |
| objects one at a time. | |
| You can switch between shapes using the Choice component located | |
| at the bottom of the window. | |
| Each square in the test represents the | |
| return values of the hit testing operators for that square region: | |
| yellow - not yet tested | |
| translucent blue overlay - the shape being tested | |
| black - all outside | |
| dark gray - rectangle intersects shape | |
| light gray - rectangle intersects and center point is inside shape | |
| white - rectangle is entirely contained in shape | |
| red - some constraint was violated, including: | |
| rectangle is contained, but center point is not | |
| rectangle is contained, but rectangle.intersects is false | |
| centerpoint is contained, but rectangle.intersects is false | |
| Visually inspect the results to see if they match the above table. | |
| Note that it is not a violation for rectangles that are entirely | |
| inside the path to be light gray instead of white since sometimes | |
| the path is complex enough to make an exact determination expensive. | |
| You might see this on the GeneralPath NonZero example where the | |
| circles that make up the path cross over the interior of the shape | |
| and cause the hit testing methods to guess that the rectangle is | |
| not guaranteed to be contained within the shape. | |
| """; | |
| PathHitTestCanvas phtc; | |
| public void init() { | |
| setLayout(new BorderLayout()); | |
| phtc = new PathHitTestCanvas(); | |
| add("Center", phtc); | |
| final Choice ch = new Choice(); | |
| for (int i = 0; i < PathHitTest.testNames.length; i++) { | |
| ch.add(PathHitTest.testNames[i]); | |
| } | |
| ch.addItemListener(e -> phtc.setShape(ch.getSelectedIndex())); | |
| ch.select(0); | |
| phtc.setShape(0); | |
| add("South", ch); | |
| } | |
| public void start() { | |
| phtc.start(); | |
| } | |
| public void stop() { | |
| phtc.stop(); | |
| } | |
| public static class PathHitTestCanvas extends Canvas implements Runnable { | |
| public static final Color[] colors = { | |
| /* contains? point in? intersects? */ | |
| Color.black, /* NO NO NO */ | |
| Color.darkGray, /* NO NO YES */ | |
| Color.red, /* NO YES NO */ | |
| Color.lightGray, /* NO YES YES */ | |
| Color.red, /* YES NO NO */ | |
| Color.red, /* YES NO YES */ | |
| Color.red, /* YES YES NO */ | |
| Color.white, /* YES YES YES */ | |
| Color.yellow, /* used for untested points */ | |
| }; | |
| public Dimension getPreferredSize() { | |
| return new Dimension(TESTSIZE, TESTSIZE); | |
| } | |
| public synchronized void start() { | |
| if (!testdone) { | |
| renderer = new Thread(this); | |
| renderer.setPriority(Thread.MIN_PRIORITY); | |
| renderer.start(); | |
| } | |
| } | |
| public synchronized void stop() { | |
| renderer = null; | |
| } | |
| private Thread renderer; | |
| private int shapeIndex = 0; | |
| private byte[] indices = new byte[NUMTESTS * NUMTESTS]; | |
| boolean testdone = false; | |
| private synchronized void setShape(int index) { | |
| shapeIndex = index; | |
| testdone = false; | |
| start(); | |
| } | |
| public void run() { | |
| Thread me = Thread.currentThread(); | |
| Graphics2D g2d = (Graphics2D) getGraphics(); | |
| byte[] indices; | |
| Shape s = testShapes[shapeIndex]; | |
| synchronized (this) { | |
| if (renderer != me) { | |
| return; | |
| } | |
| this.indices = new byte[NUMTESTS * NUMTESTS]; | |
| java.util.Arrays.fill(this.indices, (byte) 8); | |
| indices = this.indices; | |
| } | |
| System.err.printf("%s %s\n", g2d, Color.yellow); | |
| g2d.setColor(Color.yellow); | |
| g2d.fillRect(0, 0, TESTSIZE, TESTSIZE); | |
| int numtests = 0; | |
| long start = System.currentTimeMillis(); | |
| for (int y = 0; renderer == me && y < TESTSIZE; y += BOXSIZE) { | |
| for (int x = 0; renderer == me && x < TESTSIZE; x += BOXSIZE) { | |
| byte index = 0; | |
| if (s.intersects(x, y, BOXSIZE, BOXSIZE)) { | |
| index += 1; | |
| } | |
| if (s.contains(x + BOXCENTER, y + BOXCENTER)) { | |
| index += 2; | |
| } | |
| if (s.contains(x, y, BOXSIZE, BOXSIZE)) { | |
| index += 4; | |
| } | |
| numtests++; | |
| int i = (y / BOXSIZE) * NUMTESTS + (x / BOXSIZE); | |
| indices[i] = index; | |
| g2d.setColor(colors[index]); | |
| g2d.fillRect(x, y, BOXSIZE, BOXSIZE); | |
| } | |
| } | |
| synchronized (this) { | |
| if (renderer != me) { | |
| return; | |
| } | |
| g2d.setColor(new Color(0, 0, 1, .2f)); | |
| g2d.fill(s); | |
| testdone = true; | |
| long end = System.currentTimeMillis(); | |
| System.out.println(numtests + " tests took " + (end - start) + "ms"); | |
| } | |
| } | |
| public void paint(Graphics g) { | |
| g.setColor(Color.yellow); | |
| g.fillRect(0, 0, TESTSIZE, TESTSIZE); | |
| byte[] indices = this.indices; | |
| if (indices != null) { | |
| for (int y = 0; y < TESTSIZE; y += BOXSIZE) { | |
| for (int x = 0; x < TESTSIZE; x += BOXSIZE) { | |
| int i = (y / BOXSIZE) * NUMTESTS + (x / BOXSIZE); | |
| g.setColor(colors[indices[i]]); | |
| g.fillRect(x, y, BOXSIZE, BOXSIZE); | |
| } | |
| } | |
| } | |
| Graphics2D g2d = (Graphics2D) g; | |
| g2d.setColor(new Color(0, 0, 1, .2f)); | |
| g2d.fill(testShapes[shapeIndex]); | |
| } | |
| } | |
| static volatile PathHitTestManual pathHitTestManual; | |
| private static void createAndShowGUI() { | |
| pathHitTestManual = new PathHitTestManual(); | |
| Frame frame = new Frame("PathHitTestManual test window"); | |
| frame.add(pathHitTestManual); | |
| frame.setSize(400, 450); | |
| PassFailJFrame.addTestWindow(frame); | |
| PassFailJFrame.positionTestWindow(frame, PassFailJFrame.Position.HORIZONTAL); | |
| frame.setVisible(true); | |
| pathHitTestManual.init(); | |
| pathHitTestManual.start(); | |
| } | |
| public static void doManual() throws Exception { | |
| PassFailJFrame passFailJFrame = PassFailJFrame.builder() | |
| .title("PathHitTestManual Instructions") | |
| .instructions(INSTRUCTIONS) | |
| .testTimeOut(5) | |
| .rows(30) | |
| .columns(70) | |
| .screenCapture() | |
| .build(); | |
| EventQueue.invokeAndWait(PathHitTestManual::createAndShowGUI); | |
| try { | |
| passFailJFrame.awaitAndCheck(); | |
| } finally { | |
| pathHitTestManual.stop(); | |
| } | |
| } | |
| } | |
| } |