KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jfree > util > ShapeUtilities


1 /* ========================================================================
2  * JCommon : a free general purpose class library for the Java(tm) platform
3  * ========================================================================
4  *
5  * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
6  *
7  * Project Info: http://www.jfree.org/jcommon/index.html
8  *
9  * This library is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
17  * License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
22  * USA.
23  *
24  * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
25  * in the United States and other countries.]
26  *
27  * -------------------
28  * ShapeUtilities.java
29  * -------------------
30  * (C)opyright 2003-2005, by Object Refinery Limited and Contributors.
31  *
32  * Original Author: David Gilbert (for Object Refinery Limited);
33  * Contributor(s): -;
34  *
35  * $Id: ShapeUtilities.java,v 1.17 2005/11/02 16:31:50 mungady Exp $
36  *
37  * Changes
38  * -------
39  * 13-Aug-2003 : Version 1 (DG);
40  * 16-Mar-2004 : Moved rotateShape() from RefineryUtilities.java to here (DG);
41  * 13-May-2004 : Added new shape creation methods (DG);
42  * 30-Sep-2004 : Added createLineRegion() method (DG);
43  * Moved drawRotatedShape() method from RefineryUtilities class
44  * to this class (DG);
45  * 04-Oct-2004 : Renamed ShapeUtils --> ShapeUtilities (DG);
46  * 26-Oct-2004 : Added a method to test the equality of two Line2D
47  * instances (DG);
48  * 10-Nov-2004 : Added new translateShape() and equal(Ellipse2D, Ellipse2D)
49  * methods (DG);
50  * 11-Nov-2004 : Renamed translateShape() --> createTranslatedShape() (DG);
51  * 07-Jan-2005 : Minor Javadoc fix (DG);
52  * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
53  * 21-Jan-2005 : Modified return type of RectangleAnchor.coordinates()
54  * method (DG);
55  * 22-Feb-2005 : Added equality tests for Arc2D and GeneralPath (DG);
56  * 16-Mar-2005 : Fixed bug where equal(Shape, Shape) fails for two Polygon
57  * instances (DG);
58  *
59  */

60
61 package org.jfree.util;
62
63 import java.awt.Graphics2D JavaDoc;
64 import java.awt.Polygon JavaDoc;
65 import java.awt.Shape JavaDoc;
66 import java.awt.geom.AffineTransform JavaDoc;
67 import java.awt.geom.Arc2D JavaDoc;
68 import java.awt.geom.Ellipse2D JavaDoc;
69 import java.awt.geom.GeneralPath JavaDoc;
70 import java.awt.geom.Line2D JavaDoc;
71 import java.awt.geom.PathIterator JavaDoc;
72 import java.awt.geom.Point2D JavaDoc;
73 import java.awt.geom.Rectangle2D JavaDoc;
74 import java.util.Arrays JavaDoc;
75
76 import org.jfree.ui.RectangleAnchor;
77
78 /**
79  * Utility methods for {@link Shape} objects.
80  *
81  * @author David Gilbert
82  */

