图像裁剪和调整大小:ImageMagick与C#中的GDI+比较

3
我有一个Web应用程序,用户可以上传图片创建他们的画廊。多年前,当我编写这个应用程序时,我选择了ImageMagick,并使用ImageMagick进行了所有的裁剪和调整大小操作。
现在我正在从头开始重写应用程序,我用本地GDI+操作替换了ImageMagick,但是我越学习关于GDI+的知识,就越担心自己做出了错误的选择。
无论我阅读哪里,都说GDI+是为桌面设计的,不应该在服务器应用程序上使用。我不知道细节,但我猜测这是因为内存消耗的原因,而且我确实可以看到GDI+使用更多的内存来进行相同的操作(裁剪和调整大小)在相同的图像上比ImageMagick(虽然真相是GDI+更快)。

我认为GDI+、ImageMagick或其他库对于这些基本操作应该是差不多的,我喜欢使用原生的GDI+,相信微软在.NET中提供的东西至少还可以。

什么是正确的方法/工具?

这是我用来裁剪的代码:

internal Image Crop(Image image, Rectangle r)
{
    Bitmap bmpCrop;
    using (Bitmap bmpImage = new Bitmap(image))
    {
        bmpCrop = bmpImage.Clone(r, bmpImage.PixelFormat);
        bmpImage.Dispose();
    }
    return (Image)(bmpCrop);
}

这是我用来调整大小的代码:

internal Image ResizeTo(Image sourceImage, int width, int height)
{
    System.Drawing.Image newImage = new Bitmap(width, height);
    using (Graphics gr = Graphics.FromImage(newImage))
    {
        gr.SmoothingMode = SmoothingMode.AntiAlias;
        gr.InterpolationMode = InterpolationMode.HighQualityBicubic;
        gr.PixelOffsetMode = PixelOffsetMode.HighQuality;
        gr.DrawImage(sourceImage, new Rectangle(0, 0, width, height));
        gr.Dispose();
    }
    return newImage;
}

我在这里猜测,但GDI+的某些部分可能依赖于包含有效桌面的环境,例如已登录用户,而不是NT服务。 - Chris O
当使用Imagemagick时,如果您保存为jpg格式,可以使用jpg提示来加快处理速度,或尝试使用Imagick。GDI+只能在Windows服务器上使用吗? - Bonzo
猜想是这样,但我无论如何都在使用IIS7和.Net。 - Max Favilli
1
我几年前在服务器应用程序中使用了GDI+来完成这个任务,它仍在运行。它承受着轻至中等的负载。我也对在服务器上使用GDI持谨慎态度,但我完全没有遇到任何问题。 - vlad259
2个回答

2
你能否提供一些人们认为不应该在服务器上使用GDI +的链接?也许他们知道我不知道的事情。
我了解一些关于GDI +工作原理的东西,但对ImageMagick一无所知。我确实看到了这个描述ImageMagick架构的页面:http://www.imagemagick.org/script/architecture.php 它似乎会将图像内部转换为具有4个通道和特定位深度(通常每通道16位)的未压缩格式,并使用未压缩数据进行处理,这些数据可能在内存或磁盘上,具体取决于大小。'identify-version'将告诉您的位深度。我的印象是,实际上,除非您使用Q8版本,否则ImageMagick通常会使用64位RGBA缓冲区,在内部工作,它将使用32位RGBA。它还可以使用多个线程,但我认为这并不重要,除非您正在处理非常大的图像。(如果您正在处理非常大的图像,则ImageMagick是明显的赢家。)
GDI +位图对象将始终在内存中存储未压缩数据,并且通常默认为32位RGBA。那个和32位RGB可能是最有效的格式。 GDI +是一个绘图库,它不是为大型图像而设计的,但至少Bitmap对象不会持有除像素数据和图像元数据之外的任何资源(与流行观点相反,它们不包含HBITMAP对象)。
所以它们对我来说看起来非常相似。对于您的用例,我无法明确说出哪一个更好。如果您选择imagemagick,则应该使用Q8版本以获得速度和内存增益,除非精度更重要。
如果您的操作仅涉及加载、保存、缩放和裁剪,那么您应该能够轻松地在以后替换实现。
除非您需要处理metafiles,否则您应该在内部使用Bitmap对象而不是Images。然后,您就不必在Crop函数中创建中间Bitmap对象。那个中间对象可能是您观察到的额外内存消耗的原因之一。如果您从外部源获取Image对象,建议尝试将其强制转换为Bitmap,并在不起作用时创建新的Bitmap。
此外,“using”语句自动调用Dispose,因此无需显式调用它。

