14

I have animated gif and I'm using a class to parse the images(frames) from it. The class is:

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Collections.Generic;
using System.IO;

public class AnimatedGif
{
    private List<AnimatedGifFrame> mImages = new List<AnimatedGifFrame>();
    public AnimatedGif(string path)
    {
        Image img = Image.FromFile(path);
        int frames = img.GetFrameCount(FrameDimension.Time);
        if (frames <= 1) throw new ArgumentException("Image not animated");
        byte[] times = img.GetPropertyItem(0x5100).Value;
        int frame = 0;
        for (; ; )
        {
            int dur = BitConverter.ToInt32(times, 4 * frame);
            mImages.Add(new AnimatedGifFrame(new Bitmap(img), dur));
            if (++frame >= frames) break;
            img.SelectActiveFrame(FrameDimension.Time, frame);
        }
        img.Dispose();
    }
    public List<AnimatedGifFrame> Images { get { return mImages; } }
}

public class AnimatedGifFrame
{
    private int mDuration;
    private Image mImage;
    internal AnimatedGifFrame(Image img, int duration)
    {
        mImage = img; mDuration = duration;
    }
    public Image Image { get { return mImage; } }
    public int Duration { get { return mDuration; } }
}

Now in form1 I loop over the frames in this case 4 and I want to rotate the animation by any degree. Now its rotating each 45 or 90 degrees. I want to add more frames(images) to the animation so if I set the rotation to 31 or to 10 degrees so I will see the animation rotating in 10 degrees.

