Calculate Bounding box coordinates from a rotated rectangle
Asked Answered
B

12

83

I have the coordinates of the top left point of a rectangle as well as its width, height and rotation from 0 to 180 and -0 to -180.

I am trying to get the bounding coordinates of the actual box around the rectangle.

What is a simple way of calculating the coordinates of the bounding box

  • Min y, max y, min x, max x?

The A point is not always on the min y bound, it can be anywhere.

I can use matrix the transform toolkit in as3 if needed.

Berar answered 7/3, 2009 at 17:3 Comment(3)
Picture (Image) is not visible .. (The image says : Click and discover Imageshack) !!!Jointress
Right my bad, I wonder if I can get it back from the google archive or something.Berar
Whats the A point?Moreover
M
92
  • Transform the coordinates of all four corners
  • Find the smallest of all four x's as min_x
  • Find the largest of all four x's and call it max_x
  • Ditto with the y's
  • Your bounding box is (min_x,min_y), (min_x,max_y), (max_x,max_y), (max_x,min_y)

AFAIK, there isn't any royal road that will get you there much faster.

If you are wondering how to transform the coordinates, try:

x2 = x0+(x-x0)*cos(theta)+(y-y0)*sin(theta)
y2 = y0-(x-x0)*sin(theta)+(y-y0)*cos(theta)

where (x0,y0) is the center around which you are rotating. You may need to tinker with this depending on your trig functions (do they expect degrees or radians) the sense / sign of your coordinate system vs. how you are specifying angles, etc.

Morocco answered 7/3, 2009 at 17:20 Comment(7)
Indeed, using matrices for all corners and comparing them did the job, thanks!Berar
Actually, due to symmetry, you need to transform only 2 corners, and if you give a little additional thought, it is just 1 corner to rotate.Aurignacian
@Aurignacian That is valid only in case where you rotate around the center of the rectangle.Scarcity
@Scarcity - this is true. However, this is how most of the drawing programs do it.Aurignacian
@Scarcity - yet, I think that even for the case where the rotation point is the corner, you should need only 2 (trigonometric) calculations, and not 4.Aurignacian
@Aurignacian isn't it more efficient to check all 4 corners instead of transposing them? also, i think not checking all of them only works if the rotation is a modulo of 90 degrees.Hindsight
@Morocco the equation you mention for x2 should be x0+(x-x0)*cos(theta)- (y-y0)*sin(theta) instead of x0+(x-x0)*cos(theta)+(y-y0)*sin(theta), am i right ?Andria
L
29

I realize that you're asking for ActionScript but, just in case anyone gets here looking for the iOS or OS-X answer, it is this:

+ (CGRect) boundingRectAfterRotatingRect: (CGRect) rect toAngle: (float) radians
{
    CGAffineTransform xfrm = CGAffineTransformMakeRotation(radians);
    CGRect result = CGRectApplyAffineTransform (rect, xfrm);

    return result;
}

If your OS offers to do all the hard work for you, let it! :)

Swift:

func boundingRectAfterRotatingRect(rect: CGRect, toAngle radians: CGFloat) -> CGRect {
    let xfrm = CGAffineTransformMakeRotation(radians)
    return CGRectApplyAffineTransform (rect, xfrm)
}
Lactescent answered 6/2, 2012 at 22:22 Comment(1)
Wanted to add that the angle has to be in radians, not degrees. May save you some time. ;)Slickenside
Z
13

The method outlined by MarkusQ works perfectly but bear in mind that you don't need to transform the other three corners if you have point A already.

An alternative method, which is more efficient, is to test which quadrant your rotation angle is in and then simply compute the answer directly. This is more efficient as you only have a worst case of two if statements (checking the angle) whereas the other approach has a worst case of twelve (6 for each component when checking the other three corners to see if they are greater than the current max or less than the current min) I think.

The basic algorithm, which uses nothing more than a series of applications of Pythagoras' theorem, is shown below. I have denoted the rotation angle by theta and expressed the check there in degrees as it's pseudo-code.

ct = cos( theta );
st = sin( theta );

hct = h * ct;
wct = w * ct;
hst = h * st;
wst = w * st;

if ( theta > 0 )
{
    if ( theta < 90 degrees )
    {
        // 0 < theta < 90
        y_min = A_y;
        y_max = A_y + hct + wst;
        x_min = A_x - hst;
        x_max = A_x + wct;
    }
    else
    {
        // 90 <= theta <= 180
        y_min = A_y + hct;
        y_max = A_y + wst;
        x_min = A_x - hst + wct;
        x_max = A_x;
    }
}
else
{
    if ( theta > -90 )
    {
        // -90 < theta <= 0
        y_min = A_y + wst;
        y_max = A_y + hct;
        x_min = A_x;
        x_max = A_x + wct - hst;
    }
    else
    {
        // -180 <= theta <= -90
        y_min = A_y + wst + hct;
        y_max = A_y;
        x_min = A_x + wct;
        x_max = A_x - hst;
    }
}

