/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @author Denis M. Kishenko * @version $Revision$ */ package java.awt.geom; import java.util.NoSuchElementException; import org.apache.harmony.awt.internal.nls.Messages; import org.apache.harmony.misc.HashCode; /** * The Class Rectangle2D represents a rectangle whose coordinates are given with * the correct precision to be used with the Graphics2D classes. * * @since Android 1.0 */ public abstract class Rectangle2D extends RectangularShape { /** * The Constant OUT_LEFT is a mask that is used to indicate that a given * point is outside the rectangle and to its left. */ public static final int OUT_LEFT = 1; /** * The Constant OUT_TOP is a mask that is used to indicate that a given * point is outside the rectangle and above it. */ public static final int OUT_TOP = 2; /** * The Constant OUT_RIGHT is a mask that is used to indicate that a given * point is outside the rectangle and to its right. */ public static final int OUT_RIGHT = 4; /** * The Constant OUT_BOTTOM is a mask that is used to indicate that a given * point is outside the rectangle and above it. */ public static final int OUT_BOTTOM = 8; /** * The Class Float is the subclass of Rectangle2D that represents a * rectangle whose data values are given as floats (with float-level * precision). * * @since Android 1.0 */ public static class Float extends Rectangle2D { /** * The x coordinate of the rectangle's upper left corner. */ public float x; /** * The y coordinate of the rectangle's upper left corner. */ public float y; /** * The width of the rectangle. */ public float width; /** * The height of the rectangle. */ public float height; /** * Instantiates a new empty rectangle with float-precision data fields. */ public Float() { } /** * Instantiates a new rectangle with the specified float-precision data. * * @param x * the x coordinate of the rectangle's upper left corner. * @param y * the y coordinate of the rectangle's upper left corner. * @param width * the width of the rectangle. * @param height * the height of the rectangle. */ public Float(float x, float y, float width, float height) { setRect(x, y, width, height); } @Override public double getX() { return x; } @Override public double getY() { return y; } @Override public double getWidth() { return width; } @Override public double getHeight() { return height; } @Override public boolean isEmpty() { return width <= 0.0f || height <= 0.0f; } /** * Sets the rectangle's data to the given values. * * @param x * the x coordinate of the rectangle's upper left corner. * @param y * the y coordinate of the rectangle's upper left corner. * @param width * the width of the rectangle. * @param height * the height of the rectangle. */ public void setRect(float x, float y, float width, float height) { this.x = x; this.y = y; this.width = width; this.height = height; } @Override public void setRect(double x, double y, double width, double height) { this.x = (float)x; this.y = (float)y; this.width = (float)width; this.height = (float)height; } @Override public void setRect(Rectangle2D r) { this.x = (float)r.getX(); this.y = (float)r.getY(); this.width = (float)r.getWidth(); this.height = (float)r.getHeight(); } @Override public int outcode(double px, double py) { int code = 0; if (width <= 0.0f) { code |= OUT_LEFT | OUT_RIGHT; } else if (px < x) { code |= OUT_LEFT; } else if (px > x + width) { code |= OUT_RIGHT; } if (height <= 0.0f) { code |= OUT_TOP | OUT_BOTTOM; } else if (py < y) { code |= OUT_TOP; } else if (py > y + height) { code |= OUT_BOTTOM; } return code; } @Override public Rectangle2D getBounds2D() { return new Float(x, y, width, height); } @Override public Rectangle2D createIntersection(Rectangle2D r) { Rectangle2D dst; if (r instanceof Double) { dst = new Rectangle2D.Double(); } else { dst = new Rectangle2D.Float(); } Rectangle2D.intersect(this, r, dst); return dst; } @Override public Rectangle2D createUnion(Rectangle2D r) { Rectangle2D dst; if (r instanceof Double) { dst = new Rectangle2D.Double(); } else { dst = new Rectangle2D.Float(); } Rectangle2D.union(this, r, dst); return dst; } @Override public String toString() { // The output format based on 1.5 release behaviour. It could be // obtained in the following way // System.out.println(new Rectangle2D.Float().toString()) return getClass().getName() + "[x=" + x + ",y=" + y + ",width=" + width + ",height=" + height + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ } } /** * The Class Double is the subclass of Rectangle2D that represents a * rectangle whose data values are given as doubles (with * double-precision-level precision). * * @since Android 1.0 */ public static class Double extends Rectangle2D { /** * The x coordinate of the rectangle's upper left corner. */ public double x; /** * The y coordinate of the rectangle's upper left corner. */ public double y; /** * The width of the rectangle. */ public double width; /** * The height of the rectangle. */ public double height; /** * Instantiates a new empty rectangle with double-precision data fields. */ public Double() { } /** * Instantiates a new rectangle with the given double values. * * @param x * the x coordinate of the rectangle's upper left corner. * @param y * the y coordinate of the rectangle's upper left corner. * @param width * the width of the rectangle. * @param height * the height of the rectangle. */ public Double(double x, double y, double width, double height) { setRect(x, y, width, height); } @Override public double getX() { return x; } @Override public double getY() { return y; } @Override public double getWidth() { return width; } @Override public double getHeight() { return height; } @Override public boolean isEmpty() { return width <= 0.0 || height <= 0.0; } @Override public void setRect(double x, double y, double width, double height) { this.x = x; this.y = y; this.width = width; this.height = height; } @Override public void setRect(Rectangle2D r) { this.x = r.getX(); this.y = r.getY(); this.width = r.getWidth(); this.height = r.getHeight(); } @Override public int outcode(double px, double py) { int code = 0; if (width <= 0.0) { code |= OUT_LEFT | OUT_RIGHT; } else if (px < x) { code |= OUT_LEFT; } else if (px > x + width) { code |= OUT_RIGHT; } if (height <= 0.0) { code |= OUT_TOP | OUT_BOTTOM; } else if (py < y) { code |= OUT_TOP; } else if (py > y + height) { code |= OUT_BOTTOM; } return code; } @Override public Rectangle2D getBounds2D() { return new Double(x, y, width, height); } @Override public Rectangle2D createIntersection(Rectangle2D r) { Rectangle2D dst = new Rectangle2D.Double(); Rectangle2D.intersect(this, r, dst); return dst; } @Override public Rectangle2D createUnion(Rectangle2D r) { Rectangle2D dest = new Rectangle2D.Double(); Rectangle2D.union(this, r, dest); return dest; } @Override public String toString() { // The output format based on 1.5 release behaviour. It could be // obtained in the following way // System.out.println(new Rectangle2D.Double().toString()) return getClass().getName() + "[x=" + x + ",y=" + y + ",width=" + width + ",height=" + height + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ } } /** * The Class Iterator provides access to the coordinates of the * Rectangle2D's boundary modified by an AffineTransform. */ class Iterator implements PathIterator { /** * The x coordinate of the rectangle's upper left corner. */ double x; /** * The y coordinate of the rectangle's upper left corner. */ double y; /** * The width of the rectangle. */ double width; /** * The height of the rectangle. */ double height; /** * The AffineTransform that is used to modify the coordinates that are * returned by the path iterator. */ AffineTransform t; /** * The current segment index. */ int index; /** * Constructs a new Rectangle2D.Iterator for given rectangle and * transformation. * * @param r * the source Rectangle2D object. * @param at * the AffineTransform object to apply to the coordinates * before returning them. */ Iterator(Rectangle2D r, AffineTransform at) { this.x = r.getX(); this.y = r.getY(); this.width = r.getWidth(); this.height = r.getHeight(); this.t = at; if (width < 0.0 || height < 0.0) { index = 6; } } public int getWindingRule() { return WIND_NON_ZERO; } public boolean isDone() { return index > 5; } public void next() { index++; } public int currentSegment(double[] coords) { if (isDone()) { throw new NoSuchElementException(Messages.getString("awt.4B")); //$NON-NLS-1$ } if (index == 5) { return SEG_CLOSE; } int type; if (index == 0) { type = SEG_MOVETO; coords[0] = x; coords[1] = y; } else { type = SEG_LINETO; switch (index) { case 1: coords[0] = x + width; coords[1] = y; break; case 2: coords[0] = x + width; coords[1] = y + height; break; case 3: coords[0] = x; coords[1] = y + height; break; case 4: coords[0] = x; coords[1] = y; break; } } if (t != null) { t.transform(coords, 0, coords, 0, 1); } return type; } public int currentSegment(float[] coords) { if (isDone()) { throw new NoSuchElementException(Messages.getString("awt.4B")); //$NON-NLS-1$ } if (index == 5) { return SEG_CLOSE; } int type; if (index == 0) { coords[0] = (float)x; coords[1] = (float)y; type = SEG_MOVETO; } else { type = SEG_LINETO; switch (index) { case 1: coords[0] = (float)(x + width); coords[1] = (float)y; break; case 2: coords[0] = (float)(x + width); coords[1] = (float)(y + height); break; case 3: coords[0] = (float)x; coords[1] = (float)(y + height); break; case 4: coords[0] = (float)x; coords[1] = (float)y; break; } } if (t != null) { t.transform(coords, 0, coords, 0, 1); } return type; } } /** * Instantiates a new Rectangle2D. */ protected Rectangle2D() { } /** * Sets the rectangle's location and dimension. * * @param x * the x coordinate of the rectangle's upper left corner. * @param y * the y coordinate of the rectangle's upper left corner. * @param width * the width of the rectangle. * @param height * the height of the rectangle. */ public abstract void setRect(double x, double y, double width, double height); /** * Gets the location of the point with respect to the rectangle and packs * the information into a single integer using the bitmasks * {@link Rectangle2D#OUT_LEFT}, {@link Rectangle2D#OUT_RIGHT}, * {@link Rectangle2D#OUT_TOP}, and {@link Rectangle2D#OUT_BOTTOM}. If the * rectangle has zero or negative width, then every point is regarded as * being both to the left and to the right of the rectangle. Similarly, if * the height is zero or negative then all points are considered to be both * both above and below it. * * @param x * the x coordinate of the point to check. * @param y * the y coordinate of the point to check. * @return the point's location with respect to the rectangle. */ public abstract int outcode(double x, double y); /** * Creates an new rectangle that is the intersection of this rectangle with * the given rectangle. The resulting rectangle may be empty. The data of * this rectangle is left unchanged. * * @param r * the rectangle to intersect with this rectangle. * @return the new rectangle given by intersection. */ public abstract Rectangle2D createIntersection(Rectangle2D r); /** * Creates an new rectangle that is the union of this rectangle with the * given rectangle. The new rectangle is the smallest rectangle which * contains both this rectangle and the rectangle specified as a parameter. * The data of this rectangle is left unchanged. * * @param r * the rectangle to combine with this rectangle. * @return the new rectangle given by union. */ public abstract Rectangle2D createUnion(Rectangle2D r); /** * Sets the data of this rectangle to match the data of the given rectangle. * * @param r * the rectangle whose data is to be copied into this rectangle's * fields. */ public void setRect(Rectangle2D r) { setRect(r.getX(), r.getY(), r.getWidth(), r.getHeight()); } @Override public void setFrame(double x, double y, double width, double height) { setRect(x, y, width, height); } public Rectangle2D getBounds2D() { return (Rectangle2D)clone(); } /** * Determines whether any part of the line segment between (and including) * the two given points touches any part of the rectangle, including its * boundary. * * @param x1 * the x coordinate of one of the points that determines the line * segment to test. * @param y1 * the y coordinate of one of the points that determines the line * segment to test. * @param x2 * the x coordinate of one of the points that determines the line * segment to test. * @param y2 * the y coordinate of one of the points that determines the line * segment to test. * @return true, if at least one point of the line segment between the two * points matches any point of the interior of the rectangle or the * rectangle's boundary. */ public boolean intersectsLine(double x1, double y1, double x2, double y2) { double rx1 = getX(); double ry1 = getY(); double rx2 = rx1 + getWidth(); double ry2 = ry1 + getHeight(); return (rx1 <= x1 && x1 <= rx2 && ry1 <= y1 && y1 <= ry2) || (rx1 <= x2 && x2 <= rx2 && ry1 <= y2 && y2 <= ry2) || Line2D.linesIntersect(rx1, ry1, rx2, ry2, x1, y1, x2, y2) || Line2D.linesIntersect(rx2, ry1, rx1, ry2, x1, y1, x2, y2); } /** * Determines whether any part of the specified line segment touches any * part of the rectangle, including its boundary. * * @param l * the line segment to test. * @return true, if at least one point of the given line segment matches any * point of the interior of the rectangle or the rectangle's * boundary. */ public boolean intersectsLine(Line2D l) { return intersectsLine(l.getX1(), l.getY1(), l.getX2(), l.getY2()); } /** * Gets the location of the point with respect to the rectangle and packs * the information into a single integer using the bitmasks * {@link Rectangle2D#OUT_LEFT}, {@link Rectangle2D#OUT_RIGHT}, * {@link Rectangle2D#OUT_TOP}, and {@link Rectangle2D#OUT_BOTTOM}. If the * rectangle has zero or negative width, then every point is regarded as * being both to the left and to the right of the rectangle. Similarly, if * the height is zero or negative then all points are considered to be both * both above and below it. * * @param p * the point to check. * @return the point's location with respect to the rectangle. */ public int outcode(Point2D p) { return outcode(p.getX(), p.getY()); } public boolean contains(double x, double y) { if (isEmpty()) { return false; } double x1 = getX(); double y1 = getY(); double x2 = x1 + getWidth(); double y2 = y1 + getHeight(); return x1 <= x && x < x2 && y1 <= y && y < y2; } public boolean intersects(double x, double y, double width, double height) { if (isEmpty() || width <= 0.0 || height <= 0.0) { return false; } double x1 = getX(); double y1 = getY(); double x2 = x1 + getWidth(); double y2 = y1 + getHeight(); return x + width > x1 && x < x2 && y + height > y1 && y < y2; } public boolean contains(double x, double y, double width, double height) { if (isEmpty() || width <= 0.0 || height <= 0.0) { return false; } double x1 = getX(); double y1 = getY(); double x2 = x1 + getWidth(); double y2 = y1 + getHeight(); return x1 <= x && x + width <= x2 && y1 <= y && y + height <= y2; } /** * Changes the data values of the destination rectangle to match the * intersection of the two source rectangles, leaving the two source * rectangles unchanged. The resulting rectangle may be empty. * * @param src1 * one of the two source rectangles giving the data to intersect. * @param src2 * one of the two source rectangles giving the data to intersect. * @param dst * the destination object where the data of the intersection is * written. */ public static void intersect(Rectangle2D src1, Rectangle2D src2, Rectangle2D dst) { double x1 = Math.max(src1.getMinX(), src2.getMinX()); double y1 = Math.max(src1.getMinY(), src2.getMinY()); double x2 = Math.min(src1.getMaxX(), src2.getMaxX()); double y2 = Math.min(src1.getMaxY(), src2.getMaxY()); dst.setFrame(x1, y1, x2 - x1, y2 - y1); } /** * Changes the data values of the destination rectangle to match the union * of the two source rectangles, leaving the two source rectangles * unchanged. The union is the smallest rectangle that completely covers the * two source rectangles. * * @param src1 * one of the two source rectangles giving the data. * @param src2 * one of the two source rectangles giving the data. * @param dst * the destination object where the data of the union is written. */ public static void union(Rectangle2D src1, Rectangle2D src2, Rectangle2D dst) { double x1 = Math.min(src1.getMinX(), src2.getMinX()); double y1 = Math.min(src1.getMinY(), src2.getMinY()); double x2 = Math.max(src1.getMaxX(), src2.getMaxX()); double y2 = Math.max(src1.getMaxY(), src2.getMaxY()); dst.setFrame(x1, y1, x2 - x1, y2 - y1); } /** * Enlarges the rectangle so that it includes the given point. * * @param x * the x coordinate of the new point to be covered by the * rectangle. * @param y * the y coordinate of the new point to be covered by the * rectangle. */ public void add(double x, double y) { double x1 = Math.min(getMinX(), x); double y1 = Math.min(getMinY(), y); double x2 = Math.max(getMaxX(), x); double y2 = Math.max(getMaxY(), y); setRect(x1, y1, x2 - x1, y2 - y1); } /** * Enlarges the rectangle so that it includes the given point. * * @param p * the new point to be covered by the rectangle. */ public void add(Point2D p) { add(p.getX(), p.getY()); } /** * Enlarges the rectangle so that it covers the given rectangle. * * @param r * the new rectangle to be covered by this rectangle. */ public void add(Rectangle2D r) { union(this, r, this); } public PathIterator getPathIterator(AffineTransform t) { return new Iterator(this, t); } @Override public PathIterator getPathIterator(AffineTransform t, double flatness) { return new Iterator(this, t); } @Override public int hashCode() { HashCode hash = new HashCode(); hash.append(getX()); hash.append(getY()); hash.append(getWidth()); hash.append(getHeight()); return hash.hashCode(); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof Rectangle2D) { Rectangle2D r = (Rectangle2D)obj; return getX() == r.getX() && getY() == r.getY() && getWidth() == r.getWidth() && getHeight() == r.getHeight(); } return false; } }