All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.geotoolkit.display.shape.TransformedShape Maven / Gradle / Ivy

/*
 *    Geotoolkit.org - An Open Source Java GIS Toolkit
 *    http://www.geotoolkit.org
 *
 *    (C) 2003-2012, Open Source Geospatial Foundation (OSGeo)
 *    (C) 2009-2012, Geomatys
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library 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
 *    Lesser General Public License for more details.
 */
package org.geotoolkit.display.shape;

import java.awt.Shape;
import java.awt.Rectangle;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.PathIterator;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.io.IOException;
import java.io.ObjectInputStream;
import net.jcip.annotations.NotThreadSafe;

import org.geotoolkit.util.Utilities;
import org.geotoolkit.util.logging.Logging;
import org.geotoolkit.referencing.operation.matrix.XAffineTransform;

import static org.geotoolkit.util.ArgumentChecks.ensureNonNull;


/**
 * Applies an arbitrary {@link AffineTransform} on a {@link Shape}. A {@code TransformedShape}
 * instance is a view over a shape, i.e. the shape coordinates are transformed on the
 * fly, never copied.
 * 

* The shape to be transformed is specified by {@link #setOriginalShape(Shape)}. The transform * to apply is specified by the inherited {@code AffineTransform}, which can be modified at any * time using all its usual methods. Every changes to the original shape or to the inherited * transform take immediate effect since this class is only a view. * * {@note This class is final because extending directly AffineTransform is not a * good example of object-oriented programming, since a transformed shape is not a special * kind of affine transform. We did that as a convenience for allowing frequent modifications * of the transform by direct access to the AffineTransform methods, but this is * not an example that shoud be replicated.} * * {@section Performance cost} * When {@linkplain #getPathIterator(AffineTransform) iterating over the shape boundary}, the only * performance cost is a {@linkplain #concatenate(AffineTransform) matrix multiplication} applied * before the iterator is created - the cost of the iteration itself usually stay * unchanged. * * {@section Example} * The {@link Arrow2D} class is unconditionally oriented toward 0° arithmetic. * In order to draw a field of arrows in arbitrary directions, the code below can be used: * * {@preformat java * protected void paint(Graphics2D graphics) { * Arrow2D arrow = new Arrow2D(...); * TransformedShape orientedArrow = new TransformedShape(arrow); * for (int i=0; ireal world" units. * * @author Martin Desruisseaux (IRD, Geomatys) * @version 3.20 * * @see ProjectedShape * * @since 2.0 * @module */ @NotThreadSafe public final class TransformedShape extends AffineTransform implements Shape { /** * For cross-version compatibility. */ private static final long serialVersionUID = 3541606381365714951L; /** * The original shape for which this {@code TransformedShape} is a view. */ private Shape shape; /** * A temporary point. */ private transient Point2D.Double point; /** * A temporary rectangle. */ private transient Rectangle2D.Double rectangle; /** * Constructs a {@code TransformedShape} initialized to the identity transform * with no original shape. The {@link #setOriginalShape(Shape)} method must be * invoked at least once before this object can be used. */ public TransformedShape() { initTransientFields(); } /** * Constructs a {@code TransformedShape} initialized to the given transform with no * original shape. The {@link #setOriginalShape(Shape)} method must be invoked at * least once before this object can be used. * * @param transform The initial affine transform. */ public TransformedShape(final AffineTransform transform) { super(transform); initTransientFields(); } /** * Constructs a view of the given shape initialized to the identity transform. * * @param shape The shape to wrap in the new {@code TransformedShape} instance. */ public TransformedShape(final Shape shape) { ensureNonNull("shape", shape); this.shape = shape; initTransientFields(); } /** * Constructs a {@code TransformedShape} initialized to the given transform and shape. * * @param shape The shape to wrap in the new {@code TransformedShape} instance. * @param transform The initial affine transform. * * @since 3.20 */ public TransformedShape(final Shape shape, final AffineTransform transform) { super(transform); ensureNonNull("shape", shape); this.shape = shape; initTransientFields(); } /** * Initializes the transient fields. Invoked on construction and on deserialization. */ private void initTransientFields() { point = new Point2D.Double(); rectangle = new Rectangle2D.Double(); } /** * Returns the original shape for which this {@code TransformedShape} is a view. * * @return The shape wrapped by this {@code TransformedShape} instance. */ public Shape getOriginalShape() { return shape; } /** * Sets the shape for which this {@code TransformedShape} will be a view. * * @param shape The shape to wrap in this {@code TransformedShape} instance. */ public void setOriginalShape(final Shape shape) { ensureNonNull("shape", shape); this.shape = shape; } /** * Returns the 6 coefficients values as {@code float} numbers. The coefficients are stored * in the same order than {@link #getMatrix(double[])}, with the first coefficient stored * in {@code matrix[offset]} and the last coefficient stored in {@code matrix[offset+5]} * * @param matrix The matrix where to store the coefficient values. * @param offset Index of the first element where to write in the {@code matrix} array. */ public void getMatrix(final float[] matrix, int offset) { matrix[ offset] = (float) getScaleX(); // m00 matrix[++offset] = (float) getShearY(); // m10 matrix[++offset] = (float) getShearX(); // m01 matrix[++offset] = (float) getScaleY(); // m11 matrix[++offset] = (float) getTranslateX(); // m02 matrix[++offset] = (float) getTranslateY(); // m12 } /** * Sets the transform from a flat matrix. The coefficients are read in the same order * than they were stored by {@link #getMatrix}. * * @param matrix The flat matrix. * @param offset Index of the first element to use in {@code matrix} array. */ public void setTransform(final float[] matrix, int offset) { setTransform(matrix[ offset], matrix[++offset], matrix[++offset], matrix[++offset], matrix[++offset], matrix[++offset]); } /** * Tests if the specified coordinate is inside the boundary of this shape. * This method might conservatively return {@code false} if the transform is not invertible. *

* The default implementation delegates to {@link #contains(Point2D)}. * * @param x The x ordinate of the point to be tested. * @param y The y ordinate of the point to be tested. * @return {@code true} if this shape contains the given point. */ @Override public boolean contains(final double x, final double y) { final Point2D.Double p = point; p.x = x; p.y = y; return contains(p); } /** * Tests if a specified {@link Point2D} is inside the boundary of this shape. * This method might conservatively return {@code false} if the transform is not invertible. * * @param p The point to be tested. * @return {@code true} if this shape contains the given point. */ @Override public boolean contains(final Point2D p) { try { return shape.contains(inverseTransform(p, point)); } catch (NoninvertibleTransformException exception) { Logging.recoverableException(TransformedShape.class, "contains", exception); return false; } } /** * Tests if the interior of this shape entirely contains the specified rectangular area. * This method might conservatively return {@code false} if the transform is not invertible * or if the calculation of {@code originalShape.contains(...)} is too expensive. *

* The default implementation delegates to {@link #contains(Rectangle2D)}. * * @param x The minimal x ordinate of the rectangle to be tested. * @param y The minimal y ordinate of the rectangle to be tested. * @param width The width of the rectangle to be tested. * @param height The height of the rectangle to be tested. * @return {@code true} if this shape contains the given rectangle. */ @Override public boolean contains(final double x, final double y, final double width, final double height) { final Rectangle2D.Double r = rectangle; r.x = x; r.y = y; r.width = width; r.height = height; return contains(r); } /** * Tests if the interior of this shape entirely contains the specified rectangle. * This method might conservatively return {@code false} if the transform is not * invertible or if the calculation of {@code originalShape.contains(...)} is too * expensive. * * @param r The rectangle to be tested. * @return {@code true} if this shape contains the given rectangle. */ @Override public boolean contains(final Rectangle2D r) { try { return shape.contains(XAffineTransform.inverseTransform(this, r, rectangle)); } catch (NoninvertibleTransformException exception) { Logging.recoverableException(TransformedShape.class, "contains", exception); return false; // Consistent with the Shape interface contract. } } /** * Tests if the interior of this shape intersects the interior of a specified rectangular area. * This method might conservatively return {@code true} if the transform is not invertible or * if the calculation of {@code originalShape.intersects(...)} is too expensive. *

* The default implementation delegates to {@link #intersects(Rectangle2D)}. * * @param x The minimal x ordinate of the rectangle to be tested. * @param y The minimal y ordinate of the rectangle to be tested. * @param width The width of the rectangle to be tested. * @param height The height of the rectangle to be tested. * @return {@code true} if this shape intersects the given rectangle. */ @Override public boolean intersects(double x, double y, double width, double height) { final Rectangle2D.Double r = rectangle; r.x = x; r.y = y; r.width = width; r.height = height; return intersects(r); } /** * Tests if the interior of this shape intersects the interior of a specified rectangle. * This method might conservatively return {@code true} if the transform is not invertible * or if the calculation of {@code originalShape.intersects(...)} is too expensive. * * @param r The rectangle to be tested. * @return {@code true} if this shape intersects the given rectangle. */ @Override public boolean intersects(final Rectangle2D r) { try { return shape.intersects(XAffineTransform.inverseTransform(this, r, rectangle)); } catch (NoninvertibleTransformException exception) { Logging.recoverableException(TransformedShape.class, "intersects", exception); return true; // Consistent with the Shape interface contract. } } /** * Returns an integer rectangle that completely encloses this shape. */ @Override public Rectangle getBounds() { // Delegates to getBounds2D(), not getBounds(), because a scale greater than 1 // will use the fraction digits of a number that would be otherwise rounded. return (Rectangle) XAffineTransform.transform(this, shape.getBounds2D(), new Rectangle()); } /** * Returns a high precision and more accurate bounding box of the * shape than the {@link #getBounds} method. */ @Override public Rectangle2D getBounds2D() { return XAffineTransform.transform(this, shape.getBounds2D(), null); } /** * Returns an iterator object that iterates along the shape boundary * and provides access to the geometry of the shape outline. */ @Override public PathIterator getPathIterator(AffineTransform at) { if (!isIdentity()) { if (at == null || at.isIdentity()) { return shape.getPathIterator(this); } at = new AffineTransform(at); at.concatenate(this); } return shape.getPathIterator(at); } /** * Returns an iterator object that iterates along the shape boundary and provides * access to a flattened view of the shape outline geometry. */ @Override public PathIterator getPathIterator(AffineTransform at, final double flatness) { if (!isIdentity()) { if (at == null || at.isIdentity()) { return shape.getPathIterator(this, flatness); } at = new AffineTransform(at); at.concatenate(this); } return shape.getPathIterator(at, flatness); } /** * Returns a hash code value for this shape. * * @since 3.20 */ @Override public int hashCode() { int code = super.hashCode() ^ (int) serialVersionUID; if (shape != null) { code ^= shape.hashCode(); } return code; } /** * Compares this shape with the given object for equality. Do not compare * {@code TransformedShape} instances with plain {@link AffineTransform} instances, * because the "be symmetric" part of the {@link Object#equals(Object)} contract * can not be enforced. * * @param object The object to compare with this shape. * * @since 3.20 */ @Override public boolean equals(final Object object) { if (object instanceof TransformedShape && super.equals(object)) { return Utilities.equals(shape, ((TransformedShape) object).shape); } return false; } /** * Invoked on deserialization. */ private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { in.readObject(); initTransientFields(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy