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

Viewed 210

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

2 Answers

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.

I'd say "let cairo compute those coordinates". If you have access to a cairo_t*, you can do something like the following (untested!):

double x1, y1, x2, y2;
cairo_save(cr);
cairo_rotate(cr, theta); // You can also do cairo_translate() and whatever your heart desires
cairo_new_path(cr);
cairo_rectangle(cr, x, y, width, height);
cairo_restore(cr); // Note: This preserved the path!
cairo_fill_extents(cr, &x1, &y1, &x2, &y2);
cairo_new_path(cr); // Clean up after ourselves
printf("Rectangle is inside of (%g,%g) to (%g,%g) (size %g,%g)\n",
    x1, y1, x2, y2, x2 - x1, y2 - y1);

The above code applies some transformation, then constructs a path. This makes cairo apply the transformation to the given coordinates. Afterwards, the transformation is "thrown away" with cairo_restore(). Next, we ask cairo for the area covered by the current path, which it provides in the current coordinate system, i.e. without the transformation.

Related