This approach assumes that you have what you say you have i.e. point A and a value for theta that lies in the range [-180, 180]. I've also assumed that theta increases in the clockwise direction as that's what the rectangle that has been rotated by 30 degrees in your diagram seems to indicate you are using, I wasn't sure what the part on the right was trying to denote. If this is the wrong way around then just swap the symmetric clauses and also the sign of the st terms.

Zosi answered 8/3, 2009 at 18:47 Comment(3)
I know this is very old, but it's a first hit for google so here i'll note: Just apply the scale to the h and w before this and it should work fine. This answer is currently my favorite for this problem since it breaks down the calculation into small chunks so well. 2 branches and with some NEON magic in ios, 4-6 operations or less depending how tricksy you get.Xi
People keep incorrectly editing out the "degrees" in the second if statement. This is pseudo-code so it will never compile. The purpose of explicitly stating "degrees" is because theta is clearly not in degrees. It's there as a reminder when implementing proper working code to get the angular units correct.Zosi
Ok, well then to avoid this confusion it would help if you changes all of the if statements so that they were if (theta > 0 degrees) and if (theta > -90 degrees) so that it is consistent. The inconsistency prompts people to edit it.Calaverite
R
7
    fitRect: function( rw,rh,radians ){
            var x1 = -rw/2,
                x2 = rw/2,
                x3 = rw/2,
                x4 = -rw/2,
                y1 = rh/2,
                y2 = rh/2,
                y3 = -rh/2,
                y4 = -rh/2;

            var x11 = x1 * Math.cos(radians) + y1 * Math.sin(radians),
                y11 = -x1 * Math.sin(radians) + y1 * Math.cos(radians),
                x21 = x2 * Math.cos(radians) + y2 * Math.sin(radians),
                y21 = -x2 * Math.sin(radians) + y2 * Math.cos(radians), 
                x31 = x3 * Math.cos(radians) + y3 * Math.sin(radians),
                y31 = -x3 * Math.sin(radians) + y3 * Math.cos(radians),
                x41 = x4 * Math.cos(radians) + y4 * Math.sin(radians),
                y41 = -x4 * Math.sin(radians) + y4 * Math.cos(radians);

            var x_min = Math.min(x11,x21,x31,x41),
                x_max = Math.max(x11,x21,x31,x41);

            var y_min = Math.min(y11,y21,y31,y41);
                y_max = Math.max(y11,y21,y31,y41);

            return [x_max-x_min,y_max-y_min];
        }
Roussillon answered 6/10, 2010 at 2:38 Comment(0)
D
3

if you are using GDI+ , you can create a new GrpaphicsPath -> Add any points or shapes to it -> Apply rotate transformation -> use GraphicsPath.GetBounds() and it will return a rectangle that bounds your rotated shape.

(edit) VB.Net Sample

Public Shared Sub RotateImage(ByRef img As Bitmap, degrees As Integer)
' https://mcmap.net/q/161766/-calculate-bounding-box-coordinates-from-a-rotated-rectangle#680877
'
Using gp As New GraphicsPath
  gp.AddRectangle(New Rectangle(0, 0, img.Width, img.Height))

  Dim translateMatrix As New Matrix
  translateMatrix.RotateAt(degrees, New PointF(img.Width \ 2, img.Height \ 2))
  gp.Transform(translateMatrix)

  Dim gpb = gp.GetBounds

  Dim newwidth = CInt(gpb.Width)
  Dim newheight = CInt(gpb.Height)

  ' http://www.codeproject.com/Articles/58815/C-Image-PictureBox-Rotations
  '
  Dim rotatedBmp As New Bitmap(newwidth, newheight)

  rotatedBmp.SetResolution(img.HorizontalResolution, img.VerticalResolution)

  Using g As Graphics = Graphics.FromImage(rotatedBmp)
    g.Clear(Color.White)
    translateMatrix = New Matrix
    translateMatrix.Translate(newwidth \ 2, newheight \ 2)
    translateMatrix.Rotate(degrees)
    translateMatrix.Translate(-img.Width \ 2, -img.Height \ 2)
    g.Transform = translateMatrix
    g.DrawImage(img, New PointF(0, 0))
  End Using
  img.Dispose()
  img = rotatedBmp
End Using

End Sub

Disease answered 25/3, 2009 at 9:41 Comment(0)
S
2

