Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid bitmap out of memory when working on very large image for ie: 10.000.000 pixel and above

Currently i'm working on a system that load a very large image, with minimum width x heigh >= 10.000.000 pixel.

But the ratio of the user's upload image usually do not match our requirement ratio so i have to crop it to proper ratio, but when using System.Drawing bitmap to crop it, i always got SytemOutOfMemory exception.

I have try Bitmap.Clone and Graphic.DrawImage with correct RectangleF but no luck.

Is there anyways to do this without getting the outofmemory exception or are there any alternatives to System.Drawing library to get this task done easily ?

My code to load the image from user upload file:

    var fileBinary = new byte[stream.Length];
    stream.Read(fileBinary, 0, fileBinary.Length);
    stream.Position = 0;
    var fileExtension = Path.GetExtension(fileName);
    using (Image image = Image.FromStream(stream, false, false))
    {
        //validation and check ratio
        CropImage(image, PORTRAIT_RATIO, fileExtension);
     }

And the CropImage function:

//Crop Image from center with predefine ratio
    private byte[] CropImage(Image sourceImg, float ratio, string fileExtension)
        var height = sourceImg.Height;
        var width = sourceImg.Width;

        var isPortrait = width < height;
        RectangleF croppingRec = new RectangleF();

        float positionX = 0;
        float positionY = 0;
        float cropHeight = (float)height;
        float cropWidth = cropHeight * PORTRAIT_RATIO;
        positionY = 0;
        positionX = (width - cropWidth) / 2;

        if (cropWidth > width)
        {
            cropWidth = width;
            cropHeight = cropWidth * (1 / PORTRAIT_RATIO);
            positionX = 0;
            positionY = ((height - cropHeight) / 2);

        }

        croppingRec.Width = cropWidth;
        croppingRec.Height = cropHeight;
        croppingRec.X = positionX;
        croppingRec.Y = positionY;

        Bitmap bmpImage = sourceImg as Bitmap;
        Bitmap bmpCrop = bmpImage.Clone(croppingRec, bmpImage.PixelFormat);
        bmpCrop.Save("D:/test" + fileExtension, ImageFormat.Jpeg);

        ImageConverter converter = new ImageConverter();

        return (byte[])converter.ConvertTo(bmpCrop, typeof(byte[]));
    }

}
like image 759
thinh.lam Avatar asked Apr 09 '15 17:04

thinh.lam


1 Answers

You could convert the bitmap to a byte array. Try something like this (looks hackie but i don't know another way):

int pixelSize = 3;
int bytesCount = imgHeight * imgWidth * pixelSize;
byte[] byteArray= new byte[bytesCount];

BitmapData bitmapData = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, imgWidth, imgHeight), ImageLockMode.ReadOnly, bitmap.PixelFormat);
Marshal.Copy(bitmapData.Scan0, byteArray, 0, bytesCount);

Each pixel in this array is represented by 3 bytes (this depends on the bitmap type). So you know that lenght of a bitmap line is 3 * imgWidth. Using this you could simply navigate in the byte array and copy just what you need into a new array.

You would then create a new bitmap with the desired final size, get the bitmap data and Marshal.Copy the new array into that:

Bitmap newBitmap = new Bitmap(Width, Height);
BitmapData newBitmapData = b.LockBits(BoundsRect,
                                ImageLockMode.WriteOnly,
                                newBitmap.PixelFormat);
Marshal.Copy(newByteArray, 0, newBitmapData.Scan0, newBytesCount);

Unlock the bitmaps at the end:

newBitmap.UnlockBits(newBitmapData );
bitmap.UnlockBits(bitmapData);

Hope this helps. Cheers.

like image 63
Andrei Nadejde Avatar answered Sep 30 '22 06:09

Andrei Nadejde