Images take up to 50% of the total size of an average web page. And if images are not optimized, users end up downloading extra bytes. And if they’re downloading extra bytes, the site not only takes that much more time to load, but users are using more data, both of which can be resolved, at least in part, by optimizing the images before they are downloaded.
Researchers around the world are busy developing new image formats that possess high visual quality despite being smaller in size compared to other formats like PNG or JPG. Although these new formats are still in development and generally have limited browser support, one of them, WebP, is gaining a lot of attention. And while they aren’t really in the same class as raster images, SVGs are another format many of us have been using in recent years because of their inherently light weight.
There are tons of ways we can make smaller and optimized images. In this tutorial, we will write bash scripts that create and optimize images in different image formats, targeting the most common formats, including JPG, PNG, WebP, and SVG. The idea is to optimize images before we serve them so that users get the most visually awesome experience without all the byte bloat.
This GitHub repo has all the images we’re using and you’re welcome to grab them and follow along.
Set up
Before we start, let’s get all of our dependencies in order. Again, we’re writing Bash scripts, so we’ll be spending time in the command line.
Here are the commands for all of the dependencies we need to start optimizing images:
It’s a good idea to know what we’re working with before we start using them:
- ImageMagick: works with all kinds of raster images
- webp optimizes WebP files
- JPEGoptim optimizes JPG/JPEG files
- OptiPNG optimizes PNG files
- SVGO and svgexport are Node packages that optimize SVG assets
OK, we have our images in the original-images
directory from the GitHub repo. You can follow along at commit <a href="https://github.com/ravgeetdhillon/create-optimize-images/tree/3584f9b00380aac7c60a87427029500a04a20592">3584f9b</a>
.
Note: It is strongly recommended to backup your images before proceeding. We’re about to run programs that alter these images, and while we plan to leave the originals alone, one wrong command might change them in some irreversible way. So back anything up that you plan to use on a real project to prevent cursing yourself later.
Organize images
OK, we’re technically set up. But before we jump into optimizing all the things, we should organize our files a bit. Let’s organize them by splitting them up into different sub-directories based on their MIME type. In fact, we can create a new bash to do that for us!
The following code creates a script called organize-images.sh
:
#!/bin/bash
input_dir="$1"
if [[ -z "$input_dir" ]]; then
echo "Please specify an input directory."
exit 1
fi
for img in $( find $input_dir -type f -iname "*" );
do
# get the type of the image
img_type=$(basename `file --mime-type -b $img`)
# create a directory for the image type
mkdir -p $img_type
# move the image into its type directory
rsync -a $img $img_type
done
This might look confusing if you’re new to writing scripts, but what it’s doing is actually pretty simple. We give the script an input directory where it looks for images. the script then goes into that input directory, looks for image files and identifies their MIME type. Finally, it creates subdirectories in the input folder for each MIME type and drops a copy of each image into their respective sub-directory.
Let’s run it!
bash organize-images.sh original-images
Sweet. The directory looks like this now. Now that our images are organized, we can move onto creating variants of each image. We’ll tackle one image type at a time.
Convert to PNG
We will convert three types of images into PNG in this tutorial: WebP, JPEG, and SVG. Let’s start by writing a script called webp2png.sh
, which pretty much says what it does: convert WebP files to PNG files.
#!/bin/bash
# directory containing images
input_dir="$1"
if [[ -z "$input_dir" ]]; then
echo "Please specify an input directory."
exit 1
fi
# for each webp in the input directory
for img in $( find $input_dir -type f -iname "*.webp" );
do
dwebp $img -
done
Here’s what happening:
input_dir="$1"
: Stores the command line input to the scriptif [[ -z "$input_dir" ]]; then
: Runs the subsequent conditional if the input directory is not definedfor img in $( find $input_dir -type f -iname "*.webp" );
: Loops through each file in the directory that has a.webp
extension.dwebp $img -o ${img%.*}.png
: Converts the WebP image into a PNG variant.
And away we go:
bash webp2png.sh webp
We now have our PNG images in the webp
directory. Next up, let’s convert JPG/JPEG files to PNG with another script called jpg2png.sh
:
#!/bin/bash
# directory containing images
input_dir="$1"
if [[ -z "$input_dir" ]]; then
echo "Please specify an input directory."
exit 1
fi
# for each jpg or jpeg in the input directory
for img in $( find $input_dir -type f -iname "*.jpg" -o -iname "*.jpeg" );
do
convert $img ${img%.*}.png
done
This uses the convert
command provided by the ImageMagick package we installed. Like the last script, we provide an input directory that contains JPEG/JPG images. The script looks in that directory and creates a PNG variant for each matching image. If you look closely, we have added -o -iname "*.jpeg"
in the find
. This refers to Logical OR, which is the script that finds all the images that have either a .jpg
or .jpeg
extension.
Here’s how we run it:
bash jpg2png.sh jpeg
Now that we have our PNG variants from JPG, we can do the exact same thing for SVG files as well:
#!/bin/bash
# directory containing images
input_dir="$1"
# png image width
width="$2"
if [[ -z "$input_dir" ]]; then
echo "Please specify an input directory."
exit 1
elif [[ -z "$width" ]]; then
echo "Please specify image width."
exit 1
fi
# for each svg in the input directory
for img in $( find $input_dir -type f -iname "*.svg" );
do
svgexport $img ${img%.*}.png $width:
done
This script has a new feature. Since SVG is a scalable format, we can specify the width
directive to scale our SVGs up or down. We use the svgexport
package we installed earlier to convert each SVG file into a PNG:
bash svg2png.sh svg+xml
Commit <a href="https://github.com/ravgeetdhillon/create-optimize-images/tree/76ff80a36ac69ad96e93963e30adac4acfda6a5f">76ff80a</a>
shows the result in the repo.
We’ve done a lot of great work here by creating a bunch of PNG files based on other image formats. We still need to do the same thing for the rest of the image formats before we get to the real task of optimizing them.
Convert to JPG
Following in the footsteps of PNG image creation, we will convert WebP, JPEG, and SVG into JPG. Let’s start by writing a script called png2jpg.sh
that converts PNG to SVG:
#!/bin/bash
# directory containing images
input_dir="$1"
# jpg image quality
quality="$2"
if [[ -z "$input_dir" ]]; then
echo "Please specify an input directory."
exit 1
elif [[ -z "$quality" ]]; then
echo "Please specify image quality."
exit 1
fi
# for each png in the input directory
for img in $( find $input_dir -type f -iname "*.png" );
do
convert $img -quality $quality% ${img%.*}.jpg
done
You might be noticing a pattern in these scripts by now. But this one introduces a new power where we can set a -quality
directive to convert PNG images to JPG images. Rest is the same.
And here’s how we run it:
bash png2jpg.sh png 90
Woah. We now have JPG images in our png
directory. Let’s do the same with a webp2jpg.sh
script:
#!/bin/bash
# directory containing images
input_dir="$1"
# jpg image quality
quality="$2"
if [[ -z "$input_dir" ]]; then
echo "Please specify an input directory."
exit 1
elif [[ -z "$quality" ]]; then
echo "Please specify image quality."
exit 1
fi
# for each webp in the input directory
for img in $( find $input_dir -type f -iname "*.webp" );
do
# convert to png first
dwebp $img -o ${img%.*}.png
# then convert png to jpg
convert ${img%.*}.png -quality $quality% ${img%.*}.jpg
done
Again, this is the same thing we wrote for converting WebP to PNG. However, there is a twist. We cannot convert WebP format directly into a JPG format. Hence, we need to get a little creative here and convert WebP to PNG using dwebp
and then convert PNG to JPG using convert
. That is why, in the for
loop, we have two different steps.
Now, let’s run it:
bash webp2jpg.sh jpeg 90
Voilà! We have created JPG variants for our WebP images. Now let’s tackle SVG to JPG:
#!/bin/bash
# directory containing images
input_dir="$1"
# jpg image width
width="$2"
# jpg image quality
quality="$3"
if [[ -z "$input_dir" ]]; then
echo "Please specify an input directory."
exit 1
elif [[ -z "$width" ]]; then
echo "Please specify image width."
exit 1
elif [[ -z "$quality" ]]; then
echo "Please specify image quality."
exit 1
fi
# for each svg in the input directory
for img in $( find $input_dir -type f -iname "*.svg" );
do
svgexport $img ${img%.*}.jpg $width: $quality%
done
You might bet thinking that you have seen this script before. You have! We used the same script for to create PNG images from SVG. The only addition to this script is that we can specify the quality
directive of our JPG images.
Everything we just did is contained in commit <a href="https://github.com/ravgeetdhillon/create-optimize-images/tree/884c6cff68d42c574d7266595ee6ff4260d5590b/svg%2Bxml">884c6cf</a>
in the repo.
Wrapping up
See that? With a few scripts, we can perform heavy-duty image optimizations right from the command line, and use them on any project since they’re installed globally. We can set up CI/CD pipelines to create different variants of each image and serve them using valid HTML, APIs, or even set up our own image conversion websites.
I hope you enjoyed reading and learning something from this article as much as I enjoyed writing it for you. Happy coding!