Resizing Images using Elixir

Various colored pictures frames in many sizes.
Image by Annie Ruygt

In this article we show how to optimize and resize images on the fly!. Fly.io is a great place to run your Phoenix LiveView applications! Check out how to get started!

When building web applications we accept user uploaded images and later want to use them as in our applications. This leads to many questions:

  • What format will their device upload? Which format do we accept? Which format do we use?
  • How many bytes is the upload and do we want to pay to serve that?
  • How physically large in pixels is the photo?
  • Is the photo oriented correctly?
  • Do we want to strip the metadata to help protect our user’s privacy?

Luckily, with Elixir, there are many good libraries to help with this task. So many in fact that you might be wondering what’s the point of even starting this article?

While using libraries is a good and handy way to quickly deliver on your goals, it can end up leaving you stuck when you need to go your own way. In this post, we will explore how those libraries work under the hood, because frankly it’s not that complicated! And if you knew how to do it yourself, you might not need a library at all.

In this post we will explore shelling out to The Swiss army knife of image manipulation, ImageMagick and an example using a wrapper library around libvips called Vix. We will cover:

  • Resizing
  • Converting formats
  • Optimizing
  • Fixing Orientation
  • Stripping Metadata

Setup

In this post we will simply be manipulating files on your file system and thus we assume:

  1. You have an image file from a user.
  2. If you don’t already use a Plug.Upload, you are deleting uploads once you are done with them.
  3. You are putting your file elsewhere to be served.

In our first example, we will be using ImageMagick. Most package managers have a PACKAGE_MANAGER install imagemagick command which should get you set up!

ImageMagick

The first and most straightforward example is calling the command line tools provided by ImageMagick which is a rock solid, heavily deployed and used, open source toolkit for image manipulation. If we were to call it directly from the command line, it would look something like

convert USER_FILE.webp -strip -thumbnail 100x100^ -auto-orient -format png NEW_FILE.webp

This will -strip your file of metadata and extra bits, resize it to a -thumbnail with an minimum width and height of 100x100^ with the aspect ratio preserved, attempts to -auto-orient the file and finally -format it as a PNG to NEW_FILE.webp. All in one command and one go to limit memory use!

Calling this same function from Elixir is actually incredibly straight forward:

path = "USER_FILE.webp"
output = "NEW_FILE.webp"
System.cmd("convert", [path, "-auto-orient", "-strip", "-thumbnail", "100x100^", "-format", "png", output])

The result of this is {_cmd output as string, return code 0 for good}. By default, System.cmd/3 runs synchronously. You can play around with its options to run it in parallel, but I would run it in an Async Task. Imagine a function taking the input path and returning a new one, leaving the original image untouched. If you want to change the file in place, replace convert with morgiphy.

libvips

The libvips project is an open source image manipulation library written in high performance C/C++, it can do way more low level operations than ImageMagick and is used often in machine learning.

Luckily for us someone has already made NIF bindings for Elixir, so we don’t have to mess around with C or machine learning matrix math.

path = "USER_FILE.webp"
output = "NEW_FILE.webp"
Vix.Vips.Operation.thumbnail!(path, 100)
|> Vix.Image.write_to_file(output)

And that’s it, the default options for Operation.thumbnail!/3 will strip, orient and resize preserving the aspect ratio for you. The Image.write_to_file/2 will magically convert to a PNG and apply some optimizations. Vix has many, many options and functions to really dial in your images, but I recommend you dig into them yourself! For real, reading the docs is a great way to learn.

If you’d like a higher level library for working with images there is the fantastic Image package which provides a higher level API to Vix, and to help with the important stuff like memes.

Wrapping up

Ultimately, being able to shell out to the command line is a powerful tool, and greatly expands any developer’s capabilities. I have only barely scratched the surface of what’s possible here in terms of calling command line tools like this. We can go very far with just System.cmd, but if you need more fined grained process control and supervision, you will want to look into the Port module documentation.

What else is there for images?

Optimizing images is a deep rabbit hole and sticking with these tools you can’t really go wrong, but here are some other tools:

  • pngquant can help you squeeze a PNG down to size.
  • jpgegoptim is the industry standard for JPEG optimization.

Call these after you’ve resized them to what you need. You may also want to reduce quality optimizations in ImageMagick/Vix to a minimum so that you don’t apply them twice. Experimentation is required!

And one may be asking “sure but what do the pro’s use?” and the answer is ImageMagick and libvips. They may have complex image processing pipelines running on huge machines, but ultimately somewhere they shell out or call C/C++ to these tools or similar open source projects. Every “cloud image processor” is doing this as well and now we can too!

Fly.io ❤️ Elixir

Fly.io is a great way to run your Phoenix LiveView app close to your users. It’s really easy to get started. You can be running in minutes.

Deploy a Phoenix app today!