The RedBit team was developing a software system for a customer that consisted of a REST API, Web Application and mobile apps for iOS & Android. The mobile app was using Xamarin Forms was to show images to the user and these images are delivered via the REST API. There was a backend web application where images could be uploaded and these images were being served to iOS and Android devices as required.
The Problem
These images were being served to devices in their raw uploaded size and resolution. Although we limited the file sizes to 2MB, users took full advantage of that and uploaded 2MB images. Sometimes a collection of 15-20 images would be downloaded so a user could potentially download 30-40MB of just images.
This caused two main issues
- The application was using a lot more data than it needed to
- Low end devices were crashing from out of memory exceptions because of large images
With some images being upwords of 2MB in size and 3 times the resolution of the device, it was pointless for the device showing a hi-res image if it’s pixel density as 1x.
The Solution
We needed to resize images and instead of serving just one image, we would serve a set of images with a range of sizes. Devices can then pick which size is appropriate for their device. The architecture would be as follows
Resizing Images With Azure Functions
Visual Studio Azure Functions SDK has a built in template called Image Resize that worked perfectly for our needs. When you create a new function, you will get a dialog box as follows
NOTE: Make sure you are on .NET Functions v1, to get the dialog box above as v2 does not provide the Image Resizer template. Doesn’t mean you can’t use V2, just means you will have to manually hook things up.
When you fill in the dialog you set your parameters and you will get a file similar to the following
We modified a bit as we needed 3 image sizes. Our images were being uploaded to a blob storage container named Images. The Image Resize Function triggers every time a new image is added to that container.
The function creates three new images and places them into their respective folders:
- Images-Small
- Images-Medium
- Images-Large
Note that this function uses the ImageResizer nuget package.
Serving Multiple Images
Now our REST API was built using ASP.NET Web APIs so to update this we added a new property to send not only the original image, but also the other three images sizes so the consumers of the API (iOS, Android and web app) could chose which one fits their specifications.
We’ll send down a JSON object that is defined by the following class which contains links to each of our new image sizes along with the original image link:
This C# class was used on both ASP.NET to serialize to JSON and then on iOS and Android to deserialize from JSON to object form. The JSON object would look like the following
The Xamarin Client
The mobile clients used to just display the original image that was uploaded. Now with multiple images, the client can be updated to download and show based on the devices DPI. This is similar to how Android use different image sizes based on the devices DPI with iOS doing the same. We will create our own way of utilizing DPI to server appropriate images on mobile.
On Android we had the following helper method
For iOS, we did not have any crashing issues, but these tend to be higher end devices but since we are sharing code across platforms for iOS we had the following
We had an enum to define the image size as follows
Based on your projects needs you can include a wider range of image sizes. You may also include a a set of images for tablets. We won’t get into how to display an image for Xamarin Forms as you can find documentation here
The Conclusion
As with any software, you try to think of scenarios and try to optimize things as much as you can and the RedBit team thought we were doing that by limiting the image upload size to 2MB. But of course, users took full advantage of that. This resulted in users uploading 2MB images but then 15-20 images would need to be downloaded to mobile devices, this obviously became a problem.
By refactoring some code we were able to reduce the footprint on devices drastically and reduce the crash rate on android to almost 0 compared to previous versions, and previous versions were not too bad!
One of the cases we observed was and image size going from 2MB to 50KB. That’s a huge reduction in data costs and memory usage especially on low end devices and can be seen in the crash reports. Users are being served hundreds of images from the application and reducing and providing multiple image options was a relatively easy adjustment with a huge impact.