使用C#创建缩略图图片

5
@functions{

    public void GetThumbnailView(string originalImagePath, int height, int width)
    {
        //Consider Image is stored at path like "ProductImage\\Product1.jpg"

        //Now we have created one another folder ProductThumbnail to store thumbnail image of product.
        //So let name of image be same, just change the FolderName while storing image.
        string thumbnailImagePath = originalImagePath;
        originalImagePath = originalImagePath.Replace("thumb_", "");
        //If thumbnail Image is not available, generate it.
        if (!System.IO.File.Exists(Server.MapPath(thumbnailImagePath)))
        {
            System.Drawing.Image imThumbnailImage; 
            System.Drawing.Image OriginalImage = System.Drawing.Image.FromFile(Server.MapPath(originalImagePath));

            double originalWidth = OriginalImage.Width;
            double originalHeight = OriginalImage.Height;

            double ratioX = (double)width / (double)originalWidth;
            double ratioY = (double)height / (double)originalHeight;

            double ratio = ratioX < ratioY ? ratioX : ratioY; // use whichever multiplier is smaller

            // now we can get the new height and width
            int newHeight = Convert.ToInt32(originalHeight * ratio);
            int newWidth = Convert.ToInt32(originalWidth * ratio);

            imThumbnailImage = OriginalImage.GetThumbnailImage(newWidth, newHeight,
                         new System.Drawing.Image.GetThumbnailImageAbort(ThumbnailCallback), IntPtr.Zero);
            imThumbnailImage.Save(Server.MapPath(thumbnailImagePath), System.Drawing.Imaging.ImageFormat.Jpeg);

            imThumbnailImage.Dispose();
            OriginalImage.Dispose();
        }

    }

    public bool ThumbnailCallback() { return false; }

}

在另一个StackOverflow的问题中,我找到了这段代码并真的很喜欢它,但在使用它时,创建缩略图时出现了以下问题:

应用程序中的服务器错误。

内存不足。描述:当前网络请求的执行过程中发生未处理的异常。请查看堆栈跟踪以获取有关错误的更多信息以及其在代码中的起源。

异常详情:System.OutOfMemoryException: 内存不足。

源错误:

