Image Processing Experiments (1) - Rust

Tagged with: programming, rust

Published on and last updated on

TL;DR I implemented an image processing experiment in Rust and was positively surprised. Find the source code in this repository: tobiaswatzek/image-processing-experiments.

In one of my courses I recently had to implement a program that satisfies the following criteria in three programming languages.

The program should do the following:

  1. Read in an image as the first argument passed to it on the command line.
  2. Print the file path, width and height of the image to the console.
  3. Display the original image and a blurred version of the image.
  4. Convert the image to grayscale and save it besides the original image as gray-IMAGENAME.

There was no constraint on the programming language that we used so i decided to implement my solution in Rust, Bash (with ImageMagick), and F#. I dabbled in each of those languages a bit before but none of them can be considered a language in which I am fluent in.

In this article I would like to discuss my takeaways from implementing the assignment in Rust.

Ferris the crab, unofficial mascot for Rust

Rust is a systems programming language that can be compared with C or C++ in that it does not run your program inside of a runtime or use a garbage collector. It can be used when performance is one of the main concerns but in opposite to C or similar languages it provides a memory management model that leads to memory and thread safety.

The installation instructions that are provided on the Rust website worked without any problems on my Laptop that runs Arch Linux. I used VS Code with the Rust (rls) extension to implement the assignment.

Rust has a package manager named Cargo that can be used to manage dependencies (in Rust slang those are called crates) of the application - much like NPM for JavaScript or Nuget for .NET. In addition to package management Cargo can be used to run and build the project.

I did not want to write the image loading and transformation code from scratch so I looked for libraries that would do that task for me. I stumbled upon the image crate which can be used to work with images in rust. In addition to the image crate I found the imageproc crate which extends the image crate with image processing functionalities. To fulfil the 3rd point of the requirements “Display the original image and a blurred version of the image.”

I wanted to use a GUI library or bindings to existing GUI libraries. I tried to use the GTK bindings for Rust but because of my lack of experience with Rust and GTK things got complicated really fast and I thought that there must be an easier way to display two images. As it is often the case the simple path was right in front of me the whole time. The imageproc crate provides the method imageproc::window::display_multiple_images which was exactly what I needed. If you think that just adding the library to the dependencies file (Cargo.toml) and calling the method would be enough you are wrong. I had to learn the hard way that crates can have feature flags that enable certain additional features of a dependency. In this case the display-window flag has to be added which will internally add a dependency to sdl2 which provides rust bindings for Simple Directmedia Layer. Going down this rabbit hole has shown me once again how important it is to be able to read other people’s code or I would never have found the definition of the features inside of the source code of the imageproc crate.

After I had all the libraries and a working development environment it was surprisingly easy to implement the task. Two things that seem to be common in Rust were Option types returned by functions and macros like println!.

The Option type can be seen as an alternative to returning null. It can either have a value Some or it can have no value None. I recognized the construct from functional languages like F# or Haskell. The great thing about option types is that they make it explicit that a function could return nothing aka null and that those cases should be handled or else you end up in the WhoopsieThereIsNothingException club (aka NullPointerException club or NullReferenceException club).

Macros in Rust can be compared to function templates that will be replaced with a piece of code at compile time. Therefore the println! macro can actually parse its format string and error if it is not given enough parameters.

This is the first article I wrote and I hope it contained something useful for you. I will follow up with articles about the two other implementations in Bash and F#. The source code for all the implementations can be found in this repository: tobiaswatzek/image-processing-experiments.