83 public class ShapeUtilities {
84
85     /**
86      * Prevents instantiation.
87      */

88     private ShapeUtilities() {
89     }
90
91     /**
92      * Returns a clone of the specified shape, or <code>null</code>. At the
93      * current time, this method supports cloning for instances of
94      * <code>Line2D</code>, <code>RectangularShape</code>, <code>Area</code>
95      * and <code>GeneralPath</code>.
96      * <p>
97      * <code>RectangularShape</code> includes <code>Arc2D</code>,
98      * <code>Ellipse2D</code>, <code>Rectangle2D</code>,
99      * <code>RoundRectangle2D</code>.
100      *
101      * @param shape the shape to clone (<code>null</code> permitted,
102      * returns <code>null</code>).
103      *
104      * @return A clone or <code>null</code>.
105      */

106     public static Shape JavaDoc clone(final Shape JavaDoc shape) {
107
108         if (shape instanceof Cloneable JavaDoc) {
109             try {
110                 return (Shape JavaDoc) ObjectUtilities.clone(shape);
111             }
112             catch (CloneNotSupportedException JavaDoc cnse) {
113             }
114         }
115         final Shape JavaDoc result = null;
116         return result;
117     }
118     
119     /**
120      * Tests two shapes for equality. If both shapes are <code>null</code>,
121      * this method will return <code>true</code>.
122      * <p>
123      * In the current implementation, the following shapes are supported:
124      * <code>Ellipse2D</code>, <code>Line2D</code> and <code>Rectangle2D</code>
125      * (implicit).
126      *
127      * @param s1 the first shape (<code>null</code> permitted).
128      * @param s2 the second shape (<code>null</code> permitted).
129      *
130      * @return A boolean.
131      */

132     public static boolean equal(final Shape JavaDoc s1, final Shape JavaDoc s2) {
133         if (s1 instanceof Line2D JavaDoc && s2 instanceof Line2D JavaDoc) {
134             return equal((Line2D JavaDoc) s1, (Line2D JavaDoc) s2);
135         }
136         else if (s1 instanceof Ellipse2D JavaDoc && s2 instanceof Ellipse2D JavaDoc) {
137             return equal((Ellipse2D JavaDoc) s1, (Ellipse2D JavaDoc) s2);
138         }
139         else if (s1 instanceof Arc2D JavaDoc && s2 instanceof Arc2D JavaDoc) {
140             return equal((Arc2D JavaDoc) s1, (Arc2D JavaDoc) s2);
141         }
142         else if (s1 instanceof Polygon JavaDoc && s2 instanceof Polygon JavaDoc) {
143             return equal((Polygon JavaDoc) s1, (Polygon JavaDoc) s2);
144         }
145         else if (s1 instanceof GeneralPath JavaDoc && s2 instanceof GeneralPath JavaDoc) {
146             return equal((GeneralPath JavaDoc) s1, (GeneralPath JavaDoc) s2);
147         }
148         else {
149             // this will handle Rectangle2D...
150
return ObjectUtilities.equal(s1, s2);
151         }
152     }
153
154     /**
155      * Compares two lines are returns <code>true</code> if they are equal or
156      * both <code>null</code>.
157      *
158      * @param l1 the first line (<code>null</code> permitted).
159      * @param l2 the second line (<code>null</code> permitted).
160      *
161      * @return A boolean.
162      */

163     public static boolean equal(final Line2D JavaDoc l1, final Line2D JavaDoc l2) {
164         if (l1 == null) {
165             return (l2 == null);
166         }
167         if (l2 == null) {
168             return false;
169         }
170         if (!l1.getP1().equals(l2.getP1())) {
171             return false;
172         }
173         if (!l1.getP2().equals(l2.getP2())) {
174             return false;
175         }
176         return true;
177     }
178     
179     /**
180      * Compares two ellipses and returns <code>true</code> if they are equal or
181      * both <code>null</code>.
182      *
183      * @param e1 the first ellipse (<code>null</code> permitted).
184      * @param e2 the second ellipse (<code>null</code> permitted).
185      *
186      * @return A boolean.
187      */

188     public static boolean equal(final Ellipse2D JavaDoc e1, final Ellipse2D JavaDoc e2) {
189         if (e1 == null) {
190             return (e2 == null);
191         }
192         if (e2 == null) {
193             return false;
194         }
195         if (!e1.getFrame().equals(e2.getFrame())) {
196             return false;
197         }
198         return true;
199     }
200     
201     /**
202      * Compares two arcs and returns <code>true</code> if they are equal or
203      * both <code>null</code>.
204      *
205      * @param a1 the first arc (<code>null</code> permitted).
206      * @param a2 the second arc (<code>null</code> permitted).
207      *
208      * @return A boolean.
209      */

210     public static boolean equal(final Arc2D JavaDoc a1, final Arc2D JavaDoc a2) {
211         if (a1 == null) {
212             return (a2 == null);
213         }
214         if (a2 == null) {
215             return false;
216         }
217         if (!a1.getFrame().equals(a2.getFrame())) {
218             return false;
219         }
220         if (a1.getAngleStart() != a2.getAngleStart()) {
221             return false;
222         }
223         if (a1.getAngleExtent() != a2.getAngleExtent()) {
224             return false;
225         }
226         if (a1.getArcType() != a2.getArcType()) {
227             return false;
228         }
229         return true;
230     }
231     
232     /**
233      * Tests two polygons for equality. If both are <code>null</code> this
234      * method returns <code>true</code>.
235      *
236      * @param p1 polygon 1 (<code>null</code> permitted).
237      * @param p2 polygon 2 (<code>null</code> permitted).
238      *
239      * @return A boolean.
240      */

241     public static boolean equal(final Polygon JavaDoc p1, final Polygon JavaDoc p2) {
242         if (p1 == null) {
243             return (p2 == null);
244         }
245         if (p2 == null) {
246             return false;
247         }
248         if (p1.npoints != p2.npoints) {
249             return false;
250         }
251         if (!Arrays.equals(p1.xpoints, p2.xpoints)) {
252             return false;
253         }
254         if (!Arrays.equals(p1.ypoints, p2.ypoints)) {
255             return false;
256         }
257         return true;
258     }
259     
260     /**
261      * Tests two polygons for equality. If both are <code>null</code> this
262      * method returns <code>true</code>.
263      *
264      * @param p1 path 1 (<code>null</code> permitted).
265      * @param p2 path 2 (<code>null</code> permitted).
266      *
267      * @return A boolean.
268      */

269     public static boolean equal(final GeneralPath JavaDoc p1, final GeneralPath JavaDoc p2) {
270         if (p1 == null) {
271             return (p2 == null);
272         }
273         if (p2 == null) {
274             return false;
275         }
276         if (p1.getWindingRule() != p2.getWindingRule()) {
277             return false;
278         }
279         PathIterator JavaDoc iterator1 = p1.getPathIterator(null);
280         PathIterator JavaDoc iterator2 = p1.getPathIterator(null);
281         double[] d1 = new double[6];
282         double[] d2 = new double[6];
283         boolean done = iterator1.isDone() && iterator2.isDone();
284         while (!done) {
285             if (iterator1.isDone() != iterator2.isDone()) {
286                 return false;
287             }
288             int seg1 = iterator1.currentSegment(d1);
289             int seg2 = iterator2.currentSegment(d2);
290             if (seg1 != seg2) {
291                 return false;
292             }
293             if (!Arrays.equals(d1, d2)) {
294                 return false;
295             }
296             iterator1.next();
297             iterator2.next();
298             done = iterator1.isDone() && iterator2.isDone();
299         }
300         return true;
301     }
302     
303     /**
304      * Creates and returns a translated shape.
305      *
306      * @param shape the shape (<code>null</code> not permitted).
307      * @param transX the x translation (in Java2D space).
308      * @param transY the y translation (in Java2D space).
309      *
310      * @return The translated shape.
311      */

312     public static Shape JavaDoc createTranslatedShape(final Shape JavaDoc shape,
313                                               final double transX,
314                                               final double transY) {
315         if (shape == null) {
316             throw new IllegalArgumentException JavaDoc("Null 'shape' argument.");
317         }
318         final AffineTransform JavaDoc transform = AffineTransform.getTranslateInstance(
319             transX, transY
320         );
321         return transform.createTransformedShape(shape);
322     }
323     
324     /**
325      * Translates a shape to a new location such that the anchor point
326      * (relative to the rectangular bounds of the shape) aligns with the
327      * specified (x, y) coordinate in Java2D space.
328      *
329      * @param shape the shape (<code>null</code> not permitted).
330      * @param anchor the anchor (<code>null</code> not permitted).
331      * @param locationX the x-coordinate (in Java2D space).
332      * @param locationY the y-coordinate (in Java2D space).
333      *
334      * @return A new and translated shape.
335      */

336     public static Shape JavaDoc createTranslatedShape(final Shape JavaDoc shape,
337                                               final RectangleAnchor anchor,
338                                               final double locationX,
339                                               final double locationY) {
340         if (shape == null) {
341             throw new IllegalArgumentException JavaDoc("Null 'shape' argument.");
342         }
343         if (anchor == null) {
344             throw new IllegalArgumentException JavaDoc("Null 'anchor' argument.");
345         }
346         Point2D JavaDoc anchorPoint = RectangleAnchor.coordinates(
347             shape.getBounds2D(), anchor
348         );
349         final AffineTransform JavaDoc transform = AffineTransform.getTranslateInstance(
350             locationX - anchorPoint.getX(), locationY - anchorPoint.getY()
351         );
352         return transform.createTransformedShape(shape);
353     }
354
355     /**
356      * Rotates a shape about the specified coordinates.
357      *
358      * @param base the shape (<code>null</code> permitted, returns
359      * <code>null</code>).
360      * @param angle the angle (in radians).
361      * @param x the x coordinate for the rotation point (in Java2D space).
362      * @param y the y coordinate for the rotation point (in Java2D space).
363      *
364      * @return the rotated shape.
365      */

366     public static Shape JavaDoc rotateShape(final Shape JavaDoc base, final double angle,
367                                     final float x, final float y) {
368         if (base == null) {
369             return null;
370         }
371         final AffineTransform JavaDoc rotate = AffineTransform.getRotateInstance(
372             angle, x, y
373         );
374         final Shape JavaDoc result = rotate.createTransformedShape(base);
375         return result;
376     }
377
378     /**
379      * Draws a shape with the specified rotation about <code>(x, y)</code>.
380      *
381      * @param g2 the graphics device (<code>null</code> not permitted).
382      * @param shape the shape (<code>null</code> not permitted).
383      * @param angle the angle (in radians).
384      * @param x the x coordinate for the rotation point.
385      * @param y the y coordinate for the rotation point.
386      */

387     public static void drawRotatedShape(final Graphics2D JavaDoc g2, final Shape JavaDoc shape,
388                                         final double angle,
389                                         final float x, final float y) {
390
391         final AffineTransform JavaDoc saved = g2.getTransform();
392         final AffineTransform JavaDoc rotate = AffineTransform.getRotateInstance(
393             angle, x, y
394         );
395         g2.transform(rotate);
396         g2.draw(shape);
397         g2.setTransform(saved);
398
399     }
400
401     /** A useful constant used internally. */
402     private static final float SQRT2 = (float) Math.pow(2.0, 0.5);
403
404     /**
405      * Creates a diagonal cross shape.
406      *
407      * @param l the length of each 'arm'.
408      * @param t the thickness.
409      *
410      * @return A diagonal cross shape.
411      */

412     public static Shape JavaDoc createDiagonalCross(final float l, final float t) {
413         final GeneralPath JavaDoc p0 = new GeneralPath JavaDoc();
414         p0.moveTo(-l - t, -l + t);
415         p0.lineTo(-l + t, -l - t);
416         p0.lineTo(0.0f, -t * SQRT2);
417         p0.lineTo(l - t, -l - t);
418         p0.lineTo(l + t, -l + t);
419         p0.lineTo(t * SQRT2, 0.0f);
420         p0.lineTo(l + t, l - t);
421         p0.lineTo(l - t, l + t);
422         p0.lineTo(0.0f, t * SQRT2);
423         p0.lineTo(-l + t, l + t);
424         p0.lineTo(-l - t, l - t);
425         p0.lineTo(-t * SQRT2, 0.0f);
426         p0.closePath();
427         return p0;
428     }
429     
430     /**
431      * Creates a diagonal cross shape.
432      *
433      * @param l the length of each 'arm'.
434      * @param t the thickness.
435      *
436      * @return A diagonal cross shape.
437      */

438     public static Shape JavaDoc createRegularCross(final float l, final float t) {
439         final GeneralPath JavaDoc p0 = new GeneralPath JavaDoc();
440         p0.moveTo(-l, t);
441         p0.lineTo(-t, t);
442         p0.lineTo(-t, l);
443         p0.lineTo(t, l);
444         p0.lineTo(t, t);
445         p0.lineTo(l, t);
446         p0.lineTo(l, -t);
447         p0.lineTo(t, -t);
448         p0.lineTo(t, -l);
449         p0.lineTo(-t, -l);
450         p0.lineTo(-t, -t);
451         p0.lineTo(-l, -t);
452         p0.closePath();
453         return p0;
454     }
455     
456     /**
457      * Creates a diamond shape.
458      *
459      * @param s the size factor (equal to half the height of the diamond).
460      *
461      * @return A diamond shape.
462      */

463     public static Shape JavaDoc createDiamond(final float s) {
464         final GeneralPath JavaDoc p0 = new GeneralPath JavaDoc();
465         p0.moveTo(0.0f, -s);
466         p0.lineTo(s, 0.0f);
467         p0.lineTo(0.0f, s);
468         p0.lineTo(-s, 0.0f);
469         p0.closePath();
470         return p0;
471     }
472     
473     /**
474      * Creates a triangle shape that points upwards.
475      *
476      * @param s the size factor (equal to half the height of the triangle).
477      *
478      * @return A triangle shape.
479      */

480     public static Shape JavaDoc createUpTriangle(final float s) {
481         final GeneralPath JavaDoc p0 = new GeneralPath JavaDoc();
482         p0.moveTo(0.0f, -s);
483         p0.lineTo(s, s);
484         p0.lineTo(-s, s);
485         p0.closePath();
486         return p0;
487     }
488
489     /**
490      * Creates a triangle shape that points downwards.
491      *
492      * @param s the size factor (equal to half the height of the triangle).
493      *
494      * @return A triangle shape.
495      */

496     public static Shape JavaDoc createDownTriangle(final float s) {
497         final GeneralPath JavaDoc p0 = new GeneralPath JavaDoc();
498         p0.moveTo(0.0f, s);
499         p0.lineTo(s, -s);
500         p0.lineTo(-s, -s);
501         p0.closePath();
502         return p0;
503     }
504
505     /**
506      * Creates a region surrounding a line segment by 'widening' the line
507      * segment. A typical use for this method is the creation of a
508      * 'clickable' region for a line that is displayed on-screen.
509      *
510      * @param line the line (<code>null</code> not permitted).
511      * @param width the width of the region.
512      *
513      * @return A region that surrounds the line.
514      */

515     public static Shape JavaDoc createLineRegion(final Line2D JavaDoc line, final float width) {
516         final GeneralPath JavaDoc result = new GeneralPath JavaDoc();
517         final float x1 = (float) line.getX1();
518         final float x2 = (float) line.getX2();
519         final float y1 = (float) line.getY1();
520         final float y2 = (float) line.getY2();
521         if ((x2 - x1) != 0.0) {
522             final double theta = Math.atan((y2 - y1) / (x2 - x1));
523             final float dx = (float) Math.sin(theta) * width;
524             final float dy = (float) Math.cos(theta) * width;
525             result.moveTo(x1 - dx, y1 + dy);
526             result.lineTo(x1 + dx, y1 - dy);
527             result.lineTo(x2 + dx, y2 - dy);
528             result.lineTo(x2 - dx, y2 + dy);
529             result.closePath();
530         }
531         else {
532             // special case, vertical line
533
result.moveTo(x1 - width / 2.0f, y1);
534             result.lineTo(x1 + width / 2.0f, y1);
535             result.lineTo(x2 + width / 2.0f, y2);
536             result.lineTo(x2 - width / 2.0f, y2);
537             result.closePath();
538         }
539         return result;
540     }
541         
542     /**
543      * Returns a point based on (x, y) but constrained to be within the bounds
544      * of a given rectangle.
545      *
546      * @param x the x-coordinate.
547      * @param y the y-coordinate.
548      * @param area the constraining rectangle (<code>null</code> not
549      * permitted).
550      *
551      * @return A point within the rectangle.
552      *
553      * @throws NullPointerException if <code>area</code> is <code>null</code>.
554      */

555     public static Point2D JavaDoc getPointInRectangle(double x, double y,
556                                               final Rectangle2D JavaDoc area) {
557
558         x = Math.max(area.getMinX(), Math.min(x, area.getMaxX()));
559         y = Math.max(area.getMinY(), Math.min(y, area.getMaxY()));
560         return new Point2D.Double JavaDoc(x, y);
561
562     }
563
564     /**
565      * Checks, whether the given rectangle1 fully contains rectangle 2
566      * (even if rectangle 2 has a height or width of zero!).
567      *
568      * @param rect1 the first rectangle.
569      * @param rect2 the second rectangle.
570      *
571      * @return A boolean.
572      */

573     public static boolean contains(final Rectangle2D JavaDoc rect1,
574                                    final Rectangle2D JavaDoc rect2) {
575         
576         final double x0 = rect1.getX();
577         final double y0 = rect1.getY();
578         final double x = rect2.getX();
579         final double y = rect2.getY();
580         final double w = rect2.getWidth();
581         final double h = rect2.getHeight();
582
583         return ((x >= x0) && (y >= y0)
584                 && ((x + w) <= (x0 + rect1.getWidth()))
585                 && ((y + h) <= (y0 + rect1.getHeight())));
586     
587     }
588     
589
590     /**
591      * Checks, whether the given rectangle1 fully contains rectangle 2
592      * (even if rectangle 2 has a height or width of zero!).
593      *
594      * @param rect1 the first rectangle.
595      * @param rect2 the second rectangle.
596      *
597      * @return A boolean.
598      */

599     public static boolean intersects (final Rectangle2D JavaDoc rect1,
600                                       final Rectangle2D JavaDoc rect2) {
601
602       final double x0 = rect1.getX();
603       final double y0 = rect1.getY();
604
605       final double x = rect2.getX();
606       final double width = rect2.getWidth();
607       final double y = rect2.getY();
608       final double height = rect2.getHeight();
609       return (x + width >= x0 && y + height >= y0 && x <= x0 + rect1.getWidth()
610               && y <= y0 + rect1.getHeight());
611     }
612 }
613
Popular Tags