如何在WinForms中旋转图片

52

我希望在我的应用程序中有一张图片,可以旋转以指示方向,比如风向或时间。我该使用什么代码来旋转图片?谢谢。

更新:我正在使用.NET 2.0,Windows 2000,VS C# 2005。


你是在使用WinForms还是WPF? - RandomEngy
不是WPF,我认为也不是WinForms。 - Arlen Beiler
3
如果你没有使用WPF(含有.xaml文件等)并且在Visual Studio中使用C#开发UI,那么你正在使用WinForms开发。WinForms是Windows Forms的缩写。 - RandomEngy
12个回答

47

这是一个你可以使用的在C#中旋转图片的方法:

/// <summary>
/// method to rotate an image either clockwise or counter-clockwise
/// </summary>
/// <param name="img">the image to be rotated</param>
/// <param name="rotationAngle">the angle (in degrees).
/// NOTE: 
/// Positive values will rotate clockwise
/// negative values will rotate counter-clockwise
/// </param>
/// <returns></returns>
public static Image RotateImage(Image img, float rotationAngle)
{
    //create an empty Bitmap image
    Bitmap bmp = new Bitmap(img.Width, img.Height);

    //turn the Bitmap into a Graphics object
    Graphics gfx = Graphics.FromImage(bmp);

    //now we set the rotation point to the center of our image
    gfx.TranslateTransform((float)bmp.Width / 2, (float)bmp.Height / 2);

    //now rotate the image
    gfx.RotateTransform(rotationAngle);

    gfx.TranslateTransform(-(float)bmp.Width / 2, -(float)bmp.Height / 2);

    //set the InterpolationMode to HighQualityBicubic so to ensure a high
    //quality image once it is transformed to the specified size
    gfx.InterpolationMode = InterpolationMode.HighQualityBicubic;

    //now draw our new image onto the graphics object
    gfx.DrawImage(img, new Point(0, 0));

    //dispose of our Graphics object
    gfx.Dispose();

    //return the image
    return bmp;
}

漂亮、紧凑的实现。 - TheBlastOne
8
如果您将图像旋转0度到90度之间的角度,例如45度,那么旋转后的图像可能比原始图像更大,因为它会超出边缘。因此,您需要为旋转后的图像腾出足够的空间。 - MrFox
2
@Orhan Cinar 在创建新的 bmp 下面添加以下行。 bmp.SetResolution(img.HorizontalResolution, img.VerticalResolution); 这行代码将把新图像的分辨率设置为原始图像的分辨率。 - Juan M. Elosegui
尝试了代码,不知何故我的图像变大了。它是一个完美圆圈内的箭头。箭头指向右侧,当我点击按钮时,将其旋转180度以指向左侧,但图像变大了。 - poudigne
不要忘记在你的项目中添加对 System.Drawing 的引用。 - Mehdi Souregi

22