Although Code Guru stated the GetBounds() method, I've noticed the question is tagged as3, flex, so here is an as3 snippet that illustrates the idea.

var box:Shape = new Shape();
box.graphics.beginFill(0,.5);
box.graphics.drawRect(0,0,100,50);
box.graphics.endFill();
box.rotation = 20;
box.x = box.y = 100;
addChild(box);

var bounds:Rectangle = box.getBounds(this);

var boundingBox:Shape = new Shape();
boundingBox.graphics.lineStyle(1);
boundingBox.graphics.drawRect(bounds.x,bounds.y,bounds.width,bounds.height);
addChild(boundingBox);

I noticed that there two methods that seem to do the same thing: getBounds() and getRect()

Shechem answered 18/4, 2009 at 0:30 Comment(1)
getRect performs the same operation as getBounds, but minus the stroke on an object: help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/…Zoologist
S
2
/**
     * Applies the given transformation matrix to the rectangle and returns
     * a new bounding box to the transformed rectangle.
     */
    public static function getBoundsAfterTransformation(bounds:Rectangle, m:Matrix):Rectangle {
        if (m == null) return bounds;

        var topLeft:Point = m.transformPoint(bounds.topLeft);
        var topRight:Point = m.transformPoint(new Point(bounds.right, bounds.top));
        var bottomRight:Point = m.transformPoint(bounds.bottomRight);
        var bottomLeft:Point = m.transformPoint(new Point(bounds.left, bounds.bottom));

        var left:Number = Math.min(topLeft.x, topRight.x, bottomRight.x, bottomLeft.x);
        var top:Number = Math.min(topLeft.y, topRight.y, bottomRight.y, bottomLeft.y);
        var right:Number = Math.max(topLeft.x, topRight.x, bottomRight.x, bottomLeft.x);
        var bottom:Number = Math.max(topLeft.y, topRight.y, bottomRight.y, bottomLeft.y);
        return new Rectangle(left, top, right - left, bottom - top);
    }
Silverpoint answered 20/2, 2011 at 21:2 Comment(1)
In the problem, the user says he has rotation in degrees. Your solution requires having a transformation matrix. How do you go from a rotation in degrees to a rotation transformation matrix?Grumpy
R
1

Apply the rotation matrix to your corner points. Then use the minimum/maximum respectively of the obtained x,y coordinates to define your new bounding box.

Reichstag answered 7/3, 2009 at 17:19 Comment(0)
K
1

Here are three functions from my open source libraries. The functions are fully tested in Java but the formulae can be easily translated to any language.

The signatures are:

public static float getAngleFromPoint(final Point centerPoint, final Point touchPoint)

public static float getTwoFingerDistance(float firstTouchX, float firstTouchY, float secondTouchX, float secondTouchY)

Point getPointFromAngle(final double angle, final double radius)

This solution assumes that the pixel density is evenly spaced. Before rotating the object do the following:

  1. Use getAngleFromPoint to calculate the angle from the center to the upper right corner (lets say this returns 20 degrees) meaning that the upp left corner is -20 degrees or 340 degrees.

  2. Use the getTwoFingerDistance to return the diagonal distance between the center point and the upper right corner (this distance should obvoiusly be the same to all corners, This distance will be used in the next calculation).

  3. Now lets say we rotate the object clockwise by 30 degrees. We now know that the upper right corner must be at 50 degrees and the upper left corner is at 10 degrees.

  4. You should now be able to use the getPointFromAngle function on the upper left and upper right corner. using the radius returned from step 2. The X position multiplied by 2 from the upper right corner should give you the new width and the Y position times 2 from the upper left corner should give the the new height.

These above 4 steps should be put into conditions based upon how far you have rotated your object other wise you may return the height as the width and the width as the height.

Bare in mind the angle functions are expressed in factors of 0-1 instead of 0-360 (just multiply or divide by 360 where appropriate):

//Gets an angle from two points expressed as a factor of 0 -1 (0 being 0/360, 0.25 being 90 degrees etc)

public float getAngleFromPoint(final Point centerPoint, final Point touchPoint) {

    float returnVal = 0;

    //+0 - 0.5
    if(touchPoint.x > centerPoint.x) {

        returnVal = (float) (Math.atan2((touchPoint.x - centerPoint.x), (centerPoint.y - touchPoint.y)) * 0.5 / Math.PI);

    }
    //+0.5
    else if(touchPoint.x < centerPoint.x) {

        returnVal = (float) (1 - (Math.atan2((centerPoint.x - touchPoint.x), (centerPoint.y - touchPoint.y)) * 0.5 / Math.PI));

    }//End if(touchPoint.x > centerPoint.x)

    return returnVal;

}