This is the code in Form1 which is not working good. I'm using a function for the rotation which I didn't test yet if its any working.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace AnimatedGifEditor
{
    public partial class Form1 : Form
    {
        Image myImage;
        AnimatedGif myGif;
        Bitmap bitmap;

        public Form1()
        {
            InitializeComponent();

            myImage = Image.FromFile(@"D:\fananimation.gif");
            myGif = new AnimatedGif(@"D:\fananimation.gif");
            for (int i = 0; i < myGif.Images.Count; i++)
            {
                pictureBox1.Image = myGif.Images[3].Image;
                bitmap = new Bitmap(pictureBox1.Image);
                rotateImage(bitmap, 76);
                pictureBox1.Image = bitmap;
            }
        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }

private Bitmap RotateImg(Bitmap bmp, float angle, Color bkColor)
    {
        int w = bmp.Width;
        int h = bmp.Height;
        bmp.PixelFormat pf = default(bmp.PixelFormat);
        if (bkColor == Color.Transparent)
        {
            pf = bmp.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;
    }
    }
}

The animated gif I'm using for the test can be found here:

enter image description here

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
Daniel Lip
  • 3,867
  • 7
  • 58
  • 120
  • 1
    `"Im using a function for the rotation wich i didnt test yet if its any working."` Test and then ask if you have a problem. – L.B Aug 19 '12 at 07:30

7 Answers7

29

I didn't understand what's your problem but I think that your code could be improved. I think that you don't need to use directly the Matrix class. There are some functions that does this work for you. Infact the only things you need are: set the point of the rotation as the center, rotate the graphics and draw on it, using some functions by the Graphics class. So to rotate an image you can use this simple code:

private Bitmap RotateImage(Bitmap bmp, float angle) {
     Bitmap rotatedImage = new Bitmap(bmp.Width, bmp.Height);
     rotatedImage.SetResolution(bmp.HorizontalResolution, bmp.VerticalResolution);

     using (Graphics g = Graphics.FromImage(rotatedImage)) {
        // Set the rotation point to the center in the matrix
        g.TranslateTransform(bmp.Width / 2, bmp.Height / 2);
        // Rotate
        g.RotateTransform(angle);
        // Restore rotation point in the matrix
        g.TranslateTransform(- bmp.Width / 2, - bmp.Height / 2);
        // Draw the image on the bitmap
        g.DrawImage(bmp, new Point(0, 0));
     }

     return rotatedImage;
}
Community
  • 1
  • 1
Omar
  • 16,329
  • 10
  • 48
  • 66
  • To account for potentially clipping the edges of an image, it has to be re-centered first. Just wanted to note that I modified your solution for my needs and used it as an answer to another question here: http://stackoverflow.com/a/34895611/1137728 – sraboy Jan 20 '16 at 09:07
  • For some reason if I rotate my image over 180 degrees using this method, it stats looking hexagonal. Any idea why? – Alejandro Lozdziejski Aug 25 '17 at 09:08
  • This code does not work correctly. Afterwards the rotated image is significantly bigger than the original one. (approx 30% - 50%) Also it does not rotate in the center point of the original image. Your calculations are wrong. – Elmue Aug 24 '18 at 21:37
  • I found that using Image.RotateFlip kept the image from becoming pixelated, while Graphics.RotateTransform and Graphics.TranslateTransform resulted in a lower quality (fuzzy) result. However using Transforms with Text appeared to maintain sharpness. See answer from @Rajesh. – mdlars Jun 04 '19 at 23:13
  • It is corrupting the image quality after a while ;( – Görkem Hacıoğlu Dec 21 '22 at 20:09
9

Based on the previous answers I created this code that doesn't cut the image (the other examples were not working for me)

    private Bitmap RotateImage(Bitmap bmp, float angle)
    {
        float height = bmp.Height;
        float width = bmp.Width;
        int hypotenuse = System.Convert.ToInt32(System.Math.Floor(Math.Sqrt(height * height + width * width)));
        Bitmap rotatedImage = new Bitmap(hypotenuse, hypotenuse);
        using (Graphics g = Graphics.FromImage(rotatedImage))
        {
            g.TranslateTransform((float)rotatedImage.Width / 2, (float)rotatedImage.Height / 2); //set the rotation point as the center into the matrix
            g.RotateTransform(angle); //rotate
            g.TranslateTransform(-(float)rotatedImage.Width / 2, -(float)rotatedImage.Height / 2); //restore rotation point into the matrix
            g.DrawImage(bmp, (hypotenuse - width) / 2, (hypotenuse - height) / 2, width, height);
        }
        return rotatedImage;
    }
Queequeg
  • 131
  • 1
  • 3
  • 1
    While the code from Omar and from Timo does not work, this one gives the expected result. However there is still an issue. When the input image is not square you ALWAYS create a square output image. This does not make sense for rectangular input images which are rotated by a small angle. (e.g. 15 degree) Your calculation using the hypotenuse is wrong. To do it correctly the formula must take into account the rotation angle to calculate the with and height of the output image.. – Elmue Aug 24 '18 at 21:42
  • @Elmue, you are right, I could get the minimum size necessary just to not crop the image. Feel free to provide a better alternative – Queequeg Sep 10 '18 at 16:25
  • The answer from Timo works much better although it also crops a the image a little bit. – Elmue Sep 10 '18 at 21:37
7

I tried the answer of @Omar myself and realized, that the original image gets cut at the sides ... i've rewritten it so it resizes the image to the new sizes:

private static Bitmap RotateImage(Bitmap bmp, float angle)
{
    float alpha = angle;

    //edit: negative angle +360
    while(alpha <0) alpha +=360;

    float gamma = 90;
    float beta = 180 - angle - gamma;

    float c1 = bmp.Height;
    float a1 = (float)(c1 * Math.Sin(alpha * Math.PI / 180) / Math.Sin(gamma * Math.PI / 180));
    float b1 = (float)(c1 * Math.Sin(beta * Math.PI / 180) / Math.Sin(gamma * Math.PI / 180));

    float c2 = bmp.Width;
    float a2 = (float)(c2 * Math.Sin(alpha * Math.PI / 180) / Math.Sin(gamma * Math.PI / 180));
    float b2 = (float)(c2 * Math.Sin(beta * Math.PI / 180) / Math.Sin(gamma * Math.PI / 180));

    int width = Convert.ToInt32(b2 + a1);
    int height = Convert.ToInt32(b1 + a2);

    Bitmap rotatedImage = new Bitmap(width, height);
    using (Graphics g = Graphics.FromImage(rotatedImage))
    {
        g.TranslateTransform(rotatedImage.Width / 2, rotatedImage.Height / 2); //set the rotation point as the center into the matrix
        g.RotateTransform(angle); //rotate
        g.TranslateTransform(-rotatedImage.Width / 2, -rotatedImage.Height / 2); //restore rotation point into the matrix
        g.DrawImage(bmp, new Point((width - bmp.Width) / 2, (height - bmp.Height) / 2)); //draw the image on the new bitmap
    }
    return rotatedImage;
}

Changes of the Size at 30 Degree Radius

Omar
  • 16,329
  • 10
  • 48
  • 66
Timo Treichel
  • 358
  • 3
  • 14
6

Have you tried RotateFlip?

public partial class Form1 : Form
{
    Image myImage;
    AnimatedGif myGif;
    Bitmap bitmap;
    public Form1()
    {
        InitializeComponent();
        myImage = Image.FromFile(@"D:\fananimation.gif");
        bitmap = new Bitmap(myImage);
        bitmap.RotateFlip(System.Drawing.RotateFlipType.Rotate90FlipNone);
        this.pictureBox1.Image = bitmap;
    }

}

Source

Rajesh
  • 1,459
  • 10
  • 17
  • 2
    The RotateFlip() method works fine but there is only a few default flip types and this method could not work if you want to simulate that gif with a fluid motion. – Omar Aug 19 '12 at 11:20
2

I checked the answers and they all have at least one of the following problems:

  • Cropping/incorrect centering
  • Unnecessary margin
  • Errors with some angle ranges
  • Unnecessarily complicated calculations/code

This solution can handle any angle (positive, negative, over 360° etc.). There is no cropping or excessive margins. No memory leaks either.

public Bitmap RotateBitmap(Bitmap bmp, float angle)
{
    double radianAngle = angle / 180.0 * Math.PI;
    double cosA = Math.Abs(Math.Cos(radianAngle));
    double sinA = Math.Abs(Math.Sin(radianAngle));

    int newWidth = (int)(cosA * bmp.Width + sinA * bmp.Height);
    int newHeight = (int)(cosA * bmp.Height + sinA * bmp.Width);

    var rotatedBitmap = new Bitmap(newWidth, newHeight);
    rotatedBitmap.SetResolution(bmp.HorizontalResolution, bmp.VerticalResolution);
    
    using (Graphics g = Graphics.FromImage(rotatedBitmap))
    {
        g.TranslateTransform(rotatedBitmap.Width / 2, rotatedBitmap.Height / 2);
        g.RotateTransform(angle);
        g.TranslateTransform(-bmp.Width / 2, -bmp.Height / 2);
        g.DrawImage(bmp, new Point(0, 0));
    }

    bmp.Dispose();//Remove if you want to keep oryginal bitmap

    return rotatedBitmap;
}
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jan 09 '22 at 03:20
  • 1
    I would use `Mail.Ceiling(...)` on the new width/height. E.g: `int newWidth = (int)Math.Ceiling(cosA * bmp.Width + sinA * bmp.Height));` Otherwise pixels on the corners will be cut-off. – Loathing Apr 10 '23 at 04:01
1

I was using this function in VB:

    Public Function RotateImage(ByRef image As Image, ByVal angle As Single) As Drawing.Bitmap
    If image Is Nothing Then
        Throw New ArgumentNullException("image")
    End If

    Dim pi2 As Single = Math.PI / 2.0
    Dim oldWidth As Single = image.Width
    Dim oldHeight As Single = image.Height

    Dim theta As Single = angle * Math.PI / 180.0
    Dim locked_theta As Single = theta

    If locked_theta < 0.0 Then locked_theta += 2 * Math.PI

    Dim newWidth, newHeight As Single
    Dim nWidth, nHeight As Integer

    Dim adjacentTop, oppositeTop As Single
    Dim adjacentBottom, oppositeBottom As Single

    If (locked_theta >= 0.0 And locked_theta < pi2) Or _
    (locked_theta >= Math.PI And locked_theta < (Math.PI + pi2)) Then
        adjacentTop = Math.Abs(Math.Cos(locked_theta)) * oldWidth
        oppositeTop = Math.Abs(Math.Sin(locked_theta)) * oldWidth

        adjacentBottom = Math.Abs(Math.Cos(locked_theta)) * oldHeight
        oppositeBottom = Math.Abs(Math.Sin(locked_theta)) * oldHeight
    Else
        adjacentTop = Math.Abs(Math.Sin(locked_theta)) * oldHeight
        oppositeTop = Math.Abs(Math.Cos(locked_theta)) * oldHeight

        adjacentBottom = Math.Abs(Math.Sin(locked_theta)) * oldWidth
        oppositeBottom = Math.Abs(Math.Cos(locked_theta)) * oldWidth
    End If



    newWidth = adjacentTop + oppositeBottom
    newHeight = adjacentBottom + oppositeTop

    nWidth = Int(Math.Ceiling(newWidth))
    nHeight = Int(Math.Ceiling(newHeight))

    Dim rotatedBmp As New Drawing.Bitmap(nWidth, nHeight)

    Dim g As Graphics = Graphics.FromImage(rotatedBmp)

    Dim points(2) As Point

    If (locked_theta >= 0.0 And locked_theta < pi2) Then

        points(0) = New Point(Int(oppositeBottom), 0)
        points(1) = New Point(nWidth, Int(oppositeTop))
        points(2) = New Point(0, Int(adjacentBottom))

    ElseIf locked_theta >= pi2 And locked_theta < Math.PI Then

        points(0) = New Point(nWidth, Int(oppositeTop))
        points(1) = New Point(Int(adjacentTop), nHeight)
        points(2) = New Point(Int(oppositeBottom), 0)

    ElseIf locked_theta >= Math.PI And locked_theta < (Math.PI + pi2) Then

        points(0) = New Point(Int(adjacentTop), nHeight)
        points(1) = New Point(0, Int(adjacentBottom))
        points(2) = New Point(nWidth, Int(oppositeTop))

    Else

        points(0) = New Point(0, Int(adjacentBottom))
        points(1) = New Point(Int(oppositeBottom), 0)
        points(2) = New Point(Int(adjacentTop), nHeight)
    End If

    g.DrawImage(image, points)

    g.Dispose()
    image.Dispose()

    Return rotatedBmp

End Function
gunakkoc
  • 1,069
  • 11
  • 30
  • This algorithm works very well, although disposing the passed image at the end is wrong (because it may be used further in the calling code). Best thing however is, that this code is very fast. I compared it with other solutions and with SkiaSharp with a 2 MB PNG image. The results were: This solution: 44ms in average, SkiaSharp: 79ms in average, Other solution 1: 146ms in average, Other solution 2: 172ms in average – Jürgen Bayer Dec 11 '19 at 09:33
0

Depending on Timo's code, i made some improvements, with improvement, negative angles (up to -360) can be give as a parameter succesfully

private static Bitmap RotateImage(Bitmap bmp, float angle) 
    {
        float alpha = angle;

        //edit: negative angle +360
        while (alpha < 0) alpha += 360;

        float gamma = 90;
        float beta = 180 - angle - gamma;

        float c1 = bmp.Height;
        float a1 = Math.Abs((float)(c1 * Math.Sin(alpha * Math.PI / 180)));
        float b1 = Math.Abs((float)(c1 * Math.Sin(beta * Math.PI / 180)));

        float c2 = bmp.Width;
        float a2 = Math.Abs((float)(c2 * Math.Sin(alpha * Math.PI / 180)));
        float b2 = Math.Abs((float)(c2 * Math.Sin(beta * Math.PI / 180)));

        int width = Convert.ToInt32(b2 + a1);
        int height = Convert.ToInt32(b1 + a2);

        Bitmap rotatedImage = new Bitmap(width, height);
        using (Graphics g = Graphics.FromImage(rotatedImage))
        {
            g.TranslateTransform(rotatedImage.Width / 2, rotatedImage.Height / 2); //set the rotation point as the center into the matrix
            g.RotateTransform(angle); //rotate
            g.TranslateTransform(-rotatedImage.Width / 2, -rotatedImage.Height / 2); //restore rotation point into the matrix
            g.DrawImage(bmp, new Point((width - bmp.Width) / 2, (height - bmp.Height) / 2)); //draw the image on the new bitmap
        }
        return rotatedImage;
    }