这是一个旧的帖子,关于C# WinForms图片旋转有几个其他帖子,但现在我想到了自己的解决方案,我觉得这里是发布它的好地方。

  /// <summary>
  /// Method to rotate an Image object. The result can be one of three cases:
  /// - upsizeOk = true: output image will be larger than the input, and no clipping occurs 
  /// - upsizeOk = false & clipOk = true: output same size as input, clipping occurs
  /// - upsizeOk = false & clipOk = false: output same size as input, image reduced, no clipping
  /// 
  /// A background color must be specified, and this color will fill the edges that are not 
  /// occupied by the rotated image. If color = transparent the output image will be 32-bit, 
  /// otherwise the output image will be 24-bit.
  /// 
  /// Note that this method always returns a new Bitmap object, even if rotation is zero - in 
  /// which case the returned object is a clone of the input object. 
  /// </summary>
  /// <param name="inputImage">input Image object, is not modified</param>
  /// <param name="angleDegrees">angle of rotation, in degrees</param>
  /// <param name="upsizeOk">see comments above</param>
  /// <param name="clipOk">see comments above, not used if upsizeOk = true</param>
  /// <param name="backgroundColor">color to fill exposed parts of the background</param>
  /// <returns>new Bitmap object, may be larger than input image</returns>
  public static Bitmap RotateImage(Image inputImage, float angleDegrees, bool upsizeOk, 
                                   bool clipOk, Color backgroundColor)
  {
     // Test for zero rotation and return a clone of the input image
     if (angleDegrees == 0f)
        return (Bitmap)inputImage.Clone();

     // Set up old and new image dimensions, assuming upsizing not wanted and clipping OK
     int oldWidth = inputImage.Width;
     int oldHeight = inputImage.Height;
     int newWidth = oldWidth;
     int newHeight = oldHeight;
     float scaleFactor = 1f;

     // If upsizing wanted or clipping not OK calculate the size of the resulting bitmap
     if (upsizeOk || !clipOk)
     {
        double angleRadians = angleDegrees * Math.PI / 180d;

        double cos = Math.Abs(Math.Cos(angleRadians));
        double sin = Math.Abs(Math.Sin(angleRadians));
        newWidth = (int)Math.Round(oldWidth * cos + oldHeight * sin);
        newHeight = (int)Math.Round(oldWidth * sin + oldHeight * cos);
     }

     // If upsizing not wanted and clipping not OK need a scaling factor
     if (!upsizeOk && !clipOk)
     {
        scaleFactor = Math.Min((float)oldWidth / newWidth, (float)oldHeight / newHeight);
        newWidth = oldWidth;
        newHeight = oldHeight;
     }

     // Create the new bitmap object. If background color is transparent it must be 32-bit, 
     //  otherwise 24-bit is good enough.
     Bitmap newBitmap = new Bitmap(newWidth, newHeight, backgroundColor == Color.Transparent ? 
                                      PixelFormat.Format32bppArgb : PixelFormat.Format24bppRgb);
     newBitmap.SetResolution(inputImage.HorizontalResolution, inputImage.VerticalResolution);

     // Create the Graphics object that does the work
     using (Graphics graphicsObject = Graphics.FromImage(newBitmap))
     {
        graphicsObject.InterpolationMode = InterpolationMode.HighQualityBicubic;
        graphicsObject.PixelOffsetMode = PixelOffsetMode.HighQuality;
        graphicsObject.SmoothingMode = SmoothingMode.HighQuality;

        // Fill in the specified background color if necessary
        if (backgroundColor != Color.Transparent)
           graphicsObject.Clear(backgroundColor);

        // Set up the built-in transformation matrix to do the rotation and maybe scaling
        graphicsObject.TranslateTransform(newWidth / 2f, newHeight / 2f);

        if (scaleFactor != 1f)
           graphicsObject.ScaleTransform(scaleFactor, scaleFactor);

        graphicsObject.RotateTransform(angleDegrees);
        graphicsObject.TranslateTransform(-oldWidth / 2f, -oldHeight / 2f);

        // Draw the result 
        graphicsObject.DrawImage(inputImage, 0, 0);
     }

     return newBitmap;
  }

这是许多灵感来源的结果,包括在StackOverflow和其他地方。Naveen在这篇帖子上的答案尤其有帮助。


我正在寻找的东西,这就是我想要的,最好的答案! - immayankmodi
1
我也在寻找一个好的C#图像旋转解决方案,并尝试了这个:虽然它确实正确地旋转了图像,但返回的图像周围有很大的空白区域。我理解旋转后的图像不一定具有相同的尺寸,但为什么会有一个空白边框呢?参数不起作用:我将clipOk设置为false,upsizeOk设置为true,然后得到一个周围有很大空白区域的图像;如果我将upsizeOk设置为false,则图像具有原始大小,但仍然有一些边框,而原始图像没有...有什么想法吗? - ab-tools
@ab-tools: 你确定输入图像的可视部分周围没有额外的空间吗?这是一个可能的解释... - RenniePet
非常好的答案,但我在互联网上到处寻找仍然没有找到我需要的:即使使用upsizeOk=true,您的解决方案仍会使我的图像比原始图像小,而不旋转...我在互联网上找到的所有解决方案都会在旋转图像时使其变小或裁剪它...(我的图像没有空白区域)。您知道如何避免这个问题吗? - Calvin Nunes

