/*
* Copyright (c) 1998, 2018, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.
*/
package java.awt.geom;
import java.awt.Shape;
import java.awt.Rectangle;
import java.util.Vector;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import sun.awt.geom.Curve;
import sun.awt.geom.Crossings;
import sun.awt.geom.AreaOp;
/**
* An {@code Area} object stores and manipulates a
* resolution-independent description of an enclosed area of
* 2-dimensional space.
* {@code Area} objects can be transformed and can perform
* various Constructive Area Geometry (CAG) operations when combined
* with other {@code Area} objects.
* The CAG operations include area
* {@link #add addition}, {@link #subtract subtraction},
* {@link #intersect intersection}, and {@link #exclusiveOr exclusive or}.
* See the linked method documentation for examples of the various
* operations.
* <p>
* The {@code Area} class implements the {@code Shape}
* interface and provides full support for all of its hit-testing
* and path iteration facilities, but an {@code Area} is more
* specific than a generalized path in a number of ways:
* <ul>
* <li>Only closed paths and sub-paths are stored.
* {@code Area} objects constructed from unclosed paths
* are implicitly closed during construction as if those paths
* had been filled by the {@code Graphics2D.fill} method.
* <li>The interiors of the individual stored sub-paths are all
* non-empty and non-overlapping. Paths are decomposed during
* construction into separate component non-overlapping parts,
* empty pieces of the path are discarded, and then these
* non-empty and non-overlapping properties are maintained
* through all subsequent CAG operations. Outlines of different
* component sub-paths may touch each other, as long as they
* do not cross so that their enclosed areas overlap.
* <li>The geometry of the path describing the outline of the
* {@code Area} resembles the path from which it was
* constructed only in that it describes the same enclosed
* 2-dimensional area, but may use entirely different types
* and ordering of the path segments to do so.
* </ul>
* Interesting issues which are not always obvious when using
* the {@code Area} include:
* <ul>
* <li>Creating an {@code Area} from an unclosed (open)
* {@code Shape} results in a closed outline in the
* {@code Area} object.
* <li>Creating an {@code Area} from a {@code Shape}
* which encloses no area (even when "closed") produces an
* empty {@code Area}. A common example of this issue
* is that producing an {@code Area} from a line will
* be empty since the line encloses no area. An empty
* {@code Area} will iterate no geometry in its
* {@code PathIterator} objects.
* <li>A self-intersecting {@code Shape} may be split into
* two (or more) sub-paths each enclosing one of the
* non-intersecting portions of the original path.
* <li>An {@code Area} may take more path segments to
* describe the same geometry even when the original
* outline is simple and obvious. The analysis that the
* {@code Area} class must perform on the path may
* not reflect the same concepts of "simple and obvious"
* as a human being perceives.
* </ul>
*
* @since 1.2
*/
public class Area implements Shape, Cloneable {
private static Vector<Curve> EmptyCurves = new Vector<>();
private Vector<Curve> curves;
/**
* Default constructor which creates an empty area.
* @since 1.2
*/
public Area() {
curves = EmptyCurves;
}
/**
* The {@code Area} class creates an area geometry from the
* specified {@link Shape} object. The geometry is explicitly
* closed, if the {@code Shape} is not already closed. The
* fill rule (even-odd or winding) specified by the geometry of the
* {@code Shape} is used to determine the resulting enclosed area.
* @param s the {@code Shape} from which the area is constructed
* @throws NullPointerException if {@code s} is null
* @since 1.2
*/
public Area(Shape s) {
if (s instanceof Area) {
curves = ((Area) s).curves;
} else {
curves = pathToCurves(s.getPathIterator(null));
}
}
private static Vector<Curve> pathToCurves(PathIterator pi) {
Vector<Curve> curves = new Vector<>();
int windingRule = pi.getWindingRule();
// coords array is big enough for holding:
// coordinates returned from currentSegment (6)
// OR
// two subdivided quadratic curves (2+4+4=10)
// AND
// 0-1 horizontal splitting parameters
// OR
// 2 parametric equation derivative coefficients
// OR
// three subdivided cubic curves (2+6+6+6=20)
// AND
// 0-2 horizontal splitting parameters
// OR
// 3 parametric equation derivative coefficients
double[] coords = new double[23];
double movx = 0, movy = 0;
double curx = 0, cury = 0;
double newx, newy;
while (!pi.isDone()) {
switch (pi.currentSegment(coords)) {
case PathIterator.SEG_MOVETO:
Curve.insertLine(curves, curx, cury, movx, movy);
curx = movx = coords[0];
cury = movy = coords[1];
Curve.insertMove(curves, movx, movy);
break;
case PathIterator.SEG_LINETO:
newx = coords[0];
newy = coords[1];
Curve.insertLine(curves, curx, cury, newx, newy);
curx = newx;
cury = newy;
break;
case PathIterator.SEG_QUADTO:
newx = coords[2];
newy = coords[3];
Curve.insertQuad(curves, curx, cury, coords);
curx = newx;
cury = newy;
break;
case PathIterator.SEG_CUBICTO:
newx = coords[4];
newy = coords[5];
Curve.insertCubic(curves, curx, cury, coords);
curx = newx;
cury = newy;
break;
case PathIterator.SEG_CLOSE:
Curve.insertLine(curves, curx, cury, movx, movy);
curx = movx;
cury = movy;
break;
}
pi.next();
}
Curve.insertLine(curves, curx, cury, movx, movy);
AreaOp operator;
if (windingRule == PathIterator.WIND_EVEN_ODD) {
operator = new AreaOp.EOWindOp();
} else {
operator = new AreaOp.NZWindOp();
}
return operator.calculate(curves, EmptyCurves);
}
/**
* Adds the shape of the specified {@code Area} to the
* shape of this {@code Area}.
* The resulting shape of this {@code Area} will include
* the union of both shapes, or all areas that were contained
* in either this or the specified {@code Area}.
* <pre>
* // Example:
* Area a1 = new Area([triangle 0,0 => 8,0 => 0,8]);
* Area a2 = new Area([triangle 0,0 => 8,0 => 8,8]);
* a1.add(a2);
*
* a1(before) + a2 = a1(after)
*
* ################ ################ ################
* ############## ############## ################
* ############ ############ ################
* ########## ########## ################
* ######## ######## ################
* ###### ###### ###### ######
* #### #### #### ####
* ## ## ## ##
* </pre>
* @param rhs the {@code Area} to be added to the
* current shape
* @throws NullPointerException if {@code rhs} is null
* @since 1.2
*/
public void add(Area rhs) {
curves = new AreaOp.AddOp().calculate(this.curves, rhs.curves);
invalidateBounds();
}
/**
* Subtracts the shape of the specified {@code Area} from the
* shape of this {@code Area}.
* The resulting shape of this {@code Area} will include
* areas that were contained only in this {@code Area}
* and not in the specified {@code Area}.
* <pre>
* // Example:
* Area a1 = new Area([triangle 0,0 => 8,0 => 0,8]);
* Area a2 = new Area([triangle 0,0 => 8,0 => 8,8]);
* a1.subtract(a2);
*
* a1(before) - a2 = a1(after)
*
* ################ ################
* ############## ############## ##
* ############ ############ ####
* ########## ########## ######
* ######## ######## ########
* ###### ###### ######
* #### #### ####
* ## ## ##
* </pre>
* @param rhs the {@code Area} to be subtracted from the
* current shape
* @throws NullPointerException if {@code rhs} is null
* @since 1.2
*/
public void subtract(Area rhs) {
curves = new AreaOp.SubOp().calculate(this.curves, rhs.curves);
invalidateBounds();
}
/**
* Sets the shape of this {@code Area} to the intersection of
* its current shape and the shape of the specified {@code Area}.
* The resulting shape of this {@code Area} will include
* only areas that were contained in both this {@code Area}
* and also in the specified {@code Area}.
* <pre>
* // Example:
* Area a1 = new Area([triangle 0,0 => 8,0 => 0,8]);
* Area a2 = new Area([triangle 0,0 => 8,0 => 8,8]);
* a1.intersect(a2);
*
* a1(before) intersect a2 = a1(after)
*
* ################ ################ ################
* ############## ############## ############
* ############ ############ ########
* ########## ########## ####
* ######## ########
* ###### ######
* #### ####
* ## ##
* </pre>
* @param rhs the {@code Area} to be intersected with this
* {@code Area}
* @throws NullPointerException if {@code rhs} is null
* @since 1.2
*/
public void intersect(Area rhs) {
curves = new AreaOp.IntOp().calculate(this.curves, rhs.curves);
invalidateBounds();
}
/**
* Sets the shape of this {@code Area} to be the combined area
* of its current shape and the shape of the specified {@code Area},
* minus their intersection.
* The resulting shape of this {@code Area} will include
* only areas that were contained in either this {@code Area}
* or in the specified {@code Area}, but not in both.
* <pre>
* // Example:
* Area a1 = new Area([triangle 0,0 => 8,0 => 0,8]);
* Area a2 = new Area([triangle 0,0 => 8,0 => 8,8]);
* a1.exclusiveOr(a2);
*
* a1(before) xor a2 = a1(after)
*
* ################ ################
* ############## ############## ## ##
* ############ ############ #### ####
* ########## ########## ###### ######
* ######## ######## ################
* ###### ###### ###### ######
* #### #### #### ####
* ## ## ## ##
* </pre>
* @param rhs the {@code Area} to be exclusive ORed with this
* {@code Area}.
* @throws NullPointerException if {@code rhs} is null
* @since 1.2
*/
public void exclusiveOr(Area rhs) {
curves = new AreaOp.XorOp().calculate(this.curves, rhs.curves);
invalidateBounds();
}
/**
* Removes all of the geometry from this {@code Area} and
* restores it to an empty area.
* @since 1.2
*/
public void reset() {
curves = new Vector<>();
invalidateBounds();
}
/**
* Tests whether this {@code Area} object encloses any area.
* @return {@code true} if this {@code Area} object
* represents an empty area; {@code false} otherwise.
* @since 1.2
*/
public boolean isEmpty() {
return (curves.size() == 0);
}
/**
* Tests whether this {@code Area} consists entirely of
* straight edged polygonal geometry.
* @return {@code true} if the geometry of this
* {@code Area} consists entirely of line segments;
* {@code false} otherwise.
* @since 1.2
*/
public boolean isPolygonal() {
Enumeration<Curve> enum_ = curves.elements();
while (enum_.hasMoreElements()) {
if (enum_.nextElement().getOrder() > 1) {
return false;
}
}
return true;
}
/**
* Tests whether this {@code Area} is rectangular in shape.
* @return {@code true} if the geometry of this
* {@code Area} is rectangular in shape; {@code false}
* otherwise.
* @since 1.2
*/
public boolean isRectangular() {
int size = curves.size();
if (size == 0) {
return true;
}
if (size > 3) {
return false;
}
Curve c1 = curves.get(1);
Curve c2 = curves.get(2);
if (c1.getOrder() != 1 || c2.getOrder() != 1) {
return false;
}
if (c1.getXTop() != c1.getXBot() || c2.getXTop() != c2.getXBot()) {
return false;
}
if (c1.getYTop() != c2.getYTop() || c1.getYBot() != c2.getYBot()) {
// One might be able to prove that this is impossible...
return false;
}
return true;
}
/**
* Tests whether this {@code Area} is comprised of a single
* closed subpath. This method returns {@code true} if the
* path contains 0 or 1 subpaths, or {@code false} if the path
* contains more than 1 subpath. The subpaths are counted by the
* number of {@link PathIterator#SEG_MOVETO SEG_MOVETO} segments
* that appear in the path.
* @return {@code true} if the {@code Area} is comprised
* of a single basic geometry; {@code false} otherwise.
* @since 1.2
*/
public boolean isSingular() {
if (curves.size() < 3) {
return true;
}
Enumeration<Curve> enum_ = curves.elements();
enum_.nextElement(); // First Order0 "moveto"
while (enum_.hasMoreElements()) {
if (enum_.nextElement().getOrder() == 0) {
return false;
}
}
return true;
}
private Rectangle2D cachedBounds;
private void invalidateBounds() {
cachedBounds = null;
}
private Rectangle2D getCachedBounds() {
if (cachedBounds != null) {
return cachedBounds;
}
Rectangle2D r = new Rectangle2D.Double();
if (curves.size() > 0) {
Curve c = curves.get(0);
// First point is always an order 0 curve (moveto)
r.setRect(c.getX0(), c.getY0(), 0, 0);
for (int i = 1; i < curves.size(); i++) {
curves.get(i).enlarge(r);
}
}
return (cachedBounds = r);
}
/**
* Returns a high precision bounding {@link Rectangle2D} that
* completely encloses this {@code Area}.
* <p>
* The Area class will attempt to return the tightest bounding
* box possible for the Shape. The bounding box will not be
* padded to include the control points of curves in the outline
* of the Shape, but should tightly fit the actual geometry of
* the outline itself.
* @return the bounding {@code Rectangle2D} for the
* {@code Area}.
* @since 1.2
*/
public Rectangle2D getBounds2D() {
return getCachedBounds().getBounds2D();
}
/**
* Returns a bounding {@link Rectangle} that completely encloses
* this {@code Area}.
* <p>
* The Area class will attempt to return the tightest bounding
* box possible for the Shape. The bounding box will not be
* padded to include the control points of curves in the outline
* of the Shape, but should tightly fit the actual geometry of
* the outline itself. Since the returned object represents
* the bounding box with integers, the bounding box can only be
* as tight as the nearest integer coordinates that encompass
* the geometry of the Shape.
* @return the bounding {@code Rectangle} for the
* {@code Area}.
* @since 1.2
*/
public Rectangle getBounds() {
return getCachedBounds().getBounds();
}
/**
* Returns an exact copy of this {@code Area} object.
* @return Created clone object
* @since 1.2
*/
public Object clone() {
return new Area(this);
}
/**
* Tests whether the geometries of the two {@code Area} objects
* are equal.
* This method will return false if the argument is null.
* @param other the {@code Area} to be compared to this
* {@code Area}
* @return {@code true} if the two geometries are equal;
* {@code false} otherwise.
* @since 1.2
*/
public boolean equals(Area other) {
// REMIND: A *much* simpler operation should be possible...
// Should be able to do a curve-wise comparison since all Areas
// should evaluate their curves in the same top-down order.
if (other == this) {
return true;
}
if (other == null) {
return false;
}
Vector<Curve> c = new AreaOp.XorOp().calculate(this.curves, other.curves);
return c.isEmpty();
}
/**
* Transforms the geometry of this {@code Area} using the specified
* {@link AffineTransform}. The geometry is transformed in place, which
* permanently changes the enclosed area defined by this object.
* @param t the transformation used to transform the area
* @throws NullPointerException if {@code t} is null
* @since 1.2
*/
public void transform(AffineTransform t) {
if (t == null) {
throw new NullPointerException("transform must not be null");
}
// REMIND: A simpler operation can be performed for some types
// of transform.
curves = pathToCurves(getPathIterator(t));
invalidateBounds();
}
/**
* Creates a new {@code Area} object that contains the same
* geometry as this {@code Area} transformed by the specified
* {@code AffineTransform}. This {@code Area} object
* is unchanged.
* @param t the specified {@code AffineTransform} used to transform
* the new {@code Area}
* @throws NullPointerException if {@code t} is null
* @return a new {@code Area} object representing the transformed
* geometry.
* @since 1.2
*/
public Area createTransformedArea(AffineTransform t) {
Area a = new Area(this);
a.transform(t);
return a;
}
/**
* {@inheritDoc}
* @since 1.2
*/
public boolean contains(double x, double y) {
if (!getCachedBounds().contains(x, y)) {
return false;
}
Enumeration<Curve> enum_ = curves.elements();
int crossings = 0;
while (enum_.hasMoreElements()) {
Curve c = enum_.nextElement();
crossings += c.crossingsFor(x, y);
}
return ((crossings & 1) == 1);
}
/**
* {@inheritDoc}
* @since 1.2
*/
public boolean contains(Point2D p) {
return contains(p.getX(), p.getY());
}
/**
* {@inheritDoc}
* @since 1.2
*/
public boolean contains(double x, double y, double w, double h) {
if (w < 0 || h < 0) {
return false;
}
if (!getCachedBounds().contains(x, y, w, h)) {
return false;
}
Crossings c = Crossings.findCrossings(curves, x, y, x+w, y+h);
return (c != null && c.covers(y, y+h));
}
/**
* {@inheritDoc}
* @since 1.2
*/
public boolean contains(Rectangle2D r) {
return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight());
}
/**
* {@inheritDoc}
* @since 1.2
*/
public boolean intersects(double x, double y, double w, double h) {
if (w < 0 || h < 0) {
return false;
}
if (!getCachedBounds().intersects(x, y, w, h)) {
return false;
}
Crossings c = Crossings.findCrossings(curves, x, y, x+w, y+h);
return (c == null || !c.isEmpty());
}
/**
* {@inheritDoc}
* @since 1.2
*/
public boolean intersects(Rectangle2D r) {
return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight());
}
/**
* Creates a {@link PathIterator} for the outline of this
* {@code Area} object. This {@code Area} object is unchanged.
* @param at an optional {@code AffineTransform} to be applied to
* the coordinates as they are returned in the iteration, or
* {@code null} if untransformed coordinates are desired
* @return the {@code PathIterator} object that returns the
* geometry of the outline of this {@code Area}, one
* segment at a time.
* @since 1.2
*/
public PathIterator getPathIterator(AffineTransform at) {
return new AreaIterator(curves, at);
}
/**
* Creates a {@code PathIterator} for the flattened outline of
* this {@code Area} object. Only uncurved path segments
* represented by the SEG_MOVETO, SEG_LINETO, and SEG_CLOSE point
* types are returned by the iterator. This {@code Area}
* object is unchanged.
* @param at an optional {@code AffineTransform} to be
* applied to the coordinates as they are returned in the
* iteration, or {@code null} if untransformed coordinates
* are desired
* @param flatness the maximum amount that the control points
* for a given curve can vary from colinear before a subdivided
* curve is replaced by a straight line connecting the end points
* @return the {@code PathIterator} object that returns the
* geometry of the outline of this {@code Area}, one segment
* at a time.
* @since 1.2
*/
public PathIterator getPathIterator(AffineTransform at, double flatness) {
/**代码未完, 请加载全部代码(NowJava.com).**/