Line 199: {

Line 200: System.Drawing.Image imThumbnailImage;

Line 201: System.Drawing.Image OriginalImage = System.Drawing.Image.FromFile(Server.MapPath(originalImagePath.ToString()));

Line 202:

Line 203: double originalWidth = OriginalImage.Width;

Source File: c:\Inetpub\wwwroot\Lokal\Views\Stok\SatisRaporu.cshtml
Line: 201

我的好奇心让我进入了异常详细信息,并看到了这个:

    //
    // Summary:
    //     Creates an System.Drawing.Image from the specified file.
    //
    // Parameters:
    //   filename:
    //     A string that contains the name of the file from which to create the System.Drawing.Image.
    //
    // Returns:
    //     The System.Drawing.Image this method creates.
    //
    // Exceptions:
    //   System.OutOfMemoryException:
    //     The file does not have a valid image format.-or- GDI+ does not support the
    //     pixel format of the file.
    //
    //   System.IO.FileNotFoundException:
    //     The specified file does not exist.
    //
    //   System.ArgumentException:
    //     filename is a System.Uri.
    [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
    public static Image FromFile(string filename);

但是,该文件夹中的所有图片都具有“.jpg”扩展名,所以对我来说似乎很奇怪。如果我无法从“.jpg”创建缩略图,我还能做什么?
我实际上想了解是否有人尝试在“.jpg”文件上尝试过,并且遇到了问题?如果没有问题发生,我可能做错了什么?
一个小注释:我在使用Razor语法的视图中进行此操作。我对C#语言略有了解,并且每天都在提高我的知识水平。
编辑:
如何调用该函数:
GetThumbnailView("../pics/thumb_" + (("0000000" + stocks.stockcode).Substring(("0000000" + stocks.stockcode).Length - 7, 7)) + ".jpg", 200, 200);

4
这似乎是一个非常奇怪的描述,说明了一个 OutOfMemoryException 异常被抛出。我对编写这段代码的人失去了一点尊重。他们应该验证输入并抛出另一种异常。 - David
@David Yea 告诉我!我花了10分钟才明白发生了什么。 - Berker Yüceer
你确定你传递的文件名中不包含jpeg扩展名或其他扩展名吗?我的意思是指 Server.MapPath(thumbnailImagePath) 代码。 - JoJa
这是我调用函数的方式:GetThumbnailView("../pics/thumb_" + (("0000000" + stocks.stockkode).Substring(("0000000" + stocks.stockkode).Length - 7, 7)) + ".jpg", 200, 200);,其中包含“.jpg”后缀。 - Berker Yüceer
1
我们在Web应用程序中使用WPF来缩略图图像,而不是使用GDI+。这些API非常好用。它们可以让您区分格式错误和OOM,同时支持多种流行编解码器,并在解码之前告诉您宽度/高度。感兴趣吗? - Roman Starkov
@romkyns 看起来很有趣,我想了解更多。 - Berker Yüceer
2个回答

3

我工作的网站使用WPF API而不是GDI+来生成缩略图。您需要在项目中添加两个引用以启用此功能:WindowsBase、PresentationFramework和PresentationCore。以下是代码的基本示例:

try
{
    using (var input = File.Open(inputFilename, FileMode.Open, FileAccess.Read, FileShare.Read))
    using (var thumb = File.Open(thumbFilename, FileMode.Create, FileAccess.Write, FileShare.None))
    {
        Thumbnail(input, thumb, 200, 100);
    }
}
catch (MyException)
{
    File.Delete(thumbFilename);
}

这段代码将缩略图适应200x100的矩形,同时保持宽高比。
(实际网站并不完全像上面那样做。我们实际上是尝试在文件上传POST处理程序中生成最小的缩略图。我们使用一个内存流来保存生成的缩略图。如果能够正确生成缩略图,我们就保存上传和小缩略图,否则向客户端返回错误响应。其他缩略图大小是动态生成并缓存的。)
下面是代码 - 请注意,我可能在将其转换为可重用内容时弄错了一些东西,但核心部分应该都在这里。请注意,它将所有缩略图保存为JPEG格式,但允许多种输入格式,包括JPEG和PNG。这可能对您有利或不利。
private static void Thumbnail(Stream source, Stream destination, int maxWidth, int maxHeight)
{
    int width = 0, height = 0;
    BitmapFrame frame = null;
    try
    {
        frame = BitmapDecoder.Create(source, BitmapCreateOptions.None, BitmapCacheOption.None).Frames[0];
        width = frame.PixelWidth;
        height = frame.PixelHeight;
    }
    catch
    {
        throw new MyException("The image file is not in any of the supported image formats.");
    }

    if (width > AbsoluteLargestUploadWidth || height > AbsoluteLargestUploadHeight)
        throw new MyException("This image is too large");

    try
    {
        int targetWidth, targetHeight;
        ResizeWithAspect(width, height, maxWidth, maxHeight, out targetWidth, out targetHeight);

        BitmapFrame targetFrame;
        if (frame.PixelWidth == targetWidth && frame.PixelHeight == targetHeight)
            targetFrame = frame;
        else
        {
            var group = new DrawingGroup();
            RenderOptions.SetBitmapScalingMode(group, BitmapScalingMode.HighQuality);
            group.Children.Add(new ImageDrawing(frame, new Rect(0, 0, targetWidth, targetHeight)));
            var targetVisual = new DrawingVisual();
            var targetContext = targetVisual.RenderOpen();
            targetContext.DrawDrawing(group);
            var target = new RenderTargetBitmap(targetWidth, targetHeight, 96, 96, PixelFormats.Default);
            targetContext.Close();
            target.Render(targetVisual);
            targetFrame = BitmapFrame.Create(target);
        }

        var enc = new JpegBitmapEncoder();
        enc.Frames.Add(targetFrame);
        enc.QualityLevel = 80;
        enc.Save(destination);
    }
    catch
    {
        throw new MyException("The image file appears to be corrupt.");
    }
}

/// <summary>Generic helper to compute width/height that fit into specified maxima while preserving aspect ratio.</summary>
public static void ResizeWithAspect(int origWidth, int origHeight, int maxWidth, int maxHeight, out int sizedWidth, out int sizedHeight)
{
    if (origWidth < maxWidth && origHeight < maxHeight)
    {
        sizedWidth = origWidth;
        sizedHeight = origHeight;
        return;
    }

    sizedWidth = maxWidth;
    sizedHeight = (int) ((double) origHeight / origWidth * sizedWidth + 0.5);
    if (sizedHeight > maxHeight)
    {
        sizedHeight = maxHeight;
        sizedWidth = (int) ((double) origWidth / origHeight * sizedHeight + 0.5);
    }
}

我必须承认,你的异常处理做得相当不错...现在正在尝试。 - Berker Yüceer
我猜这也需要PresentationCore引用,我可以在视图中使用吗?或者你有没有尝试过并且发现性能不足? - Berker Yüceer
@BerkerYüceer 我不明白为什么你不能在视图中使用它。根据我们的经验,最慢的事情绝对是文件上传;服务器在响应之前所做的事情并不显著。但是,您肯定希望缓存缩略图而不是即时生成;这是CPU密集型的。第一次请求缩略图时这样做已经足够好了,但每次有人请求缩略图时都这样做太慢了。 - Roman Starkov

1

文件扩展名并不重要,实际的图像字节才是关键。很可能其中一个jpg文件已经损坏了。你应该按文件逐个捕获OutOfMemory异常,并适当处理。

由于你正在尝试生成缩略图,我建议你准备一张默认图片以在无法生成缩略图时使用。例如,大多数Web浏览器在图像损坏或丢失时使用带有红色X的小框。

另请参阅: SO#6506089 SO#1108607 SO#1644108 SO#9237457

对于那些好奇为什么会抛出OutOfMemoryException的人,请参阅这个问题的答案: 为什么 Image.FromFile 会因为无效的图像格式而抛出 OutOfMemoryException?


谢谢,但我不能只留下一个“未找到照片”的图像。如果存在图片,我必须生成缩略图,因此我不能为现有图片使用默认图像。JPG文件如何会损坏?将JPG文件移动到另一个文件夹可能会导致它损坏吗?(我真的不认为这可能是原因,但只是问一下。) - Berker Yüceer
无论如何,在第二次检查中,如果我“必须”使用GDI +,那么您的答案非常有用,因此+1。 - Berker Yüceer
也许更好的短语应该是“GDI+无法理解”,可以查看一些相关问题以了解示例。至于“找不到照片”的图像,异常会被抛出,你必须有一些应对策略。那只是一个建议。 - Jim Counts
我完全理解你的意思,感谢你之前提供的非常有用的信息。 - Berker Yüceer

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