Search code examples
javarotationgraphics2d

Java Graphics2D obtain rotated image bounding and new coordiinates


I have an image in Graphics2D that I need to rotate and then obtain the new co-ordinates of the image corners and the dimensions of the new bounding box.

enter image description here

I was originally trying to work with the image itself but I think it would be easier to work with a rectangle (or polygon) to give myself more flexibility. I was originally performing the rotation on the image simply with AffineTransform.rotate(). However, it would be cleaner if there was a way to translate each corner point individually, that would give me the values of A1, B1, C1 & D1. Is there a way in Graphics2D to rotate the individual corners?

I have found several questions relating to the bounding box dimensions of a rotated rectangle but I can't seem to get any of them to work in Java with Graphics2D.


Solution

  • You'll simply have to rotate the image corners yourself. The package java.awt.geom provides the classes Point2D and AffineTransform to do that by applying a rotation transform to individual points. The width and height of the rotated bounding box can be computed as the difference between the maximum and maximum rotated x and y coordinates, with the minimum x and y coordinate as offset.

    The following program implements this algorithm and displays the results for several rotations from 0° to 360° in 30° steps:

    package stackoverflow;
    
    import java.awt.geom.AffineTransform;
    import java.awt.geom.Point2D;
    import java.awt.geom.Rectangle2D;
    
    /**
     * Demonstration of an implementation to rotate rectangles.
     * @author Franz D.
     */
    public class ImageRotate
    {
        /**
         * Rotates a rectangle with offset (0,0).
         * @param originalWidth original rectangle width
         * @param originalHeight original rectangle height
         * @param angleRadians rotation angle in radians
         * @param rotatedCorners output buffer for the four rotated corners
         * @return the bounding box of the rotated rectangle
         * @throws NullPointerException if {@code rotatedCorners == null}.
         * @throws ArrayIndexOutOfBoundsException if {@code rotatedCorners.length < 4}.
         */
        public static Rectangle2D rotateRectangle(int originalWidth, int originalHeight,
                double angleRadians,
                Point2D[] rotatedCorners) {
            // create original corner points
            Point2D a0 = new Point2D.Double(0, 0);
            Point2D b0 = new Point2D.Double(originalWidth, 0);
            Point2D c0 = new Point2D.Double(0, originalHeight);
            Point2D d0 = new Point2D.Double(originalWidth, originalHeight);
            Point2D[] originalCorners = { a0, b0, c0, d0 };
    
            // create affine rotation transform
            AffineTransform transform = AffineTransform.getRotateInstance(angleRadians);
    
            // transform original corners to rotated corners
            transform.transform(originalCorners, 0, rotatedCorners, 0, originalCorners.length);
    
            // determine rotated width and height as difference between maximum and
            // minimum rotated coordinates
            double minRotatedX = Double.POSITIVE_INFINITY;
            double maxRotatedX = Double.NEGATIVE_INFINITY;
            double minRotatedY = Double.POSITIVE_INFINITY;
            double maxRotatedY = Double.NEGATIVE_INFINITY;
    
            for (Point2D rotatedCorner: rotatedCorners) {
                minRotatedX = Math.min(minRotatedX, rotatedCorner.getX());
                maxRotatedX = Math.max(maxRotatedX, rotatedCorner.getX());
                minRotatedY = Math.min(minRotatedY, rotatedCorner.getY());
                maxRotatedY = Math.max(maxRotatedY, rotatedCorner.getY());
            }
    
            // the bounding box is the rectangle with minimum rotated X and Y as offset
            double rotatedWidth = maxRotatedX - minRotatedX;
            double rotatedHeight = maxRotatedY - minRotatedY;
    
            Rectangle2D rotatedBounds = new Rectangle2D.Double(
                    minRotatedX, minRotatedY,
                    rotatedWidth, rotatedHeight);
    
            return rotatedBounds;
        }
    
        /**
         * Simple test for {@link #rotateRectangle(int, int, double, java.awt.geom.Point2D[])}.
         * @param args ignored
         */
        public static void main(String[] args) {
            // setup original width
            int originalWidth = 500;
            int originalHeight = 400;
    
            // create buffer for rotated corners
            Point2D[] rotatedCorners = new Point2D[4];
    
            // rotate rectangle from 0° to 360° in 30° steps
            for (int angleDegrees = 0; angleDegrees < 360; angleDegrees += 30) {
                // convert angle to radians
                double angleRadians = Math.toRadians(angleDegrees);
    
                // rotate rectangle
                Rectangle2D rotatedBounds = rotateRectangle(
                        originalWidth, originalHeight,
                        angleRadians,
                        rotatedCorners);
    
                // dump results
                System.out.println("--- Rotate " + originalWidth + "x" + originalHeight + " by " + angleDegrees + "° ---");
                System.out.println("Bounds: " + rotatedBounds);
                for (Point2D rotatedCorner: rotatedCorners) {
                    System.out.println("Corner " + rotatedCorner);
                }
            }
        }
    }
    

    If your image is not placed at offset (0, 0), you can simply modify the method to have the offset as input parameter, and adding the offset coordinates to the original points.

    Also, this method rotates the image (or rectangle) about the origin (0, 0). If you want other rotation centers, AffineTransform provides an overloaded variant of getRotateInstace() which allows you to specify the rotation center (called "anchor" in the API documentation).