20

简单方法:

public Image RotateImage(Image img)
{
    var bmp = new Bitmap(img);

    using (Graphics gfx = Graphics.FromImage(bmp))
    {
        gfx.Clear(Color.White);
        gfx.DrawImage(img, 0, 0, img.Width, img.Height);
    }

    bmp.RotateFlip(RotateFlipType.Rotate270FlipNone);
    return bmp;
}

3
为什么要创建Graphics对象并使用它来清除和重新绘制Bitmap对象呢?这样做是否是多余的?在可以使用Bitmap.RotateFlip()的情况下,代码似乎没有问题。 - RenniePet
非常好!如果你想旋转90度,你应该使用bmp.RotateFlip(RotateFlipType.Rotate90FlipNone); - Oleg
4
完全需要额外的努力。我们可以在不将其膨胀为“位图”,然后再转回“图像”的情况下完成此操作。 img.RotateFlip(RotateFlipType.Rotate270FlipNone); - psyklopz

6
我找到了这篇文章
  /// <summary>
    /// Creates a new Image containing the same image only rotated
    /// </summary>
    /// <param name=""image"">The <see cref=""System.Drawing.Image"/"> to rotate
    /// <param name=""offset"">The position to rotate from.
    /// <param name=""angle"">The amount to rotate the image, clockwise, in degrees
    /// <returns>A new <see cref=""System.Drawing.Bitmap"/"> of the same size rotated.</see>
    /// <exception cref=""System.ArgumentNullException"">Thrown if <see cref=""image"/"> 
    /// is null.</see>
    public static Bitmap RotateImage(Image image, PointF offset, float angle)
    {
        if (image == null)
            throw new ArgumentNullException("image");

        //create a new empty bitmap to hold rotated image
        Bitmap rotatedBmp = new Bitmap(image.Width, image.Height);
        rotatedBmp.SetResolution(image.HorizontalResolution, image.VerticalResolution);

        //make a graphics object from the empty bitmap
        Graphics g = Graphics.FromImage(rotatedBmp);

        //Put the rotation point in the center of the image
        g.TranslateTransform(offset.X, offset.Y);

        //rotate the image
        g.RotateTransform(angle);

        //move the image back
        g.TranslateTransform(-offset.X, -offset.Y);

        //draw passed in image onto graphics object
        g.DrawImage(image, new PointF(0, 0));

        return rotatedBmp;
    }

5
旧问题,但我必须回应MrFox在被接受的答案中的评论。当尺寸改变时旋转图像会切断图像的边缘。一种解决方法是在一个更大的图像上重新绘制原始图像,并居中显示,其中更大的图像的尺寸补偿了不剪辑边缘的需要。例如,我想设计一个游戏的瓷砖以正常角度,但在等距视图下以45度角重新绘制它们。

这里是示例图片(黄色边框使其更容易在此处看到)。

原始图像: 水瓦片

更大图像中的居中瓦片: 输入图像描述

旋转的图像(在其中旋转更大的图像,而不是原始图像): 输入图像描述

以下是代码(部分基于另一个问题中的答案):

private Bitmap RotateImage(Bitmap rotateMe, float angle)
{
    //First, re-center the image in a larger image that has a margin/frame
    //to compensate for the rotated image's increased size

    var bmp = new Bitmap(rotateMe.Width + (rotateMe.Width / 2), rotateMe.Height + (rotateMe.Height / 2));

    using (Graphics g = Graphics.FromImage(bmp))
        g.DrawImageUnscaled(rotateMe, (rotateMe.Width / 4), (rotateMe.Height / 4), bmp.Width, bmp.Height);

    bmp.Save("moved.png");
    rotateMe = bmp;

    //Now, actually rotate the image
    Bitmap rotatedImage = new Bitmap(rotateMe.Width, rotateMe.Height);

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

    rotatedImage.Save("rotated.png");
    return rotatedImage;
}

