Search code examples
c#winformsgraphicsbitmapimage-rotation

Image.RotateFlip doesn't seem to rotate the Bitmap


I have three PictureBox with the images of a Gear (picture in post).
When I hover on them, they rotate. I am using System.Drawing.Image.RotateFlip(RotateFlipType).
It looks like only the center of the gear rotates but the edges are static.

Image of a Gear

private void rotationTimer_Tick(object sender, EventArgs e)
{
    Image flipImage = pictureBox1.Image;
    flipImage.RotateFlip(RotateFlipType.Rotate90FlipXY);
    pictureBox1.Image = flipImage;
}

private void rotationTimer2_Tick(object sender, EventArgs e)
{
    Image flipImage = pictureBox2.Image;
    flipImage.RotateFlip(RotateFlipType.Rotate90FlipNone);
    pictureBox2.Image = flipImage;
}

private void rotationTimer3_Tick(object sender, EventArgs e)
{
    Image flipImage = pictureBox3.Image;
    flipImage.RotateFlip(RotateFlipType.Rotate270FlipXY);
    pictureBox3.Image = flipImage;
}

private void pictureBox1_MouseHover(object sender, EventArgs e)
{
    rotationTimer.Start();
    rotationTimer2.Start();
    rotationTimer3.Start();
} //etc...

Solution

  • Here's an example of a rotating image using the Matrix.RotateAt() method.
    This is a pretty simple process:

    • Create a Bitmap object from an Image file (or a Project resource); note that the Bitmap is [Cloned][2]: this way, we are detaching it from the FileStream (GDI+ won't lock the file while in use). Remember to Dispose() of it when you're done with it (or the application closes)
    • define a rotation angle that fits the image shape
    • set a Timer interval that generates the rotation speed (combined with the rotation angle). We are using a System.Windows.Form.Timer of course: we want it to tick in the UI thread (note that this object needs to be Disposed, too)
    • when the Timer ticks, Invalidate() the canvas (a PictureBox control, here)
    • use Matrix.RotateAt(gearCurrentRotationAngle, [ImageCentre]) and apply the Matrix to the Graphics geometric world transformation using its Transform property
    • add the chosen rotation angle to the current rotation angle at each rotation. When it reaches 360 degrees, re-set to min value (the gearRotationAngle field value, here)

    Some other examples here:
    Transparent Overlapping Circular Progress Bars
    GraphicsPath and Matrix classes

    Rotation Matrix


    using System.Drawing;
    using System.Drawing.Drawing2D;
    using System.Windows.Forms;
    
    Bitmap gear = null;
    RectangleF gearRect = Rectangle.Empty;
    int gearRotateSpeed = 100;
    int gearRotationAngle = 24;
    int gearCurrentRotationAngle = 0;
    
    System.Windows.Forms.Timer gearTimer = new System.Windows.Forms.Timer();
    
    public Form1() {
        InitializeComponent();
        gear = Image.FromStream(new MemoryStream(File.ReadAllBytes(@"File Path")));
    
        // Assuming the Gear Image is square shaped and the PictureBox size remains constant
        // otherwise, recalculate in the Control.Resize event
        var gearScale = Math.Min(pictureBox1.Width, pictureBox1.Height) / (float)Gear.Width;
        var gearSize = new SizeF(gear.Width * gearScale, gear.Height * gearScale);
        gearRect = new RectangleF(new PointF((pictureBox1.Width - gearSize.Width) / 2.0f, (pictureBox1.Height - gearSize.Height) / 2.0f), gearSize);
    
        gearTimer.Tick += (s, e) => {
            gearCurrentRotationAngle += gearRotationAngle;
            if (gearCurrentRotationAngle > 360) gearCurrentRotationAngle = gearRotationAngle;
            pictureBox1.Invalidate();
        }
    }
    
    private void pictureBox1_MouseEnter(object sender, EventArgs e) {
        gearTimer.Interval = gearRotateSpeed;
        gearTimer.Start();
    }
    
    private void pictureBox1_MouseLeave(object sender, EventArgs e) => gearTimer.Stop();
    
    private void pictureBox1_Paint(object sender, PaintEventArgs e) {
        var canvas = sender as PictureBox;
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
        e.Graphics.PixelOffsetMode = PixelOffsetMode.Half;
    
        PointF centerImage = new PointF(canvas.Width / 2, canvas.Height / 2);
        using (var mx = new Matrix()) {
            mx.RotateAt(gearCurrentRotationAngle, centerImage);
            e.Graphics.Transform = mx;
            e.Graphics.DrawImage(gear, gearRect);
        }
    }