Andreas Fredriksson
Andreas Fredriksson

Reputation: 161

Out of memory when cropping a bitmap with .Clone()

I am trying to automatically generate a thumbnail from an image uploaded by a user but I keep getting the exception "Out of memory". From what I understand the out of memory exception is thrown when you specify a starting position or a width/height that's outside of the image but even if I do this

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

on an image that is 800x900 pixels I still get the "Out of memory" exception, I can't figure out what's wrong with it and I can't really get any good answers from other threads since everything regarding the OOM exception is just the mistake of going outside the image boundaries. Does anyone have an explanation or solution to this?

EDIT: A bit more context

The loop for the images.

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

The method that is being called by each image in the list to generate the thumbnail.

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;


    }

Upvotes: 4

Views: 2336

Answers (4)

Cryptc
Cryptc

Reputation: 3595

I received this Exception when the Rectangle used to crop the image was partly outside the bounds of the image.

Upvotes: 1

Immortal Blue
Immortal Blue

Reputation: 1700

I think the problem you're getting here is that Bitmaps need to be disposed. If one gets garbage collected without releasing its underlying unmanaged content (i.e. being disposed), then that memory can not be recovered...

Also note that you will need to dispose both bitmaps. Best thing to do is wrap them in a using, something like this:

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

It is worth noting that using will call Dispose() on it's target, even if an exception is thrown (thus having the same finally type functionality as @Scott Chamberlain answer

Upvotes: 2

Scott Chamberlain
Scott Chamberlain

Reputation: 127553

Here is the correct way to properly dispose of your objects.

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

Use a try/finally block to ensure that even in the event of a error your objects get disposed. Doing the extra Bitmap thumbnail = new Bitmap(bmp); in your answer just makes a extra bitmap you are forgetting to dispose.

Upvotes: 1

Andreas Fredriksson
Andreas Fredriksson

Reputation: 161

Okay so I found an answer to my problem by disposing the Bitmaps. After this bit of code

Bitmap thumbnail = bmp;

I added

bmp.Dispose();

And during debugging I noticed that none of the properties from bmp were left in the Bitmap called thumbnail so I changed it into the following

Bitmap thumbnail = new Bitmap(bmp);

Thank you all for telling me to dispose the Bitmaps!

Upvotes: 1

Related Questions