Reducing image size with ImageMagick and cwebp
🗒️ If you came here from an internet search and use Hugo for your website, just skip everything and go straight to Preparing a Hugo shortcode.
In the post describing the rationale behind using a single Org file for this website, I mentioned that “it would be pretty cool to have a place to share small trips with pictures and some comments”. Well, it is indeed really cool from my perspective as I have a single document describing what and how I felt at the moment. Well, you can also see that there’s also a mention of pictures there. What does this entail to this website?
This might be a bit obvious for some, but not so much for others, but
I do aim to make this website as “lightweight” as possible. Why the
quotes? Lightweight is really subjective, while some may think that I
was successful with this goal, others might disagree due the 13 kB
image this website currently loads on every single page1.
Another point is that I use Git to manage this website’s source code. It’s well known that managing large binary blobs with it is a pain and I want to avoid that.
Right, enough talking, what about requirements to aim for here?
- pictures should weight at most 180 kB
- it shouldn’t look too pixelated
- colors should be close to the original image
Starting out, most of the pictures I take have some weird format, it
is either Nikon’s NEF
format or Apple’s HEIC
. With this in mind,
the first step would be to convert them to an easier format to work
on.
Toying with image formats
Considering that we have a shell where ImageMagick is already available for use:
# convert all HEIC files to PNG
$ mogrify -format png *.HEIC
Now, the next step would be to reduce the image’s size. As you can see
below, the converted image is quite big compared to the original
HEIC
image:
$ du -ah
17M ./01.png
4.1M ./01.HEIC
There are quite a few ways to reduce its size. You can reduce its
resolution, reduce the color depth, change its format to a lossy one.
In this case what I’m going to do is to use the cwebp command-line
tool to resize and compress the picture. Alright, why webp
? I chose
it for two main reasons: it is supported by quite a few web browsers
and has a really nice CLI.
🗒️ You might be thinking this already, but I know, this isn’t too fair! I’m comparing a lossless format with a lossy one.
Let’s run a test on our 01.png
image first by running the following
commands:
$ cwebp -q 80 01.png -o 01.webp -metadata all -resize 0 1200 -progress
# checking the size
$ du -ah
160K 01.webp
17M 01.png
Woah, this is a huge difference! However, how good is it really?
Let’s compare the original image with the .webp
one.


This opinion might differ from person to person, but this is more than good enough for me. The image lost some of its definition, but the shapes aren’t unrecognizable nor it lost most of its colors. This is perfectly fine for what I’m aiming for here.
Shell scripting our solution
The missing piece is a shell script that can do the hard work for me as I don’t want to copy and paste from this blog post every time. 🤪
Well, guess what I’m going to use for this? That’s right, Nix! Leveraging its package management features to help us always have the needed tools to transform our images.
apps.transform-images =
let
cwebp = "${libwebp}/bin/cwebp";
identify = "${imagemagick}/bin/identify";
in
utils.lib.mkApp {
drv = writeShellScriptBin "transform-images" ''
CWEBP_PARAMS=('-q 80 -metadata all -progress')
shopt -s nullglob nocaseglob extglob
for FILE in *.@(jpg|jpeg|tif|tiff|png); do
ORIENTATION=$(${identify} -format '%[fx:(h/w)]' "$FILE")
if (( orientation > 1 ))
then
SIZE="0 1000"
else
SIZE="1000 0"
fi
${cwebp} $PARAMS -resize $SIZE "$FILE" -o "''${FILE%.*}".webp;
done
'';
};
This script loop through all images on the directory, makes sure that
we resize the image to the proper size based on its orientation and
then convert it to WEBP. All I need to do is run nix run .#apps.transform-images
on my terminal.
Preparing a Hugo shortcode
Hugo has a really cool feature called Shortcodes. They allow you to write custom snippets that can do all kinds of transformation on your website’s content. Imagine that you have a task X that you don’t want to copy and paste every time, you can write down a shortcode taking some parameters and have it render the content for you.
My idea here is to leverage HTML’s Responsive Images feature and send a different image size based on the user screen size. This can be useful to reduce bandwidth usage as the user will download a smaller image.
{{/* possible sizes */}}
{{ $sizes := (slice "480" "800" "1200") }}
{{ $side := .Get "side" }}
{{ $src := resources.Get (.Get "src") }}
{{ $caption := .Inner | default "" }}
<div class="image image-{{- $side -}}">
<figure>
<img
sizes="(min-width: 35em) 1200px, 100vw"
{{/* Only resize if the image width size is bigger than the resize size. */}}
srcset='
{{ range $sizes }}
{{ if ge $src.Width . }}
{{ ($src.Resize (printf "%sx" .)).Permalink }} {{ (printf "%sw" .) }},
{{ end }}
{{ end }}'
{{/* when no support for srcset (old browsers, RSS), we load small (800px) */}}
{{/* if image smaller than 800, then load the image itself */}}
{{ if ge $src.Width "800" }}
src="{{ ($src.Resize "800x").Permalink }}"
{{ else }}
src="{{ $src.Permalink }}"
{{ end }}
alt="{{- $caption -}}" loading="lazy"/>
{{ if $caption }}
<figcaption><em>{{ $caption | markdownify }}</em> (<a href="{{ $src.Permalink }}">original</a>)</figcaption>
{{ end }}
</figure>
</div>
This code is basically a modified version of my previous shortcode and gist:cpbotha/figure.html.The cool part is that not only this will generate the HTML we need for the responsive images, but it will also convert the images to those specified sizes automatically.
Conclusion
At the end of this journey, I realized that the only thing holding me
from using better quality images is Git. Hugo does a pretty good
job2 at reducing image size with its Resize
function and it is
already part of the build process this website goes through anyway.
Hardware and network connection tend to get faster and faster as time passes by. In this case, it makes sense to keep the highest image resolution available on your source code, this way increasing it on the build step is as easy as modifying the shortcode.