使用 .Clone() 裁剪位图时出现内存不足错误。

4

我正在尝试从用户上传的图像自动生成缩略图,但是我一直收到“内存不足”的异常。 据我所知,当您指定超出图像范围的起始位置或宽度/高度时,会引发内存不足异常,但即使我这样做了

var rct = new Rectangle(5, 5, 10, 10);
var whatever = bitmap.Clone(rct, bitmap.PixelFormat);

在一个800x900像素的图像上,我仍然会收到“内存不足”的异常,我无法弄清楚问题出在哪里,也无法从其他帖子中获得任何好的答案,因为与OOM异常有关的一切都是超出图像边界的错误。 有人有解释或解决方案吗?
编辑:更多背景信息
循环处理图像。
foreach (var blob in fileInfoList)
{
    var blockBlobName = CheckExistence(BaseBlobUrl, blob.FileName, blob.FileNameWithoutExtension);

    var image = new Image()
    {
        BlobUrl = Path.Combine(BaseBlobUrl, blockBlobName),
        FullName = blob.FileName,
        FileName = blob.FileNameWithoutExtension,
        BlockBlobName = blockBlobName,
        OwningOrganizationId = CurrentUser.UserOrganization.OrganizationId,
        ThumbnailUrl = CreateThumbnail(blob.File, blockBlobName),
        Name = "Whatever"
    };

    blobList.Add(image);

    RepositoryFactory.AzureStorageRepository.SaveImage(blob.File, blockBlobName, blob.ContentType, CurrentUser.UserOrganization.Organization.Id);
}

每个图像在列表中调用的方法来生成缩略图。
public string CreateThumbnail(byte[] b, string parentImageName)
    {
        Bitmap bmp;

        using (var ms = new MemoryStream(b))
        {
            bmp = new Bitmap(ms);
        }

        Bitmap thumbnail = bmp;

        Rectangle rect = new Rectangle(5, 5, 10, 10);

        if (bmp.Width > bmp.Height)
            thumbnail = bmp.Clone(rect, bmp.PixelFormat);
        else if (bmp.Height > bmp.Width)
            thumbnail = bmp.Clone(new Rectangle((bmp.Height/2) - (bmp.Width/2), 0, bmp.Width, bmp.Width), bmp.PixelFormat);

        byte[] bmpArray = new byte[0];

        using (var ms = new MemoryStream())
        {
            finalCrop.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
            ms.Close();

            bmpArray = ms.ToArray();
        }

        var name = "Thumbnail_" + parentImageName;

        RepositoryFactory.AzureStorageRepository.SaveThumbnail(bmpArray, name, "jpg/image", CurrentUser.UserOrganization.Organization.Id);

        return BaseBlobUrl + "thumbnails/" + name;


    }

1
你在一个循环里做这个,是吗? - Thomas Ayoub
生成缩略图的方法在循环内调用,因为用户可以同时上传多张图片。 - Andreas Fredriksson
1
你能提供更多的代码吗,包括循环上下文? - Thomas Ayoub
6
请注意,您从未对图像/位图进行“Dispose”操作;这是一个非常糟糕的想法,因为该对象关联着许多本地资源,其中一些相当有限(例如GDI+句柄)。无论如何,GDI+并不真正设计用于在服务器上使用,所以请小心。您尝试过的所有图片都出现了这个问题吗?我无法重现您看到的问题,请注意,从GDI+中的OOM可能意味着许多不同的错误,而不仅仅是OOM或边界错误。您在哪里收到异常? - Luaan
1
你好像没有在任何地方处理你创建的位图。除非你将位图返回给某个地方,否则应该为你的位图添加一个using块。 - Glorin Oakenfoot
显示剩余4条评论
4个回答

