Converting HEIC Images in Ruby on Rails

Michal Korzawski
eXpSoftwareEngineering
4 min readDec 30, 2022

--

eXp Realty Software Engineering Blog

How to convert High Efficiency Image File Format (.HEIF / .HEIC) images in Ruby on Rails without a library dependency.

Apple’s iOS 11 brought the new HEIC image format into the mainstream. The purpose of HEIC was to save storage space but still provide high fidelity images. However, like many of Apple’s decisions, the arrival of this image format causes a plethora of issues. Now, even unwittingly, many iPhone users are snapping photos solely as HEIC, as opposed to the much more ubiquitous — and supported — JPEG format. The largest consequence of a new-ish image format is the lack of browser support. Apple’s own Safari browser doesn’t even support them; in fact, not a single browser supports HEIC images.

Caniuse browser support report for HEIC images

For one of our apps, users upload images often directly from their phone, and often these images are in HEIC format due to the popularity of iPhones. Nearly daily we’d be flooded with errors when these uploads were added, and no amount of UX/UI ever prevented these users from not choosing to upload them. Occasionally, they’d even resort to adding “.jpg” at the end of a HEIC file just to pass validations.

For uploading, we are using Active Storage, and the app is deployed to AWS ECS.

To address this issue and add support of HEIC image uploading, I at first followed a solution from a blog article. Salomón Charabati’s method worked perfectly well. However, it was quite slow — too slow for us — especially when attaching large HEIC files. Also, one app in particular, requires a minimum of 12 images, where 20 or more is the norm. The Achilles heel of this implementation was the Heic2any library, which was used for the HEIC images to be previewed on the front end. So after all of this, I came up with my own solution. Let’s start with the backend.

First, make sure you have gem "image_processing” added to your project’s Gemfile. The image_processing gem makes it easy to work with both ImageMagic and libvips libraries.

First we need to create an ImageConverter service, which will encapsulate image resizing and image conversion logic.

The two methods — resize_to_limit and convert — are just two of many handy methods from the image_processing gem. Check out the gem docs to see them all.

The next step might be considered a little controversial. At first, I wanted to keep this logic closer to ActiveStorage::Blob. I was experimenting with custom callbacks but was not too fond of them. Any logic I injected into Blob felt too late in the process. This too little too late feeling forced me to take a step back and modify the image parameters before they were ever sent to ActiveStorage::Blob.

We map through :images if it’s present in the params and pass them one by one to ImageConverter. ImageConverterwill convert the image to jpg format, and if the image width is larger than 1200, it will also resize it. This service will then return a tempfile which we then pass to ActionDispatch::Http::UploadedFile. From there, we merge the converted images into permitted params.

So what exactly is ActionDispatch::Http::UploadFile anyhow? When we submit a file through an HTML form, multipart/form-data is used to send file content in the request body. Rack — a middleware between our web application and web server, Puma for example — parses the multipart/form-data parameters into a hash object which looks like:

{
"_method"=>"put",
"property"=>
{"images"=>
[{:filename=>"IMG_6542 (1).HEIC",
:type=>"image/heif",
:name=>"property[images][]",
:tempfile=>#<File:/tmp/RackMultipart20221128-1-1no4h17.HEIC>,
:head=>"Content-Disposition: form-data; name=\"property[images][]\"; filename=\"IMG_6542 (1).HEIC\"\r\nContent-Type: image/heif\r\n"}
],
},
}

ActionDispatch::Http::UploadFile is used to wrap a file’s params passed to Rails by Rack as a hash. It also adds a few methods along the way.

For the frontend, we want to make a HEIC image previewer, but without using the Heic2any library, which we already covered was too slow for us.

The steps are pretty simple. We grab a file and wrap it in a FormData object. Next, we use fetch to make an asynchronous post request to ImageController#convert_heic method — more about this soon — and read the response using blob() . This method returns a Blob which we put into an object URL using URL.createObjectURL. That object URL is then used as a source for the image tag, which is inserted into the DOM as an HTML img element.

Stimulus controller responsible for previewing heic images

It’s evident the conversion for the images’ frontend previews are taking place on the backend, so let’s take a peek inside the ImagesController where this is transpiring.

We reused the ImageConverter service here; where after converting and resizing, the image is sent back to the client. That’s it!

I mentioned above that using the Heic2any library was slow. For more evidence, I added a simple comparison of the two below. Thank you so much for reading and click Follow to see more articles!

How the HEIC conversion looks on the front end, and comparison. Left is Heic2any and right is custom solution.

--

--