Resizes or translates a Shape : Transform « 2D Graphics GUI « Java






Resizes or translates a Shape

 

/**
 * 
 * JFreeReport : a free Java reporting library
 * 
 *
 * Project Info:  http://reporting.pentaho.org/
 *
 * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
 *
 * 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;
 * either version 2.1 of the License, or (at your option) any later version.
 *
 * 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.
 *
 * You should have received a copy of the GNU Lesser General Public License along with this
 * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
 * in the United States and other countries.]
 *
 * ------------
 * ShapeTransform.java
 * ------------
 * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
 */

import java.awt.Dimension;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Dimension2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RectangularShape;

/**
 * Utility class, which resizes or translates a Shape. The class contains
 * special handlers for Rectangles and Lines.
 * 
 * @author Thomas Morgner
 */
public final strictfp class ShapeTransform {
  // some constants for the cohenen-algorithmus
  /**
   * Flag for point lying left of clipping area.
   */
  public static final int LEFT = 0x01;

  /**
   * Flag for point lying between horizontal bounds of area.
   */
  public static final int H_CENTER = 0x02;

  /**
   * Flag for point lying right of clipping area.
   */
  public static final int RIGHT = 0x04;

  /**
   * Flag for point lying "below" clipping area.
   */
  public static final int BELOW = 0x10;

  /**
   * Flag for point lying between vertical bounds of clipping area.
   */
  public static final int V_CENTER = 0x20;

  /**
   * Flag for point lying "above" clipping area.
   */
  public static final int ABOVE = 0x40;

  /** A simple way to handle rounding errors. */
  private static final double DELTA = 0.000001;

  /**
   * Mask for points which are inside.
   */
  public static final int INSIDE = H_CENTER | V_CENTER;

  /**
   * Mask for points which are outside.
   */
  public static final int OUTSIDE = LEFT | RIGHT | BELOW | ABOVE;

  /**
   * Default constructor.
   */
  private ShapeTransform() {
  }

  /**
   * Resizes a line. Instead of creating a GeneralPath (as AffineTransform's
   * scale would do) we modify the line itself.
   * 
   * @param line
   *          the line that should be scaled
   * @param width
   *          the new width of the line bounds
   * @param height
   *          the new height of the line bounds
   * @return the scale Line2D object.
   */
  private static Line2D resizeLine(final Line2D line, final double width, final double height) {
    final Line2D newLine = getNormalizedLine(line);
    final Point2D p1 = newLine.getP1();
    final Point2D p2 = newLine.getP2();
    final double normPointX = (p1.getX() - p2.getX());
    final double normPointY = (p1.getY() - p2.getY());
    final double scaleX = (normPointX == 0) ? 1 : width / Math.abs(normPointX);
    final double scaleY = (normPointY == 0) ? 1 : height / Math.abs(normPointY);
    p2.setLocation((p2.getX() - p1.getX()) * scaleX + p1.getX(), (p2.getY() - p1.getY()) * scaleY
        + p1.getY());
    newLine.setLine(p1, p2);
    return newLine;
  }

  /**
   * Normalize the line; the point with the lowest X is the primary point, if
   * both points have the same X, that point with the lowest Y value wins.
   * 
   * @param line
   *          the original line
   * @return the normalized line
   */
  private static Line2D getNormalizedLine(final Line2D line) {
    final Line2D lineClone = (Line2D) line.clone();

    final Point2D p1 = line.getP1();
    final Point2D p2 = line.getP2();
    if (p1.getX() < p2.getX()) {
      return lineClone;
    }
    if (p1.getX() > p2.getX()) {
      lineClone.setLine(p2, p1);
      return lineClone;
    }
    if (p1.getY() < p2.getY()) {
      return lineClone;
    }
    lineClone.setLine(p2, p1);
    return lineClone;
  }

  /**
   * Resizes a shape, so that the shape has the given width and height, but the
   * origin of the shape does not change. <p/> Unlike the AffineTransform, this
   * method tries to preserve the Shape's Type.
   * 
   * @param s
   *          the shape
   * @param width
   *          the new width
   * @param height
   *          the new height
   * @return the resized shape.
   */
  public static Shape resizeShape(final Shape s, final float width, final float height) {
    if (s instanceof Line2D) {
      return resizeLine((Line2D) s, width, height);
    }
    if (s instanceof RectangularShape) {
      return resizeRect((RectangularShape) s, width, height);
    }
    return transformShape(s, true, false, new Dimension((int) width, (int) height));
  }

  /**
   * Resizes a rectangle. This works for real rectangles and produces funny
   * results for RoundRects etc ..
   * 
   * @param rectangularShape
   *          the rectangle
   * @param width
   *          the new width of the rectangle
   * @param height
   *          the new height of the rectangle.
   * @return the resized rectangle.
   */
  public static Shape resizeRect(final RectangularShape rectangularShape, final double width,
      final double height) {
    final RectangularShape retval = (RectangularShape) rectangularShape.clone();
    retval.setFrame(retval.getX(), retval.getY(), width, height);
    return retval;
  }

  /**
   * Translates the given shape. The shape is translated to the origin supplied
   * in <code>point</code>. If scaling is requested, the shape will also be
   * scaled using an AffineTransform.
   * 
   * @param s
   *          the shape that should be transformed
   * @param scale
   *          true, if the shape should be scaled, false otherwise
   * @param keepAR
   *          true, if the scaled shape should keep the aspect ratio
   * @param width
   *          the target width.
   * @param height
   *          the target height.
   * @return the transformed shape
   */
  public static Shape transformShape(final Shape s, final boolean scale, final boolean keepAR,
      final double width, final double height) {
    /**
     * Always scale to the maximum bounds ...
     */
    if (scale) {

      final Rectangle2D boundsShape = s.getBounds2D();
      final double w = boundsShape.getWidth();
      final double h = boundsShape.getHeight();
      double scaleX = 1;

      if (w != 0) {
        scaleX = width / w;
      }

      double scaleY = 1;
      if (h != 0) {
        scaleY = height / h;
      }

      if (scaleX != 1 || scaleY != 1) {
        if (s instanceof RectangularShape) {
          return ShapeTransform.resizeRect((RectangularShape) s, w * scaleX, h * scaleY);
        }
        if (s instanceof Line2D) {
          return ShapeTransform.resizeLine((Line2D) s, w * scaleX, h * scaleY);
        }

        if (keepAR) {
          final double scaleFact = Math.min(scaleX, scaleY);
          return performDefaultTransformation(s, scaleFact, scaleFact);
        } else {
          return performDefaultTransformation(s, scaleX, scaleY);
        }
      }
    }
    return s;
  }

  /**
   * Translates the given shape. The shape is translated to the origin supplied
   * in <code>point</code>. If scaling is requested, the shape will also be
   * scaled using an AffineTransform.
   * 
   * @param s
   *          the shape that should be transformed
   * @param scale
   *          true, if the shape should be scaled, false otherwise
   * @param keepAR
   *          true, if the scaled shape should keep the aspect ratio
   * @param dim
   *          the target dimension.
   * @return the transformed shape
   */
  public static Shape transformShape(final Shape s, final boolean scale, final boolean keepAR,
      final Dimension2D dim) {
    return transformShape(s, scale, keepAR, dim.getWidth(), dim.getHeight());
  }

  /**
   * Clips the given shape to the given bounds. If the shape is a Line2D, manual
   * clipping is performed, as the built in Area does not handle lines.
   * 
   * @param s
   *          the shape to be clipped
   * @param bounds
   *          the bounds to which the shape should be clipped
   * @return the clipped shape.
   */
  public static Shape performCliping(final Shape s, final Rectangle2D bounds) {
    if (s instanceof Line2D) {
      final Line2D line = (Line2D) s;
      final Point2D[] clipped = getClipped(line.getX1(), line.getY1(), line.getX2(), line.getY2(),
          -DELTA, DELTA + bounds.getWidth(), -DELTA, DELTA + bounds.getHeight());
      if (clipped == null) {
        return new GeneralPath();
      }
      return new Line2D.Float(clipped[0], clipped[1]);
    }

    final Rectangle2D boundsCorrected = bounds.getBounds2D();
    boundsCorrected.setRect(-DELTA, -DELTA, DELTA + boundsCorrected.getWidth(), DELTA
        + boundsCorrected.getHeight());
    final Area a = new Area(boundsCorrected);
    if (a.isEmpty()) {
      // don't clip ... Area does not like lines
      // operations with lines always result in an empty Bounds:(0,0,0,0) area
      return new GeneralPath();
    }

    final Area clipArea = new Area(s);
    a.intersect(clipArea);
    return a;

  }

  /**
   * Scales a given shape. The shape is first normalized, then scaled and
   * finally brought back into its original position.
   * 
   * @param shape
   *          the shape to be scaled
   * @param scaleX
   *          the horizontal scaling factor
   * @param scaleY
   *          the vertical scaling factor
   * @return the scaled shape
   */
  private static Shape performDefaultTransformation(final Shape shape, final double scaleX,
      final double scaleY) {
    /**
     * Apply the normalisation shape transform ... bring the shape to pos (0,0)
     */
    final Rectangle2D bounds = shape.getBounds2D();
    AffineTransform af = AffineTransform.getTranslateInstance(0 - bounds.getX(), 0 - bounds.getY());
    // apply normalisation translation ...
    Shape s = af.createTransformedShape(shape);

    af = AffineTransform.getScaleInstance(scaleX, scaleY);
    // apply scaling ...
    s = af.createTransformedShape(s);

    // now retranslate the shape to its original position ...
    af = AffineTransform.getTranslateInstance(bounds.getX(), bounds.getY());
    return af.createTransformedShape(s);
  }

  /**
   * Translates a given shape. Special care is taken to preserve the shape's
   * original class, if the shape is a rectangle or a line.
   * 
   * @param s
   *          the shape
   * @param x
   *          the x coordinate where the shape is translated to
   * @param y
   *          the y coordinate where the shape is translated to
   * @return the translated shape
   */
  public static Shape translateShape(final Shape s, final double x, final double y) {
    if (s instanceof RectangularShape) {
      final RectangularShape rect = (RectangularShape) s;
      final RectangularShape retval = (RectangularShape) rect.clone();
      retval.setFrame(retval.getX() + x, retval.getY() + y, retval.getWidth(), retval.getHeight());
      return retval;
    }
    if (s instanceof Line2D) {
      final Line2D line = (Line2D) s;
      final Line2D retval = (Line2D) line.clone();
      retval
          .setLine(retval.getX1() + x, retval.getY1() + y, retval.getX2() + x, retval.getY2() + y);
      return retval;
    }

    final AffineTransform af = AffineTransform.getTranslateInstance(x, y);
    return af.createTransformedShape(s);
  }

  /**
   * Calculate the clipping points of a line with a rectangle.
   * 
   * @param x1
   *          starting x of line
   * @param y1
   *          starting y of line
   * @param x2
   *          ending x of line
   * @param y2
   *          ending y of line
   * @param xmin
   *          lower left x of rectangle
   * @param xmax
   *          upper right x of rectangle
   * @param ymin
   *          lower left y of rectangle
   * @param ymax
   *          upper right y of rectangle
   * @return <code>null</code> (does not clip) or array of two points
   */
  public static Point2D[] getClipped(final double x1, final double y1, final double x2,
      final double y2, final double xmin, final double xmax, final double ymin, final double ymax) {
    int mask1 = 0; // position mask for first point
    if (x1 < xmin) {
      mask1 |= LEFT;
    } else if (x1 > xmax) {
      mask1 |= RIGHT;
    } else {
      mask1 |= H_CENTER;
    }
    if (y1 < ymin) {
      // btw: I know that in AWT y runs from down but I more used to
      // y pointing up and it makes no difference for the algorithms
      mask1 |= BELOW;
    } else if (y1 > ymax) {
      mask1 |= ABOVE;
    } else {
      mask1 |= V_CENTER;
    }

    int mask2 = 0; // position mask for second point
    if (x2 < xmin) {
      mask2 |= LEFT;
    } else if (x2 > xmax) {
      mask2 |= RIGHT;
    } else {
      mask2 |= H_CENTER;
    }
    if (y2 < ymin) {
      mask2 |= BELOW;
    } else if (y2 > ymax) {
      mask2 |= ABOVE;
    } else {
      mask2 |= V_CENTER;
    }

    final int mask = mask1 | mask2;

    if ((mask & OUTSIDE) == 0) {
      // fine. everything's internal
      final Point2D[] ret = new Point2D[2];
      ret[0] = new Point2D.Double(x1, y1);
      ret[1] = new Point2D.Double(x2, y2);
      return ret;
    } else if ((mask & (H_CENTER | LEFT)) == 0 || // everything's right
        (mask & (H_CENTER | RIGHT)) == 0 || // everything's left
        (mask & (V_CENTER | BELOW)) == 0 || // everything's above
        (mask & (V_CENTER | ABOVE)) == 0) { // everything's below
      // nothing to do
      return null;
    } else {
      // need clipping
      return getClipped(x1, y1, mask1, x2, y2, mask2, xmin, xmax, ymin, ymax);
    }
  }

  /**
   * Calculate the clipping points of a line with a rectangle.
   * 
   * @param x1
   *          starting x of line
   * @param y1
   *          starting y of line
   * @param mask1
   *          clipping info mask for starting point
   * @param x2
   *          ending x of line
   * @param y2
   *          ending y of line
   * @param mask2
   *          clipping info mask for ending point
   * @param xmin
   *          lower left x of rectangle
   * @param ymin
   *          lower left y of rectangle
   * @param xmax
   *          upper right x of rectangle
   * @param ymax
   *          upper right y of rectangle
   * @return <code>null</code> (does not clip) or array of two points
   */
  private static Point2D[] getClipped(final double x1, final double y1, final int mask1,
      final double x2, final double y2, final int mask2, final double xmin, final double xmax,
      final double ymin, final double ymax) {
    final int mask = mask1 ^ mask2;
    Point2D p1 = null;

    if (mask1 == INSIDE) {
      // point 1 is internal
      p1 = new Point2D.Double(x1, y1);
      if (mask == 0) {
        // both masks are the same, so the second point is inside, too
        final Point2D[] ret = new Point2D[2];
        ret[0] = p1;
        ret[1] = new Point2D.Double(x2, y2);
        return ret;
      }
    } else if (mask2 == INSIDE) {
      // point 2 is internal
      p1 = new Point2D.Double(x2, y2);
    }

    if ((mask & LEFT) != 0) {
      // System.out.println("Trying left");
      // try to calculate intersection with left line
      final Point2D p = intersect(x1, y1, x2, y2, xmin, ymin, xmin, ymax);
      if (p != null) {
        if (p1 == null) {
          p1 = p;
        } else {
          final Point2D[] ret = new Point2D[2];
          ret[0] = p1;
          ret[1] = p;
          return ret;
        }
      }
    }
    if ((mask & RIGHT) != 0) {
      // System.out.println("Trying right");
      // try to calculate intersection with left line
      final Point2D p = intersect(x1, y1, x2, y2, xmax, ymin, xmax, ymax);
      if (p != null) {
        if (p1 == null) {
          p1 = p;
        } else {
          final Point2D[] ret = new Point2D[2];
          ret[0] = p1;
          ret[1] = p;
          return ret;
        }
      }
    }
    if (mask1 == (LEFT | BELOW) || mask1 == (RIGHT | BELOW)) {
      // for exactly these two special cases use different sequence!

      if ((mask & ABOVE) != 0) {
        // System.out.println("Trying top");
        // try to calculate intersection with lower line
        final Point2D p = intersect(x1, y1, x2, y2, xmin, ymax, xmax, ymax);
        if (p != null) {
          if (p1 == null) {
            p1 = p;
          } else {
            final Point2D[] ret = new Point2D[2];
            ret[0] = p1;
            ret[1] = p;
            return ret;
          }
        }
      }
      if ((mask & BELOW) != 0) {
        // System.out.println("Trying bottom");
        // try to calculate intersection with lower line
        final Point2D p = intersect(x1, y1, x2, y2, xmin, ymin, xmax, ymin);
        if (p != null && p1 != null) {
          final Point2D[] ret = new Point2D[2];
          ret[0] = p1;
          ret[1] = p;
          return ret;
        }
      }
    } else {
      if ((mask & BELOW) != 0) {
        // System.out.println("Trying bottom");
        // try to calculate intersection with lower line
        final Point2D p = intersect(x1, y1, x2, y2, xmin, ymin, xmax, ymin);
        if (p != null) {
          if (p1 == null) {
            p1 = p;
          } else {
            final Point2D[] ret = new Point2D[2];
            ret[0] = p1;
            ret[1] = p;
            return ret;
          }
        }
      }
      if ((mask & ABOVE) != 0) {
        // System.out.println("Trying top");
        // try to calculate intersection with lower line
        final Point2D p = intersect(x1, y1, x2, y2, xmin, ymax, xmax, ymax);
        if (p != null && p1 != null) {
          final Point2D[] ret = new Point2D[2];
          ret[0] = p1;
          ret[1] = p;
          return ret;
        }
      }
    }

    // no (or not enough) intersections found
    return null;
  }

  /**
   * Intersect two lines.
   * 
   * @param x11
   *          starting x of 1st line
   * @param y11
   *          starting y of 1st line
   * @param x12
   *          ending x of 1st line
   * @param y12
   *          ending y of 1st line
   * @param x21
   *          starting x of 2nd line
   * @param y21
   *          starting y of 2nd line
   * @param x22
   *          ending x of 2nd line
   * @param y22
   *          ending y of 2nd line
   * @return intersection point or <code>null</code>
   */
  private static Point2D intersect(final double x11, final double y11, final double x12,
      final double y12, final double x21, final double y21, final double x22, final double y22) {
    final double dx1 = x12 - x11;
    final double dy1 = y12 - y11;
    final double dx2 = x22 - x21;
    final double dy2 = y22 - y21;
    final double det = (dx2 * dy1 - dy2 * dx1);

    if (det != 0.0) {
      final double mu = ((x11 - x21) * dy1 - (y11 - y21) * dx1) / det;
      if (mu >= 0.0 && mu <= 1.0) {
        return new Point2D.Double(x21 + mu * dx2, y21 + mu * dy2);
      }
    }

    return null;
  }

}

   
  








Related examples in the same category

1.Coordinate DemoCoordinate Demo
2.Rotation and coordinate translation
3.Scaling an object
4.Transform DemoTransform Demo
5.Transform ShearTransform Shear
6.Transform ScaleTransform Scale
7.Transform Rotation Translation Transform Rotation Translation
8.Transforme Rotation demoTransforme Rotation demo
9.Transform Translation and RotationTransform Translation and Rotation
10.Transform Translated RotationTransform Translated Rotation
11.Transform TranslationTransform Translation
12.Line transformation, rotation, shear,scale Line transformation, rotation, shear,scale
13.AffineTransform demoAffineTransform demo
14.Scaling a Drawn Image
15.Shearing a Drawn Image
16.Translating a Drawn Image
17.Rotating a Drawn Image
18.Create an complex shape by rotating an ellipse.
19.Scaling a Shape with AffineTransform
20.Shearing a Shape with AffineTransform
21.Perform shearing: use share() method.
22.Translating a Shape with AffineTransform
23.Rotating a Shape with AffineTransform
24.Rotating image using Java 2D AffineTransform class
25.Rotates a shape about the specified coordinates.
26.Creates and returns a translated shape.