//Measures the diagonal distance between two points

public float getTwoFingerDistance(final float firstTouchX, final float firstTouchY, final float secondTouchX, final float secondTouchY) {

    float pinchDistanceX = 0;
    float pinchDistanceY = 0;

    if(firstTouchX > secondTouchX) {

        pinchDistanceX = Math.abs(secondTouchX - firstTouchX);

    }
    else if(firstTouchX < secondTouchX) {

        pinchDistanceX = Math.abs(firstTouchX - secondTouchX);

    }//End if(firstTouchX > secondTouchX)

    if(firstTouchY > secondTouchY) {

        pinchDistanceY = Math.abs(secondTouchY - firstTouchY);

    }
    else if(firstTouchY < secondTouchY) {

        pinchDistanceY = Math.abs(firstTouchY - secondTouchY);

    }//End if(firstTouchY > secondTouchY)

    if(pinchDistanceX == 0 && pinchDistanceY == 0) {

        return 0;

    }
    else {

        pinchDistanceX = (pinchDistanceX * pinchDistanceX);
        pinchDistanceY = (pinchDistanceY * pinchDistanceY);
        return (float) Math.abs(Math.sqrt(pinchDistanceX + pinchDistanceY));

    }//End if(pinchDistanceX == 0 && pinchDistanceY == 0)

}

//Get XY coordinates from an angle given a radius (The angle is expressed in a factor of 0-1 0 being 0/360 degrees and 0.75 being 270 etc)

public Point getPointFromAngle(final double angle, final double radius) {

    final Point coords = new Point();
    coords.x = (int) (radius * Math.sin((angle) * 2 * Math.PI));
    coords.y = (int) -(radius * Math.cos((angle) * 2 * Math.PI));

    return coords;

}

These code snippets are from my open source libraries: https://bitbucket.org/warwick/hgdialrepo and https://bitbucket.org/warwick/hacergestov2. One is a gesture library for Android and the other is a dial control for Android. There is also an OpenGLES 2.0 implementation of the dial control at: https://bitbucket.org/warwick/hggldial

Koan answered 19/9, 2015 at 1:57 Comment(0)
D
1

When the rectangle is rotated around its center, the calculation is trivial:

function getBoundingBox(rX, rY, rW, rH, rA) {
  const absCosRA = Math.abs(Math.cos(rA));
  const absSinRA = Math.abs(Math.sin(rA));

  const bbW = rW * absCosRA + rH * absSinRA;
  const bbH = rW * absSinRA + rH * absCosRA;
  
  const bbX = rX - (bbW - rW) / 2;
  const bbY = rY - (bbH - rH) / 2;

  return { x: bbX, y: bbY, w: bbW, h: bbH };
}
Disremember answered 15/4, 2022 at 0:39 Comment(1)
Thanks for saving me the derivations. This is an elegant solution and should be the accepted answer. I'd expect very few programmers outside of game development to be familiar with transforms and rotation matrices, so I probably wouldn't call it trivial.Expressway
H
0

I am not sure I understand, but a compound transformation matrix will give you the new co-ordinates for all points concerned. If you think the rectangle may spill over the imagable area post transformation apply a clipping path.

In case you are unfamiliar with the exact definition of the matrices take a look here.

Hatchett answered 7/3, 2009 at 17:12 Comment(0)
L
0

I used Region for First rotating the rectangle and then use that rotated region to detect that rectangle

        r = new Rectangle(new Point(100, 200), new Size(200, 200));         
        Color BorderColor = Color.WhiteSmoke;
        Color FillColor = Color.FromArgb(66, 85, 67);
        int angle = 13;
        Point pt = new Point(r.X, r.Y);
        PointF rectPt = new PointF(r.Left + (r.Width / 2),
                               r.Top + (r.Height / 2));
       //declare myRegion globally 
        myRegion = new Region(r);

        // Create a transform matrix and set it to have a 13 degree

        // rotation.
        Matrix transformMatrix = new Matrix();
        transformMatrix.RotateAt(angle, pt);

        // Apply the transform to the region.
        myRegion.Transform(transformMatrix);
        g.FillRegion(Brushes.Green, myRegion);
        g.ResetTransform();

now to detecting that rectangle

        private void panel_MouseMove(object sender, MouseEventArgs e)
    {


        Point point = e.Location;
        if (myRegion.IsVisible(point, _graphics))
        {
            // The point is in the region. Use an opaque brush.
            this.Cursor = Cursors.Hand;
        }
        else {
            this.Cursor = Cursors.Cross;
        }

    }
Linguist answered 27/4, 2018 at 10:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.