2
我认为你在这里遇到的问题是Bitmaps需要被释放。如果一个Bitmap在没有释放其底层未托管内容(即未被处理)的情况下被垃圾回收,那么该内存将无法被回收...
另外请注意,你需要释放两个Bitmap。最好的做法是将它们包装在using语句中,像这样:
        using (var ms = new MemoryStream(b))
        {
            using (Bitmap bmp = new Bitmap(ms))
            using (Bitmap thumbnail = bmp)
            {

                Rectangle rect = new Rectangle(5, 5, 10, 10);

                if (bmp.Width > bmp.Height)
                    thumbnail = bmp.Clone(rect, bmp.PixelFormat);
                else if (bmp.Height > bmp.Width)
                    thumbnail = bmp.Clone(new Rectangle((bmp.Height / 2) - (bmp.Width / 2), 0, bmp.Width, bmp.Width), bmp.PixelFormat);

                byte[] bmpArray = new byte[0];

                using (var ms = new MemoryStream())
                {
                    finalCrop.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
                    ms.Close();

                    bmpArray = ms.ToArray();
                }

                var name = "Thumbnail_" + parentImageName;

                RepositoryFactory.AzureStorageRepository.SaveThumbnail(bmpArray, name, "jpg/image", CurrentUser.UserOrganization.Organization.Id);

                return BaseBlobUrl + "thumbnails/" + name;
            }
        }

值得注意的是,using语句将在其目标上调用Dispose()方法,即使抛出异常(因此具有与@Scott Chamberlain答案相同的finally类型功能)。

1
好的,我找到了一个解决方案,通过处理位图来解决我的问题。 在这段代码之后:
Bitmap thumbnail = bmp;

我添加了。
bmp.Dispose();

在调试过程中,我注意到bmp中的所有属性都没有留在名为缩略图的位图中,因此我将其更改为以下内容。

Bitmap thumbnail = new Bitmap(bmp);

感谢大家告诉我应该如何处理位图!

我要么使用 using 块,要么将 bmp.Dispose() 放在 try/finally 块中。另外,您将该 dispose 放错了位置,它需要放在两行 thumbnail = bmp.Clone 之后。最后,您从未处理 thumbnail 的 dispose。 - Scott Chamberlain
我已经发布了一个答案,展示了正确的做法。你现在的方法存在一些重大的错误,你可能不会立即看到它们,但是在运行几个小时后,可能会导致程序随机崩溃。 - Scott Chamberlain
是的,我将在该方法中使用“using”块来代替所有位图。对于“Clone”行,“bmp Bitmap”不再需要。 - Andreas Fredriksson

1
这里是正确的处理对象的方法。
Bitmap bmp = null;
Bitmap thumbnail = null;

try
{
    using (var ms = new MemoryStream(b))
    {
        bmp = new Bitmap(ms);
    }

    Rectangle rect = new Rectangle(5, 5, 10, 10);

    if (bmp.Width > bmp.Height)
        thumbnail = bmp.Clone(rect, bmp.PixelFormat);
    else if (bmp.Height > bmp.Width)
        thumbnail = bmp.Clone(new Rectangle((bmp.Height/2) - (bmp.Width/2), 0, bmp.Width, bmp.Width), bmp.PixelFormat);
    else
        thumbnail = bmp;



    byte[] bmpArray = new byte[0];

    using (var ms = new MemoryStream())
    {
        finalCrop.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
        ms.Close();

        bmpArray = ms.ToArray();
    }

    var name = "Thumbnail_" + parentImageName;

    RepositoryFactory.AzureStorageRepository.SaveThumbnail(bmpArray, name, "jpg/image", CurrentUser.UserOrganization.Organization.Id);

    return BaseBlobUrl + "thumbnails/" + name;
}
finally
{
    if(bmp != null)
        bmp.Dispose();

    if(thumbnail != null)
        thumbnail.Dispose(); //If bmp and thumbnail are the same object this is still safe to do.
}

使用try/finally块以确保即使出现错误,您的对象也会被处理。在您的答案中执行额外的Bitmap thumbnail = new Bitmap(bmp);只会创建一个额外的位图,您忘记了处理它。

1

当用于裁剪图像的矩形部分超出图像范围时,我收到了此异常。


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