2
\$\begingroup\$

This code addresses performance issues specific to Android running Xamarin / C# when downloading images (BMP/PNG) into a listview. The caller simply specifies the URL to fetch the image from, and the preferred dimensions.

Optimisations I could do, but haven't:

  • Not sure if I should use the Android or Java namespace
  • Stage the downloaded byte[] in an intermediate location - not sure if memory exhaustion is possible
  • Image caching
  • Convert to an Android Drawable
  • Abstract the source to be something other than a URL, e.g. the local resources
  • Make a static "helper" class (not sure how to do this...)
using System;
using System.Threading.Tasks;
using Android.Graphics;
using Android.Content.Res;
using System.Net;
namespace validAndroid
{
 public class ImageUtils
 {
 async Task<BitmapFactory.Options> GetBitmapOptionsOfImageAsync(byte[] imageBytes)
 {
 BitmapFactory.Options options = new BitmapFactory.Options {
 /*Setting the InJustDecodeBounds property to true while decoding avoids memory allocation,
 * returning null for the bitmap object but setting OutWidth, OutHeight and OutMimeType .
 * This technique allows you to read the dimensions and type of the image data prior to
 * construction (and memory allocation) of the bitmap.*/
 InJustDecodeBounds = true
 };
 // The result will be null because InJustDecodeBounds == true.
 Bitmap result = await BitmapFactory.DecodeByteArrayAsync (imageBytes, 0, imageBytes.Length -1, options);
 int imageHeight = options.OutHeight;
 int imageWidth = options.OutWidth;
 System.Diagnostics.Debug.WriteLine (string.Format ("Original Size= {0}x{1}", imageWidth, imageHeight));
 return options;
 }
 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;
 }
 async Task<Android.Graphics.Bitmap> LoadScaledDownBitmapForDisplayAsync(byte[] imageBytes, 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 Android.Graphics.BitmapFactory.DecodeByteArrayAsync (imageBytes,0,imageBytes.Length-1, options);
 }
 public async Task<Bitmap> GetImageForDisplay(string imageURL,int reqWidth, int reqHeight )
 {
 byte[] imageBytes = null;
 using (var webClient = new WebClient())
 {
 imageBytes= webClient.DownloadData(imageURL);
 }
 BitmapFactory.Options options = await GetBitmapOptionsOfImageAsync(imageBytes);
 var bitmapToDisplay = await LoadScaledDownBitmapForDisplayAsync ( imageBytes,options, reqWidth, reqHeight);
 imageBytes = null;
 return bitmapToDisplay;
 }
 }
}
asked Jun 19, 2015 at 1:40
\$\endgroup\$
0

1 Answer 1

3
\$\begingroup\$

WebClient.DownloadData is a blocking method; you should use DownloadDataTaskAsync. Network I/O is almost certainly the slowest part of the whole operation, so it's important that we're not blocking here. You might also want to consider using the newer HttpClient instead of WebClient.

The third parameter of DecodeByteArrayAsync is the length of the array, but you're passing it imagesBytes.Length - 1. I think you want to pass it imageBytes.Length instead.

There's no need to set imageBytes to null.

I don't really like that LoadScaledDownBitmapForDisplayAsync modifies the options parameter. Since the method is private it's not that important, but it is, to me, surprising behaviour. To avoid this, I would consider merging GetBitmapOptionsOfImageAsync and LoadScaledDownBitmapForDisplayAsync into one method, e.g:

public static async Task<Bitmap> DecodeByteArrayAsync(byte[] imageBytes, int requiredWidth, int requiredHeight)
{
 var options = new Options { InJustDecodeBounds = true };
 await BitmapFactory.DecodeByteArrayAsync(imageBytes, 0, imageBytes.Length, options);
 options.InSampleSize = CalculateInSampleSize(options, requiredWidth, requiredHeight);
 options.InJustDecodeBounds = false;
 return await BitmapFactory.DecodeByteArrayAsync(imageBytes, 0, imageBytes.Length, options);
}
answered Jun 19, 2015 at 5:16
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.