14

I have done some searching and i can not find any function thats doing what i whant it todo.

I have a image file of a scanned document with text, but the document is some degrees rotated so i whant to rotate it so the text being inline with each other.

In a perfect world its should be a function doing this automaticly but i can not find anything and what i understand to get it to work automaticly its needed to be some analyze of the image and i think its to big thing todo.

But then i have done a tool to rotate the image on a website manually, but now i need a function to save the rotation to the image file.

This seems to be some differents methods for but no one i tested doing what i whant.

The function i have finded that works almost like i whant is:

public static Bitmap RotateImg(Bitmap bmp, float angle, Color bkColor) {
int w = bmp.Width;
int h = bmp.Height;
PixelFormat pf = default(PixelFormat);
if (bkColor == Color.Transparent)
{
    pf = PixelFormat.Format32bppArgb;
}
else
{
    pf = bmp.PixelFormat;
}

Bitmap tempImg = new Bitmap(w, h, pf);
Graphics g = Graphics.FromImage(tempImg);
g.Clear(bkColor);
g.DrawImageUnscaled(bmp, 1, 1);
g.Dispose();

GraphicsPath path = new GraphicsPath();
path.AddRectangle(new RectangleF(0f, 0f, w, h));
Matrix mtrx = new Matrix();
//Using System.Drawing.Drawing2D.Matrix class 
mtrx.Rotate(angle);
RectangleF rct = path.GetBounds(mtrx);
Bitmap newImg = new Bitmap(Convert.ToInt32(rct.Width), Convert.ToInt32(rct.Height), pf);
g = Graphics.FromImage(newImg);
g.Clear(bkColor);
g.TranslateTransform(-rct.X, -rct.Y);
g.RotateTransform(angle);
g.InterpolationMode = InterpolationMode.HighQualityBilinear;
g.DrawImageUnscaled(tempImg, 0, 0);
g.Dispose();
tempImg.Dispose();
return newImg; }

But this do not change the height and width of the image file so the image file is in the same size but the image "object" has been scaled and rotated.

Any idea how i can do this good?

Answer I find the solution that worked with my image that has a resolution on 300 at a old answer here.

Community
  • 1
  • 1