1
System.Drawing命名空间中的类不支持在Windows或ASP.NET服务中使用。尝试从其中一个应用程序类型中使用这些类可能会产生意外问题,例如降低服务性能和运行时异常。 - Henrik Stenbæk

0

我自己写了一些东西:

public void ResizeImageAndRatio(string origFileLocation, string newFileLocation, string origFileName, string newFileName, int newWidth, int newHeight, bool resizeIfWider)
{
    System.Drawing.Image initImage = System.Drawing.Image.FromFile(origFileLocation + origFileName);
    int templateWidth = newWidth;
    int templateHeight = newHeight;
    double templateRate = double.Parse(templateWidth.ToString()) / templateHeight;
    double initRate = double.Parse(initImage.Width.ToString()) / initImage.Height;
    if (templateRate == initRate)
    {
        System.Drawing.Image templateImage = new System.Drawing.Bitmap(templateWidth, templateHeight);
        System.Drawing.Graphics templateG = System.Drawing.Graphics.FromImage(templateImage);
        templateG.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
        templateG.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
        templateG.Clear(Color.White);
        templateG.DrawImage(initImage, new System.Drawing.Rectangle(0, 0, templateWidth, templateHeight), new System.Drawing.Rectangle(0, 0, initImage.Width, initImage.Height), System.Drawing.GraphicsUnit.Pixel);
        templateImage.Save(newFileLocation + newFileName, System.Drawing.Imaging.ImageFormat.Jpeg);
    }
    else
    {
        System.Drawing.Image pickedImage = null;
        System.Drawing.Graphics pickedG = null;

        Rectangle fromR = new Rectangle(0, 0, 0, 0);
        Rectangle toR = new Rectangle(0, 0, 0, 0);

        if (templateRate > initRate)
        {
            pickedImage = new System.Drawing.Bitmap(initImage.Width, int.Parse(Math.Floor(initImage.Width / templateRate).ToString()));
            pickedG = System.Drawing.Graphics.FromImage(pickedImage);

            fromR.X = 0;
            fromR.Y = int.Parse(Math.Floor((initImage.Height - initImage.Width / templateRate) / 2).ToString());
            fromR.Width = initImage.Width;
            fromR.Height = int.Parse(Math.Floor(initImage.Width / templateRate).ToString());

            toR.X = 0;
            toR.Y = 0;
            toR.Width = initImage.Width;
            toR.Height = int.Parse(Math.Floor(initImage.Width / templateRate).ToString());
        }
        else
        {
            pickedImage = new System.Drawing.Bitmap(int.Parse(Math.Floor(initImage.Height * templateRate).ToString()), initImage.Height);
            pickedG = System.Drawing.Graphics.FromImage(pickedImage);

            fromR.X = int.Parse(Math.Floor((initImage.Width - initImage.Height * templateRate) / 2).ToString());
            fromR.Y = 0;
            fromR.Width = int.Parse(Math.Floor(initImage.Height * templateRate).ToString());
            fromR.Height = initImage.Height;

            toR.X = 0;
            toR.Y = 0;
            toR.Width = int.Parse(Math.Floor(initImage.Height * templateRate).ToString());
            toR.Height = initImage.Height;
        }

        pickedG.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
        pickedG.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;

        pickedG.DrawImage(initImage, toR, fromR, System.Drawing.GraphicsUnit.Pixel);

        System.Drawing.Image templateImage = new System.Drawing.Bitmap(templateWidth, templateHeight);
        System.Drawing.Graphics templateG = System.Drawing.Graphics.FromImage(templateImage);
        templateG.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
        templateG.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
        templateG.Clear(Color.White);
        templateG.DrawImage(pickedImage, new System.Drawing.Rectangle(0, 0, templateWidth, templateHeight), new System.Drawing.Rectangle(0, 0, pickedImage.Width, pickedImage.Height), System.Drawing.GraphicsUnit.Pixel);
        templateImage.Save(newFileLocation + newFileName, System.Drawing.Imaging.ImageFormat.Jpeg);

        templateG.Dispose();
        templateImage.Dispose();

        pickedG.Dispose();
        pickedImage.Dispose();
    }
    initImage.Dispose();
}

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