3

I am taking an image, decoding the byte array that is generated by TakePicture method and then rotating the bitmap 270 degrees. The problem is that i seem to run out of memory, and i do not know how to solve it. Here is the code:

Bitmap image = BitmapFactory.DecodeByteArray (data, 0, data.Length);
data = null;
Bitmap rotatedBitmap = Bitmap.CreateBitmap (image, 0, 0, image.Width,
    image.Height, matrix, true);
Let'sRefactor
  • 3,303
  • 4
  • 27
  • 43
  • Make your images smaller. – Stanojkovic Feb 07 '16 at 14:15
  • This might help http://stackoverflow.com/questions/2968397/algorithm-to-rotate-an-image-90-degrees-in-place-no-extra-memory – interjaz Feb 07 '16 at 14:24
  • i cannot make images smaller.. i need them as big as posible for OCR – Bogdan Constantin Feb 07 '16 at 14:32
  • @interjaz i don`t understand how that helps me – Bogdan Constantin Feb 07 '16 at 14:33
  • How big are your images? – SushiHangover Feb 07 '16 at 14:35
  • 3264x2448 but depends on what device is used on – Bogdan Constantin Feb 07 '16 at 14:42
  • My understanding is that you have a photo in data array, then in image bitmap and then in rotatedBitmap. Depending how you are using data variable, setting it to null not necessary means that all got rid of all references, and therefore it cannot be garbage collected. If you have access to the byte array, you might want to do some manipulation on it rather than use built in libraries. – interjaz Feb 07 '16 at 14:43
  • SO you are looking at ~30MB of ram per image array in memory, you can always do a `GC.Collect` after you `null` out `data` but before you create the rotated image. After adding the `Collect` to the code, run the Xammie Profiler on it to ensure that you do not have any other references to it (or any other memory hogging objects....) – SushiHangover Feb 07 '16 at 14:44
  • @interjaz A great idea, but how? – Bogdan Constantin Feb 07 '16 at 14:50
  • @SushiHangover yeah.. but the quota for a task is 32 mb in android.. – Bogdan Constantin Feb 07 '16 at 14:54
  • I guess before you go down that route (due to overall complexity). Check: 1. If you are using data as method's parameter is ref byte[] data and not byte[] data. 2. Have you tried requesting large heap http://developer.android.com/training/articles/memory.html ? 3. Use GC.Collect after setting it to null. 4. Do you really need to rotate the picture or just thumbnail and then do the job on the server? – interjaz Feb 07 '16 at 15:00
  • i do the OCR on the phone – Bogdan Constantin Feb 07 '16 at 15:08
  • I would suggest to place everything in an using block. That way everything will be disposed after leaving the scope. Also a GC.Collect could be helpful but should be used wisely. – tequila slammer Feb 07 '16 at 15:11
  • the data array i`m getting from TakePicture method via param – Bogdan Constantin Feb 07 '16 at 15:14
  • Failing a `Bitmap.CreateBitmap` is a Java Heap allocation failure as it could not satisfy the request (contiguous allocation). You have to look at memory management in two managed worlds, C# and Java at the same time. Once you take the picture and 'save' it (tmp file), make sure you are disposing of everything from the Java side, then do a GC.Collect (see Xam' Android Java bridge docs) as Xamarin's runtime does not see the bitmap allocation as it is a peer instance of only 10 bytes, not the 30mb it really is. So, Drop the refs, call GC.Collect and watch the Profiler output. – SushiHangover Feb 07 '16 at 15:30
  • Or do the rotate in pure C# (or via NDK-based shared library and thus avoid the Java/managed heap) and do not add the Java bitmap library into the mix. FYI: Still need to drop the original Java ref (and the UI controls from memory) and call GC.Collect as you still have a peer instance via the Java bridge that needs to be released. – SushiHangover Feb 07 '16 at 15:32
  • which ocr engine you are using – nikolap Feb 07 '16 at 16:23
  • I am using tesseract – Bogdan Constantin Feb 07 '16 at 17:11

1 Answers1

2

Please have a look at Load Large Bitmaps Efficiently from official Xamarin documentation, which explains how you can load large images into memory without the application throwing an OutOfMemoryException by loading a smaller subsampled version in memory.

Read Bitmap Dimensions and Type

async Task<BitmapFactory.Options> GetBitmapOptionsOfImageAsync()
{
    BitmapFactory.Options options = new BitmapFactory.Options
    {
        InJustDecodeBounds = true
    };

    // The result will be null because InJustDecodeBounds == true.
    Bitmap result=  await BitmapFactory.DecodeResourceAsync(Resources, Resource.Drawable.someImage, options);

    int imageHeight = options.OutHeight;
    int imageWidth = options.OutWidth;

    _originalDimensions.Text = string.Format("Original Size= {0}x{1}", imageWidth, imageHeight);

    return options;
}

Load a Scaled Down Version into Memory

public static int CalculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight)
{
    // Raw height and width of image
    float height = options.OutHeight;
    float width = options.OutWidth;
    double inSampleSize = 1D;

    if (height > reqHeight || width > reqWidth)
    {
        int halfHeight = (int)(height / 2);
        int halfWidth = (int)(width / 2);

        // Calculate a inSampleSize that is a power of 2 - the decoder will use a value that is a power of two anyway.
        while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth)
        {
            inSampleSize *= 2;
        }
    }

    return (int)inSampleSize;
}

Load the Image as Async

public async Task<Bitmap> LoadScaledDownBitmapForDisplayAsync(Resources res, BitmapFactory.Options options, int reqWidth, int reqHeight)
{
    // Calculate inSampleSize
    options.InSampleSize = CalculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.InJustDecodeBounds = false;

    return await BitmapFactory.DecodeResourceAsync(res, Resource.Drawable.someImage, options);
}

And call it to load Image, say in OnCreate

protected async override void OnCreate(Bundle bundle)
{
    base.OnCreate(bundle);
    SetContentView(Resource.Layout.Main);
    _imageView = FindViewById<ImageView>(Resource.Id.resized_imageview);

    BitmapFactory.Options options = await GetBitmapOptionsOfImageAsync();
    Bitmap bitmapToDisplay = await LoadScaledDownBitmapForDisplayAsync (Resources, 
                             options, 
                             150,  //for 150 X 150 resolution
                             150);
    _imageView.SetImageBitmap(bitmapToDisplay);
}

Hope it helps.

Let'sRefactor
  • 3,303
  • 4
  • 27
  • 43