RickardP
  • 2,558
  • 7
  • 34
  • 42
  • 3
    Your question title is a bit misleading, what your really asking is how to calculate the new width/height of the image once it's been rotated, which is more of a [mathematical](http://math.stackexchange.com/) problem than a programming one. – James Jan 06 '13 at 17:40
  • possible duplicate of [LockBits image rotation method not working?](http://stackoverflow.com/questions/3860030/lockbits-image-rotation-method-not-working) – Hans Passant Jan 06 '13 at 18:10

2 Answers2

36

If I've understood your question correctly, you essentially want to work out the new size of an image once rotated, and how to position the rotated image in it's new bitmap.

enter image description here

The diagram hopefully helps make clear the solution. Here is a bit of pseudo code:

sinVal = abs(sin(angle))
cosVal = abs(cos(angle))
newImgWidth = sinVal * oldImgHeight + cosVal * oldImgWidth
newImgHeight = sinVal * oldImgWidth + cosVal * oldImgHeight
originX = 0
originY = sinVal * oldImgWidth

You want to make the new image from the newImgWidth and newImgHeight, and then perform a rotation around the origin (originX, originY) and then render the image to this point. This will fall over if the angle (in degrees) isn't between -90 and 0 degrees (depicted). If it is between 0 and 90 degrees, then you just change the origin:

originX = sinVal * oldImgHeight
originY = 0

If it is in the range 90 degrees to 270 degrees (-90 degrees) then it is a little tricker (see example code below).

Your code re-written (briefly tested) - it is slightly dodgy but seems to work:

public static Bitmap RotateImg(Bitmap bmp, float angle, Color bkColor)
{
    angle = angle % 360;
    if (angle > 180)
        angle -= 360;

    System.Drawing.Imaging.PixelFormat pf = default(System.Drawing.Imaging.PixelFormat);
    if (bkColor == Color.Transparent)
    {
        pf = System.Drawing.Imaging.PixelFormat.Format32bppArgb;
    }
    else
    {
        pf = bmp.PixelFormat;
    }

    float sin = (float)Math.Abs(Math.Sin(angle * Math.PI / 180.0)); // this function takes radians
    float cos = (float)Math.Abs(Math.Cos(angle * Math.PI / 180.0)); // this one too
    float newImgWidth = sin * bmp.Height + cos * bmp.Width;
    float newImgHeight = sin * bmp.Width + cos * bmp.Height;
    float originX = 0f;
    float originY = 0f;

    if (angle > 0)
    {
        if (angle <= 90)
            originX = sin * bmp.Height;
        else
        {
            originX = newImgWidth;
            originY = newImgHeight - sin * bmp.Width;
        }
    }
    else
    {
        if (angle >= -90)
        originY = sin * bmp.Width;
        else
        {
            originX = newImgWidth - sin * bmp.Height;
            originY = newImgHeight;
        }
    }

    Bitmap newImg = new Bitmap((int)newImgWidth, (int)newImgHeight, pf);
    Graphics g = Graphics.FromImage(newImg);
    g.Clear(bkColor);
    g.TranslateTransform(originX, originY); // offset the origin to our calculated values
    g.RotateTransform(angle); // set up rotate
    g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
    g.DrawImageUnscaled(bmp, 0, 0); // draw the image at 0, 0
    g.Dispose();

    return newImg;
}

Note the Degrees to Radians Conversion (180 Degrees == Pi Radians) for the trig functions

Edit: big issue was negative sin values, and me getting width/height confused when calculating origin x/y - this should work fine now (tested)

Edit: modified code to handle any angle

VisualMelon
  • 662
  • 12
  • 23
  • See my result of your function in my own answer. – RickardP Jan 06 '13 at 18:33
  • @RickardP could you try again, I made two stupid errors and now it works (I stupidly tested it will a near square image, which didn't help!) – VisualMelon Jan 06 '13 at 18:42
  • Same result with the latest code, can it be that i have wrong values on the angle? What do you espect? Example i getting from my jquery plugin that i should rotate it 3.5 degrees or -3.5 degrees so thats what i sending in as values, is that wrong? – RickardP Jan 06 '13 at 19:43
  • The new code will take any angle (degrees), and I've been watching it spinning (and working) on my computer. I'll make sure the effective code is the same in-case I've copied it out incorrectly. What exactly is the issue you are having with the code? – VisualMelon Jan 06 '13 at 19:46
  • I see its works with your logo and your application but when i put it in my own test application and my own file i dont get the same result as before, here is my test project: http://test.rickardp.se/stackoverflow/TestImageRotation.zip – RickardP Jan 06 '13 at 20:11
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/22279/discussion-between-visualmelon-and-rickardp) – VisualMelon Jan 06 '13 at 20:16
  • After some discussion it seems like it is my image files that are some wrong with, thanks VisualMelon for the help with the function. – RickardP Jan 06 '13 at 20:36
  • 1
    Okej, find the function almost the same as VisualMelon but some changes and it works for me with big resolutions, see http://stackoverflow.com/a/4320581/553770 – RickardP Jan 08 '13 at 12:37
1

This is strictly a comment to the nice answer by VisualMelon above, But I'm not allowed to add comments...

There are two tiny bugs in the code

a) The first check after the modulus should either be split into two, or changed to e.g.

if (180<Math.Abs(angle)) angle -= 360*Math.Sign(angle);

Otherwise angles between -360 and -180 will fail, as only +180 to +360 were handled

b) Just after the newImg assignment, a resolution assignment is missing, e.g.

newImg.SetResolution(bmp.HorizontalResolution, bmp.VerticalResolution);

If omitted the image will be scaled if the source is not 96 dpi.

....And splitting sticks, the intermediate calculations of dimensions and offsets ought to be kept in double, and only reduced to float last

Eske Rahn
  • 1,137
  • 12
  • 11
  • Thanks, these are good comments, the code in my answer isn't very good on any level, and as RickardP mentioned in a comment, this answer is a more functional version of the code: http://stackoverflow.com/a/4320581/553770. I might clean my answer up some day, I think the intent with the code was more to just demonstrate that the basic maths works rather than provide a good reference! – VisualMelon Jan 24 '16 at 16:22