我的代码有一个问题,它存在内存泄漏,因为它创建了新的位图,但没有释放它们。所以当我多次转换同一张图片时,它会逐渐占用所有内存,并在没有内存可用时崩溃。 - Hatik
@Hatik - 只需为临时位图添加“dispose”(或using)即可。 - ephraim
我注意到旋转后的图像边缘有锯齿,有没有办法修复? - Rob

5

我写了一个简单的旋转图像类。你只需要输入图像和旋转角度(以度为单位)即可。角度必须在-90到+90之间。

public class ImageRotator
{
    private readonly Bitmap image;
    public Image OriginalImage
    {
        get { return image; }
    }


    private ImageRotator(Bitmap image)
    {
        this.image = image;
    }


    private double GetRadian(double degree)
    {
        return degree * Math.PI / (double)180;
    }


    private Size CalculateSize(double angle)
    {
        double radAngle = GetRadian(angle);
        int width = (int)(image.Width * Math.Cos(radAngle) + image.Height * Math.Sin(radAngle));
        int height = (int)(image.Height * Math.Cos(radAngle) + image.Width * Math.Sin(radAngle));
        return new Size(width, height);
    }

    private PointF GetTopCoordinate(double radAngle)
    {
        Bitmap image = CurrentlyViewedMappedImage.BitmapImage;
        double topX = 0;
        double topY = 0;

        if (radAngle > 0)
        {
            topX = image.Height * Math.Sin(radAngle);
        }
        if (radAngle < 0)
        {
            topY = image.Width * Math.Sin(-radAngle);
        }
        return new PointF((float)topX, (float)topY);
    }

    public Bitmap RotateImage(double angle)
    {
        SizeF size = CalculateSize(radAngle);
        Bitmap rotatedBmp = new Bitmap((int)size.Width, (int)size.Height);

        Graphics g = Graphics.FromImage(rotatedBmp);
        g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
        g.CompositingQuality = CompositingQuality.HighQuality;
        g.SmoothingMode = SmoothingMode.HighQuality;
        g.PixelOffsetMode = PixelOffsetMode.HighQuality;

        g.TranslateTransform(topPoint.X, topPoint.Y);
        g.RotateTransform(GetDegree(radAngle));
        g.DrawImage(image, new RectangleF(0, 0, size.Width, size.Height));

        g.Dispose();
        return rotatedBmp;
    }


    public static class Builder
    {
        public static ImageRotator CreateInstance(Image image)
        {
            ImageRotator rotator = new ImageRotator(image as Bitmap);
            return rotator;
        }
    }
}

2
这段代码无法编译 - 有两个未定义的变量和一个未定义的方法。 - RenniePet

2

旋转图像是一回事,正确的图像边界又是另一回事。这里有一段代码可以帮助任何人。我很久以前在互联网上搜索后创建了这个。

    /// <summary>
    /// Rotates image in radian angle
    /// </summary>
    /// <param name="bmpSrc"></param>
    /// <param name="theta">in radian</param>
    /// <param name="extendedBitmapBackground">Because of rotation returned bitmap can have different boundaries from original bitmap. This color is used for filling extra space in bitmap</param>
    /// <returns></returns>
    public static Bitmap RotateImage(Bitmap bmpSrc, double theta, Color? extendedBitmapBackground = null)
    {
        theta = Convert.ToSingle(theta * 180 / Math.PI);
        Matrix mRotate = new Matrix();
        mRotate.Translate(bmpSrc.Width / -2, bmpSrc.Height / -2, MatrixOrder.Append);
        mRotate.RotateAt((float)theta, new Point(0, 0), MatrixOrder.Append);
        using (GraphicsPath gp = new GraphicsPath())
        {  // transform image points by rotation matrix
            gp.AddPolygon(new Point[] { new Point(0, 0), new Point(bmpSrc.Width, 0), new Point(0, bmpSrc.Height) });
            gp.Transform(mRotate);
            PointF[] pts = gp.PathPoints;

            // create destination bitmap sized to contain rotated source image
            Rectangle bbox = BoundingBox(bmpSrc, mRotate);
            Bitmap bmpDest = new Bitmap(bbox.Width, bbox.Height);

            using (Graphics gDest = Graphics.FromImage(bmpDest))
            {
                if (extendedBitmapBackground != null)
                {
                    gDest.Clear(extendedBitmapBackground.Value);
                }
                // draw source into dest
                Matrix mDest = new Matrix();
                mDest.Translate(bmpDest.Width / 2, bmpDest.Height / 2, MatrixOrder.Append);
                gDest.Transform = mDest;
                gDest.DrawImage(bmpSrc, pts);
                return bmpDest;
            }
        }
    }


    private static Rectangle BoundingBox(Image img, Matrix matrix)
    {
        GraphicsUnit gu = new GraphicsUnit();
        Rectangle rImg = Rectangle.Round(img.GetBounds(ref gu));

        // Transform the four points of the image, to get the resized bounding box.
        Point topLeft = new Point(rImg.Left, rImg.Top);
        Point topRight = new Point(rImg.Right, rImg.Top);
        Point bottomRight = new Point(rImg.Right, rImg.Bottom);
        Point bottomLeft = new Point(rImg.Left, rImg.Bottom);
        Point[] points = new Point[] { topLeft, topRight, bottomRight, bottomLeft };
        GraphicsPath gp = new GraphicsPath(points, new byte[] { (byte)PathPointType.Start, (byte)PathPointType.Line, (byte)PathPointType.Line, (byte)PathPointType.Line });
        gp.Transform(matrix);
        return Rectangle.Round(gp.GetBounds());
    }

