【问题标题】:How can I rotate an image by any degree?如何将图像旋转任意角度?
【发布时间】:2022-01-09 08:41:52
【问题描述】:

我有动画 gif,我正在使用一个类来解析其中的图像(帧)。 班级是:

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; } }
}

现在在 form1 中,我在这种情况下循环帧 4,我想将动画旋转任意角度。现在它每旋转 45 或 90 度。我想为动画添加更多帧(图像),所以如果我将旋转设置为 31 或 10 度,我会看到动画旋转 10 度。

这是 Form1 中的代码,它不能正常工作。我正在使用一个我尚未测试的旋转函数是否有效。

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;
    }
    }
}

我用于测试的动画 gif 可以在这里找到:

【问题讨论】:

  • "Im using a function for the rotation wich i didnt test yet if its any working." 测试然后询问是否有问题。

标签: c#


【解决方案1】:

我不明白您的问题是什么,但我认为您的代码可以改进。我认为您不需要直接使用Matrix 类。有一些功能可以为您工作。事实上,您唯一需要的是:将旋转点设置为中心,旋转图形并在其上绘制,使用 Graphics 类的一些函数。 因此,要旋转图像,您可以使用以下简单代码:

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;
}

【讨论】:

  • 为了考虑到可能剪裁图像的边缘,它必须首先重新居中。只是想指出,我根据自己的需要修改了您的解决方案,并将其用作此处另一个问题的答案:stackoverflow.com/a/34895611/1137728
  • 由于某种原因,如果我使用这种方法将图像旋转 180 度以上,它的统计数据看起来是六边形的。知道为什么吗?
  • 此代码无法正常工作。之后旋转的图像比原始图像大得多。 (大约 30% - 50%)它也不会在原始图像的中心点旋转。你的计算是错误的。
  • 我发现使用 Image.RotateFlip 可以防止图像像素化,而 Graphics.RotateTransform 和 Graphics.TranslateTransform 会导致质量较低(模糊)的结果。然而,使用带有文本的变换似乎可以保持清晰度。请参阅@Rajesh 的回答。
【解决方案2】:

根据之前的答案,我创建了这个不会剪切图像的代码(其他示例对我不起作用)

    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;
    }

【讨论】:

  • 虽然来自 Omar 和 Timo 的代码不起作用,但这个给出了预期的结果。但是,仍然存在一个问题。当输入图像不是正方形时,您总是创建一个正方形输出图像。这对于旋转小角度的矩形输入图像没有意义。 (例如 15 度)您使用斜边的计算是错误的。要正确执行此操作,公式必须考虑旋转角度来计算输出图像的宽度和高度。
  • @Elmue,你是对的,我可以获得不裁剪图像所需的最小尺寸。随意提供更好的选择
  • Timo 的回答效果要好得多,尽管它也会稍微裁剪图像。
【解决方案3】:

我自己尝试了@Omar 的答案并意识到,原始图像在侧面被切割......我已经重写它,因此它将图像调整为新尺寸:

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;
}

【讨论】:

  • 这部分有效,当我设置负角度时它不起作用,我需要负旋转...
  • @Elmue 我自己用过......你一定犯了一个错误,因为两个代码都在工作。它的简单数学。
  • 看来我在错误的答案下发布了这个。然而,两个角被裁剪。因此,需要对您的计算进行一些修改。
  • 由于他的作物,我额外对 Omars 代码进行了修改......这个计算了新的大小并设置了它
  • 然后添加 360 度直到其为正:D
【解决方案4】:

你试过旋转翻转吗?

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

【讨论】:

  • RotateFlip() 方法工作正常,但只有几个默认翻转类型,如果您想用流体运动模拟该 gif,此方法无法工作。
【解决方案5】:

我在 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

【讨论】:

  • 这个算法效果很好,虽然在最后处理传递的图像是错误的(因为它可能会在调用代码中进一步使用)。然而,最好的事情是,这段代码非常快。我将它与其他解决方案以及带有 2 MB PNG 图像的 SkiaSharp 进行了比较。结果是:此解决方案:平均 44 毫秒,SkiaSharp:平均 79 毫秒,其他解决方案 1:平均 146 毫秒,其他解决方案 2:平均 172 毫秒
【解决方案6】:

根据 Timo 的代码,我做了一些改进,通过改进,可以成功地将负角(最大 -360)作为参数给出

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;
    }

【讨论】:

    【解决方案7】:

    我检查了答案,他们都至少存在以下问题之一:

    • 裁剪/居中不正确
    • 不必要的边距
    • 某些角度范围的错误
    • 不必要的复杂计算/代码

    此解决方案可以处理任何角度(正、负、超过 360° 等)。没有裁剪或过多的边距。也没有内存泄漏。

    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;
    }
    

    【讨论】:

    • 您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center
    猜你喜欢
    • 1970-01-01
    • 2016-02-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-20
    • 1970-01-01
    • 2021-04-12
    • 2012-07-02
    相关资源
    最近更新 更多