Search code examples
cgeometryrotationrectanglescairo

How to calculate the sizes of a rectangle that contains rotated image (potentially with transparent pixels)


Given theta angles in radians, width and height of the rotated image, how do I calculate the new width and height of the outer rectangle that contains the rotated image?

In other words how do I calculate the new bonding box width/height? Note that the image could actually be circle and have transparent pixels on the edges.

enter image description here

That would be: x1, y1.

I am actually rotating a pixbuf with the origin at center using cairo_rotate() and I need to know the newly allocated area. What I tried is this:

double geo_rotated_rectangle_get_width (double a, double b, double theta)
{
    return abs(a*cos(theta)) + abs(b*sin(theta));
}

And it will work in the sense of always returning sufficient space to contain the rotated image, but it also always returns higher values than it should, when image is not rotated in a multiple of 90o and is a fully opaque image (a square).


EDIT: This is the image I am rotating: enter image description here

Interestingly enough, I just tried with a fully opaque image with the same size and it was OK. I use gdk_pixbuf_get_width() to get width and it returns the same value for both regardless. So I assume the formula is correct and the problem is that the transparency is not accounted for. When rotated with a diagonal orientation there are edges from the rectangle of the rotated image that are transparent.


I'll leave the above so that it is helpful to others :) Now the question becomes how to account for transparent pixels on the edges


Solution

  • To determine the bbox of the rotated rectangle, you can compute the coordinates of the 4 vertices and take the bbox of these 4 points.

    • a is the width of the unrotated rectangle and b its height ;

    • let diag = sqrt(a * a + b * b) / 2 the distance from the center to the top right corner of this rectangle. You can use diag = hypot(a, b) / 2 for better precision ;

    • first compute the angle theta0 of the first diagonal for theta=0: theta0 = atan(b / a) or better theta0 = atan2(b, a) ;

    • the 4 vertices are:

      • { diag * cos(theta0 + theta), diag * sin(theta0 + theta) }
      • { diag * cos(pi - theta0 + theta), diag * sin(pi - theta0 + theta) }
      • { diag * cos(pi + theta0 + theta), diag * sin(pi + theta0 + theta) }
      • { diag * cos(-theta0 + theta), diag * sin(-theta0 + theta) }
    • which can be simplified as:

      • { diag * cos(theta + theta0), diag * sin(theta + theta0) }
      • { -diag * cos(theta - theta0), -diag * sin(theta - theta0) }
      • { -diag * cos(theta + theta0), -diag * sin(theta + theta0) }
      • { diag * cos(theta - theta0), diag * sin(theta - theta0) }
    • which gives x1 and y1:

      • x1 = diag * fmax(fabs(cos(theta + theta0)), fabs(cos(theta - theta0))
      • y1 = diag * fmax(fabs(sin(theta + theta0)), fabs(sin(theta - theta0))
    • and the width and height of the rotated rectangle follow:

      • width = 2 * diag * fmax(fabs(cos(theta + theta0)), fabs(cos(theta - theta0))
      • height = 2 * diag * fmax(fabs(sin(theta + theta0)), fabs(sin(theta - theta0))

    This is the geometric solution, but you must take into account the rounding performed by the graphics primitive, so it is much preferable to use the graphics API and retrieve the pixbuf dimensions with gdk_pixbuf_get_width() and gdk_pixbuf_get_height(), which will allow for precise placement.