2

Richard Cox提供了一个很好的解决方案,链接为https://stackoverflow.com/a/5200280/1171321,我过去曾经使用过。值得注意的是,DPI必须为96才能正常工作。这个页面上的一些解决方案根本不起作用。


0

只要你想旋转的图片已经在“属性资源文件夹”中,这就可以正常工作。

在 Partial Class 中:

Bitmap bmp2;

OnLoad:

 bmp2 = new Bitmap(Tycoon.Properties.Resources.save2);
            pictureBox6.SizeMode = PictureBoxSizeMode.StretchImage;
            pictureBox6.Image = bmp2;

按钮或点击事件

private void pictureBox6_Click(object sender, EventArgs e)
        {
            if (bmp2 != null)
            {
                bmp2.RotateFlip(RotateFlipType.Rotate90FlipNone);
                pictureBox6.Image = bmp2;
            }
        }

0

这个解决方案假设您想要在一个图片框中绘制图像,并且图像的方向将跟随鼠标在该图片框上的移动。没有将任何图像分配给图片框。相反,我从项目资源中获取图像。

private float _angle;

public Form1()
{
    InitializeComponent();
}

private void PictureBox_MouseMove(object sender, MouseEventArgs e)
{
    (float centerX, float centerY) = GetCenter(pictureBox1.ClientRectangle);
    _angle = (float)(Math.Atan2(e.Y - centerY, e.X - centerX) * 180.0 / Math.PI);
    pictureBox1.Invalidate(); // Triggers redrawing
}

private void PictureBox_Paint(object sender, PaintEventArgs e)
{
    Bitmap image = Properties.Resources.ExampleImage;
    float scale = (float)pictureBox1.Width / image.Width;

    (float centerX, float centerY) = GetCenter(e.ClipRectangle);

    e.Graphics.TranslateTransform(centerX, centerY);
    e.Graphics.RotateTransform(_angle);
    e.Graphics.TranslateTransform(-centerX, -centerY);
    e.Graphics.ScaleTransform(scale, scale);
    e.Graphics.DrawImage(image, 0, 0);
}

// Uses C# 7.0 value tuples / .NET Framework 4.7.
// For previous versions, return a PointF instead.
private static (float, float) GetCenter(Rectangle rect)
{
    float centerX = (rect.Left + rect.Right) * 0.5f;
    float centerY = (rect.Top + rect.Bottom) * 0.5f;
    return (centerX, centerY);
}

在将此代码复制/粘贴到表单后,确保在图片框的属性窗口中选择鼠标事件处理程序PictureBox_MouseMovePictureBox_Paint

注意:您也可以使用简单的Panel或任何其他控件,如标签;但是,PictureBox默认具有使用双缓冲区的优势,可消除闪烁。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接