There are a few advanced tricks to optimize images for web consumption, such as sharpening them after size reduction and using dedicated command-line tools to reduce their sizes.
In the first part of the article, we’ve looked at Image Magick usage, removing sensitive information from images, and geometry reduction techniques. You can read more about them here: How to optimize images on Linux
Image sharpening basics
Next, we’re going to look at advanced usage of Image Magick techniques to keep better image details. When a picture is made smaller by resizing, small details often become blurry or lost simply because there are now fewer pixels to describe the same amount of information and relative to the size of objects, pixels just became bigger (or the objects smaller, depending on how you look at it)
Image sharpening is a great tool to combat this effect by applying this effect after the image is resized. It works by increasing the contract at the edges of the image, making it easier to see small details. It can also exaggerate the noise introduced by high compression (especially low-quality JPEG files) so the end result should always be reviewed for possible artifacts and it should not be used on heavily compressed images. It also helps to recover details from blurry images (out of focus photos, for example) but it’s not always possible to put details back that were never there to begin with, so results may vary.
Image Magick sharpening
To apply sharpening to an image either as a standalone process or in combination with resizing the parameter “-sharpen” can be set.
First, let’s make an image blurry by executing “convert -blur 3×3”. Blur works by setting two parameters, radius x sigma. Radius sets the size of the area around each pixel to apply blurring, sigma is the size of the brush (how much a pixel is spread) around each processed pixel.
The workflow that created this image:
$ convert -blur 3x3 90 butterflies.jpg butterflies-blurred.jpg
$ convert -sharpen 10x10 butterflies-blurred.jpg butterflies-blurred-sharpened.jpg
$ convert -append butterflies*jpg butterflies-combined.jpg
With -append it’s possible to simply combine images, +append does it horizontally, -append does it vertically.
On resized images, sharpen works especially well to restore blurred details in small sizes:
The command that resulted in the image above was this:
$ convert butterflies.jpg -resize 300x300 -write b1.jpg -sharpen 3x3 b2.jpg
$ convert +append b[12].jpg b3.jpg
We’re using -write here to save an intermittent step (the resized but not yet blurred image).
Using JpegOptim to optimize a batch of images
JpegOptim is a dedicated line tool that can be run both locally and even remotely on a server if installed, to process and reduce JPEG images. It supports both lossless and lossy operation and it’s smart enough to keep original images if this optimization process wouldn’t result in a reduced size.
It’s can also remove all meta information (Exif tags, color profiles) that should preferably be done to any image meant to be public. You can read about why it’s important in the first part of this article here.
By default, it overwrites all files with optimized versions of the same image can simply be run on a batch of images by losslessly optimizing them and removing all comments and other non-image information:
$ jpegoptim -s *.jpg
It will display details on each image and how much their sizes were reduced as it progresses. To run it in lossy mode and reduce image quality to further optimize their sizes, the “-m quality” parameter can be set. Quality is a number between 0-100, higher means better. Any file that already has a lower quality will only be compressed in “lossless” mode.
To compare these, I ran it on a batch of random files (in the “jpg” folder), here are the results:
$ jpegoptim jpg/*jpg -d jpg-optim -s
$ jpegoptim jpg/*jpg -d jpg-optim-95 -s -m 95
$ jpegoptim jpg/*jpg -d jpg-optim-90 -s -m 90
$ jpegoptim jpg/*jpg -d jpg-optim-85 -s -m 80
$ du -sh jpg*
27M jpg
17M jpg-optim
11M jpg-optim-80
13M jpg-optim-90
13M jpg-optim-95
Out of the 27MB worth of JPEG files, lossless optimization already compressed them to 63%, reducing their quality to 95% reduced it further to 48%. These results vary greatly depending on the nature and the size of the images so it’s a good idea to run various tests. Normally 90% provides pretty good results while keeping most of the details intact, especially on high-resolution images.
Optimizing PNG images
There is a command line-utility called “optipng” that is similar to “jpegoptim” in functionality. It recompresses PNG files in a lossless way by optimizing compression and reducing various PNG parameters without compromising image quality, such as maximum number of colors, color palette size. It can also remove meta-information (comments, etc) by adding the “-strip all” parameter.
To test its effectiveness, I converted all the sample JPG files to PNG then ran “optipng” on them to test its effectiveness:
$ cd jpg; for i in *jpg; do convert $i ../png/${i/.jpg/.png}; done; cd ..
$ optipng -strip all -dir png-opti png/*png
$ dh -sh jpg png png-opti
$ du -sh jpg png png-opti
27M jpg
93M png
91M png-opti
We can see how PNG files are normally waaaaaay bigger than JPGs and optimizing PNGs will often result in marginal (in this case around 4%) file size reduction.
Bonus tip – parallelize processing
When running these commands on a batch of files it can take a long time for them to finish and they’re processing files using one CPU core only. In the age of 4-6-8+ CPU cores it’s possible to quicken this operation by running multiple threads of the same command in parallel by using shell tricks.
We are going it use our “optipng” example above. We need to convert our workflow to run a separate process for each file then parallelize it using “xargs”, so this command:
optipng -strip all png/*png
should be run once for each file instead of letting optipng pick all the files for us:
find png/*png -print0 -type f | xargs -n1 -0 -P8 optipng -strip all -dir png-opti2
This first part of the command (find) will create a list of files and output them in a safe format (null-terminated), the second part (xargs) takes that list (in the same null-terminated format) and runs one optipng process for each line (filename) in parallel, while keeping the maximum number of parallel processes at 8.
This way it will run 8 streams of optipng commands, each one processing one png file at a time, reducing the total processing time 8-fold on an 8 core CPU.