Converting HEIC Images in Ruby on Rails
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.
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
. ImageConverter
will 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.
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!