ENG | Image Processing Cheat sheet for Linux
A comprehensive cheat sheet for image processing tasks on Linux, covering batch file processing, WebP conversion, PNG optimization, resizing, type conversion, video processing, and more. Includes examples and command-line instructions for tools like ImageMagick, FFmpeg, and GraphViz.
Introduction
Many of the image processing commands discussed in this article involve the use of ImageMagick, a tool designed for batch processing of images. For further reference, visit https://www.imagemagick.org/Usage/. The primary purpose of this article is to serve as a guideline for myself, eliminating the need to search for these commands in my shell history.
Some of these command were used for creating this blog.
Syntax is compatible either with Bash (Linux) or Windows command prompt. Windows command prompt does not allow breaking a single command into multiple lines
This article is continuously updated
Batch file processing
GNU Parallel is quite useful tool. Sample usage is
1
2
3
4
5
6
7
8
9
# For each filename matching vscode_*.png run cwebp -z 9 {filename} -o {remove_extension(filename)}.webp
# to convert all vscode_*.png to vscode_*.webp using all CPU threads
parallel cwebp -z 9 {} -o {.}.webp ::: vscode_*.png
# Convert all TIFFs to JPEG
parallel convert {} {.}.jpg ::: *.tif
# Resize all JPEG to width 200 and rename them to thumb_{filename}.png
parallel ~/bin/resize_image -w 200 {} thumb_{.}.png ::: *.JPG *.jpg
WebP conversion
WebP is a modern image format with better lossless compression ratio than PNG and with higher quality per file size than JPEG for lossy compression - unlike JPEG, artifacts are barely visible.
However it has limitations: it’s for web. Maximum image size is 16Kx16K and only color images (with transparency) are supported. It can store EXIF or XMP metadata.
Hopefully it will be replaced by JPEG-XL which aims for versatility.
1
2
3
4
# Lossless conversion with maximum compression
cwebp -z 9 pers_activity.png -o pers_activity.webp
# Convert all pers_hugin_*.png files to lossy webp format with quality factor 95
parallel cwebp -m 6 -hint picture -q 95 {} -o {.}.webp ::: pers_hugin_*.png
AVIF conversion
Lossy conversion examples
1
2
avifenc chilli.png rpi_chilli_thumb.avif
avifenc -s 3 -y 420 podman_thumb.png podman_thumb.avif # S3: slighly slower speed, 420: color subsampling
AVIF seems to have even better size/quality ratio than webp and defaults seem good enough. However I’m not sure how widely this format is supported and I’m not even sure about support on Safari.
PNG optimization
Honestly, I prefer WEBP for size, but when compatibility or larger images are required, there’s a zopflipng
command in a package zopfli
in basically any Linux distribution.
Usage is
1
2
3
4
# Convert all packages in directory and save result with a default prefix `zopfli_`
zopflipng --prefix *.png
# Convert one file and specify output
zopflipng in.png out.png
Resizing
Magic Kernel Sharp
Skip this tool unless you are a software developer.
I usually use Magic Kernel Sharp package from John P. Castello, which is supposed to be default resizing tool of Facebook and Instagram. There is some math background behind it, but my guess is that it’s purpose is defeated by sharpening. Package is written in C and has some weird build pipeline. On Ubuntu it requires installing libjpeg62
package (default is libjpeg-turbo, which is not compatible). Package comes with several tools, not only for image resizing, but also for rotation, perspective distorsion and so on.
Sample usage:
1
2
3
4
5
6
# Resize image to width of 900 pixels using default Magic Kernel Sharp filter
resize_image -w 900 perseidactivity.png pers_activity.png
# The same using MAGIC_KERNEL filter
resize_image -w 900 -m MAGIC_KERNEL perseidactivity.png pers_activity.png
# Resize to height of 800 pixels (-h is for help)
resize_image -H 800 Screenshot_20230820-135411429.jpg pers_stellarium.png
MAGIC_KERNEL_SHARP is indeed sharper which is better for photos, texts, screenshots … I guess everything.
Gimp
Gimp has great nohalo and lohalo filters - especially for something like graphs, comicses. Gaussian is not bad for sharpness.
Imagemagick
1
2
3
4
# Resize to fit within 900x900 pixels box, keep aspect ratio
convert in.jpg -resize 900x900 out.jpg
# and specify output JPG quality and remove EXIF and other metadata
convert in.jpg -resize 900x900 -quality 92 -strip out.jpg
Crop & rotate, adjust contrast and color, create PDF (scanned booklet processing)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Crop two areas (carefully chosen using FastStone Image Viewer on Windows) to TIFF
# What did not fit was later scanned again. This effectively crops left and right pages
parallel magick convert {} -crop 1296x744+26+29 -rotate 90 {.}a.png ::: ScanImage0??.tif
parallel magick convert {} -crop 1296x744+26+813 -rotate 90 {.}b.png ::: ScanImage0??.tif
# Make whatever is below 45% brightness and above 70% brightness black or white
# Then desature colors a bit and change hue from red to pinkish-red
# This was done to restore particular color of scan that was black, white and pinkish-red
# and used two pass print. Original paper, especially front page was quite yellow due to age
parallel magick convert {} -level 45%,70% -modulate 100,80,90 out/{} ::: *.png
# Recompress images better (at this point images were manually renamed as 0.png to 31.png)
cd out
parallel zopflipng {} z{} ::: *.png
# Merge them into PDF with 200x200 DPI and some metadata
convert `ls -1 z*.png | sort -V` -density 200x200 \
-define pdf:Producer="Pavel Peřina" \
-define pdf:Title="Návod k použití logaritmických pravítek Logarex system Darmstadt" \
-define pdf:Keywords="27451, 27251, Logarex, logaritmické pravítko, system Darmstadt, systém Darmstadt" \
manual-logarex-27451-27251-cz.pdf
Result is this PDF (file size: 4MB)
Type conversion
Conversion to Jpeg XL
I concluded that as of 2023, this format is not very practical. Conversion is sloooow, results are often larger than WEBP and even PNG and it’s not widely supported.
1
2
3
4
5
6
# Animated GIF to JXL
cjxl Frame_1a_V04_eng_small.gif Frame_1a_V04_eng_small.jxl -q 100 --effort=9
# PNGs to lossless JXL
parallel -j 1 cjxl {} {.}.jxl --brotli_effort=11 --effort=9 -q 100 ::: *.png
# JPGs to lossy JXL but without any further information loss (more efficient lossless compression stage than JPEG)
parallel -j 1 cjxl {} {.}.jxl --brotli_effort=11 --effort=9 --lossless_jpeg=1 ::: *.jfif *.jpg *.jpeg
Conversion of raw data to TIFF
1
convert -size 8000x6000 -depth 8 gray:data-8000x6000x1.raw data-8000x6000.tif
Conversion to grayscale
1
convert test.png -colorspace gray -depth 8 test.png
Combining two grayscale images to RGB (for comparison)
This command combines img1.png to red, average of images to green and img2.png to blue channel. This is useful for comparison of two images with minor changes (or colorizing images with different light direction, SEM images …). It’s assumed that input images are grayscale and they must be ordered as red, green, blue.
1
2
3
4
5
magick convert \
img1.png \
\( img1.png img2.png -evaluate-sequence mean \) \
img2.png \
-set colorspace sRGB -combine result.png
TODO: image
Removing alpha
TODO
Adding noise
TODO: retry, found in history
1
2
3
4
convert size 512x512 xc: +noise rnadom -colorspace gray noise.png # Create noise image
noise-8192x8192.tif -crop 5120x5120+1536+1536 -resize 512x512 \( +clone +noise random -colorspace gray -alpha on -channel a +channel \) -compress none zigzag-0000.tif
convert noise-8192x8192.tif -crop 5120x5120+1536+1536 -resize 512x512 \( +clone +noise Gaussian -colorspace gray -alpha on -channel a +channel \) -compose multiply -compress none zigzag-0000.tif
convert noise-8192x8192.tif -crop 5120x5120+1536+1536 -resize 512x512 -attenuate 0.75 +noise Gaussian -compress none zigzag-0002.tif
Averaging images to remove noise
This uses output of python script to get image list, but it can be replaced by five filenames for example.
1
magick convert `python3 ../get_image_list.py` -evaluate-sequence mean mean.png
Series of images to 3D TIFF
1
2
ls -1 slice-*.tif | sort -V > tifflist.txt
convert @tifflist.txt multipage.tif
Some video processing
Note: ffmpeg for Windows can be found here: https://www.gyan.dev/ffmpeg/builds/.
Remove audio from video file
1
ffmpeg -i VID_20170907_103806.mp4 -map 0:0 -vcodec copy out.avi
Series of images to video
Conversion to MPEG4 video that can be played anywhere
-pix_fmt yuv420p
is important for compatibility
1
ffmpeg -r 60 -i frame_%d.png -c:v libx264 -pix_fmt yuv420p out.mp4
Conversion to WebM video with AV1 codec (sorry for Windows)
YUV video is a raw video format that does not contain any metadata (not even resolution or framerate). It’s just image intensity picture in full resolution followed by two color components saved at half resolution (yuv420p) and then repeated for all images. It is (or was) the only image format supported by SVT-AV1 encoder which I compiled from source code.
IVF is Intel Video Format which can be remuxed to MKV (Matroska) or WebM. As far I know, WebM is simplified MKV container that can store only video data encoded by VP8, VP9 and AV1 codecs.
This is outdated, but it’s left there, yuv format can be still handy.
1
2
3
C:\apps\ffmpeg.exe -r 60 -i frame_%d.png -pix_fmt yuv420p out2.yuv
C:\apps\SvtAv1EncApp.exe -i .\out2.yuv -w 1280 -h 720 --fps-num 60000 --fps-denom 1001 -b out2.ivf
C:\apps\mkvtoolnix\mkvmerge.exe .\out2.ivf -o reaction-diffusion.webm
This works last year or two, SVT-AV1 codec was added to ffmpeg (slightly different examples).
1
2
3
ffmpeg -hide_banner -framerate 60 -f image2 -start_number 2343 -i %04d.tif -c:v libsvtav1 video.webm
C:\apps\ffmpeg.exe -i '.\Videos\2023-11-30 09-54-58.mkv' -c:v libsvtav1 -an bug_sample_import.webm
C:\apps\ffmpeg.exe -framerate 50 -f image2 -i .\blink01_00180%03d.tif -filter_complex "[0:v]scale=854x480" mosfet_blink01.webp
Playback or usage of YUV video in ffmpeg
and vlc
1
2
3
ffplay -s 1280x720 -pix_fmt yuv420p -f rawvideo out2.yuv
vlc --demux rawvideo --rawvid-fps 25 --rawvid-width 1920 --rawvid-height 1080 --rawvid-chroma I420 input.yuv
c:\apps\ffmpeg -hide_banner -s:v 1280x720 -f rawvideo -pix_fmt yuv420p -framerate 60000/1001 -i out2.yuv -c:v libx264 -preset veryslow -crf 17 out.mp4
Remuxing MKV video to MP4
1
ffmpeg -i obs_recording.mkv -c copy obs_recording.mp4
Speed up video
1
2
.\ffmpeg6.exe -i 'center of rotation.mkv' -vf "tmix=frames=5:weights='1',select='not(mod(n\,8))',setpts=0.2*PTS" -an -c:v libsvtav1 -crf 26 test.webm
.\ffmpeg7.exe -i 'center of rotation.mkv' -vf "tmix=frames=5:weights='1',select='not(mod(n\,8))',setpts=0.2*PTS" -an -r 60 -c:v libsvtav1 -crf 26 test.webm
I don’t know why, ffmpeg7 requires to specify FPS after the filter (-r 60
). It’s optional for older versions, but it somehow changes output file size, despite fps is the same.
Adding overlay to video
Taken from NLC timelapse pipeline which includes script for generating overlay and more.
1
2
3
4
5
ffmpeg -hide_banner \
-framerate 60 -f image2 -start_number 2343 -i %04d.tif \
-framerate 60 -f image2 -start_number 2343 -i overlay_%04d.png \
-filter_complex "[0:v]scale=1280:720[v1];[v1][1:v]overlay=0:0" \
-pix_fmt yuv420p -c:v libsvtav1 nlc-full-720p.webm
Basically takes input video stream #0 [0:v], downscales it to [v1]. Takes [v1] and applies it to [1:v] as overlay.
Note that multiple lines do not work on Windows.
Reencoding audio
1
ffmpeg -i edited.flac -ab 192k edited.mp3
Advanced stuff
Graphs and schemas for dark mode
Convert graph on white background to white image on black background. Use this image as an alpha channel. Then replace original image by a solid color and use the previous image as alpha channel.
In this example, the source image is
1
2
3
4
5
6
7
8
9
10
wget https://blogs.nasa.gov/Watch_the_Skies/wp-content/uploads/sites/193/2021/07/perseidactivity.png
resize_image -w 900 perseidactivity.png pers_activity.png
convert pers_activity.png \
\( -clone 0 -fill "#6d9998" -colorize 100 \) \
\( -clone 0 -negate -colorspace Gray \) \
\( -clone 1 -clone 2 -alpha off -compose copy_opacity -composite \) \
-delete 0,1,2 \
pers_activity2.png
cwebp -z 9 pers_activity2.png -o pers_activity_dark.webp
cwebp -z 9 pers_activity.png -o pers_activity_light.webp
TODO: image
Breakdown of the convert command
- 0: read
pers_activity.png
- 1: clone image 0. Set fill color. Colorize blends image with a solid color using 100% weight - effectively replacing image by this color
- 2: clone image 0. Negate it and convert it to grayscale
- 3: using temporary images 1 and 2, assure there’s no alpha on input and copy opacity from 2nd image turning on alpha again
- delete images from steps 0,1,2 leaving only 3
- save it as
pers_activity2.png
PDF from OpenOffice to dark mode picture
This describes how to convert OpenOffice document exported into PDF to image (e.g. table used in article about logarithms).
It may work with PDFs in general.
Imagemagick uses antialiasing which complicates things. It’s easier to do conversion using GhostScript.
Actually it be done only in GIMP. The PDF can be imported to GIMP without anti-alias, dithering etc. at 600dpi.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Create palette (colors determined prior to conversion)
convert -size 1x4 xc:#000000 xc:#e8f2a1 xc:#f6f9d4 xc:#ffffff +append palette.png
# Export PDF as image with 600 DPI
gs -dSAFER -dBATCH -dNOPAUSE -sDEVICE=png16m -r600 -sOutputFile=output.png exp_tables_101.pdf
# Make it indexed without dithering
convert output.png -dither none -remap palette.png output-indexed.png
# Open it in GIMP and
# Optionally replace colors, e.g black -> #cccccc, light green to khaki: #606444
# Optionallly click on a layer in Layers and add alpha
# Select -> by color -> click on background
# Press Delete
# Export image (e.g. output-dark transp)
# Resize image (e.g 6x to 100dpi or 827x1169px for A4 page) using GIMP's lo halo filter (it's quite good) or
resize_image -r 6 output-indexed-dark-transp.png temp.png
cwebp -z 9 temp.png -o table.png
# NOTE: steps above can be done in GIMP with lohalo resize -> better quality and smaller files in this case
Creating image strips and tiles
1
2
3
4
5
6
7
8
9
10
11
# Four tiles per row with no spacing
magick montage thumb_????.png -geometry +0+0 -tile 4x -background none tiles-out.png
# 24 images in 3 rows with 32 pixels border on all sides (64 pixels between images)
magick montage `python3 get_sorted_list.py` -tile 8x3 -geometry +32+32 -background none output.png
# Vertical strip of two images with 40 pixels south of "before"
magick convert before.png -background none -gravity south -splice 0x40 after.png -append output.png
# Horizontal strip
magick convert milkyway_fuji_thumb.png -background none -gravity east -splice 13x0 milkyway_pana_thumb.png +append milkyway_comparison.png
Adding shadows
1
magick test.png -bordercolor transparent -border 10x10 \( -clone 0 -page +3+3 -fill "#303030" -colorize 100% -blur 0x3 -background transparent -flatten \) +swap -composite -gravity northwest -crop +5+5 test-final.png
Breakdown
- add transparent border 10 pixels on each size (cannot be specified for left and right inddepently)
- clone image, shift canvas by 3x3 pixels and efficiently replace it by dark gray rectangle. Blur this rectangle. Set background to transparent and apply canvas movement
- swap shadown and image
- compose both layers together
- from north east crop 5,5 pixels
These parameters are close to FastStone Image Viewer shadow, border should be 3x blur radius
Resize with subpixel precision (Clear-Type simulation)
Based on i believe this thread and here is the result of image downscaled from 800x600 to 267x200 pixels. Bottom image is for comparison.
1
2
3
4
5
6
7
convert ResizeTestImage.png -gamma 0.5 -filter Mitchell -resize 800x200! \
-morphology Convolve "3x1: 0.33, 0.34, 0.33" \
-channel Red -morphology Convolve "3x1: 0.0, 0.0, 1.0" \
-channel Green -morphology Convolve "3x1: 0.0, 1.0, 0.0" \
-channel Blue -morphology Convolve "3x1: 1, 0, 0" \
+channel -filter Point -resize 267x200! -gamma 2.0 -depth 8 \
ResizeTestSubpixel.png
Breakdown:
- apply gamma correction and resize to 1/3 of heigth using Mitchell filter
- apply box filter to blur image horizontally
- shift red channel to the right and blue to the left
- resize to 1/3 of width using point/nearest neighbor filter, apply gamma
- save as 8bit PNG
Creating images from TeX files
This is done on Windows, but should work on Linux. First we need to update PATH
environment variable.
1
$env:Path += ";C:\users\pavel\AppData\Local\Programs\MiKTeX\miktex\bin\x64\;C:\apps\"
Convert TeX to DVI
1
latex -output-format="dvi" dft8_2.tex
Convert DVI to SVG
1
dvisvgm -p1 -v 7 -n dft8_2.dvi
Or convert DVI to PNG (for this page dark theme or light theme).
NOTES:
- Indexed colors are ugly after cropping in FastStone or GIMP.
- Foreground color does not work when file uses xcolor package
1
2
dvipng -z 9 -bg Transparent -D 144 -T tight -p 1 -fg 'rgb 0.69 0.69 0.69' --truecolor dft8_2.dvi
dvipng -z 9 -bg Transparent -D 144 -T tight -p 1 -fg 'rgb 0.20 0.20 0.24' --truecolor dft8_2.dvi
Adding metadata
1
2
3
4
5
6
7
8
9
10
11
# Title, description, author, ...
exiftool \
-XMP-xmpRights:UsageTerms="This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License" \
-XMP-xmpRights:WebStatement="http://creativecommons.org/licenses/by-sa/4.0/" \
-XMP-dc:Creator="Pavel Perina" \
-XMP-dc:Title="Fujitsu Esprimo Q956" \
-XMP-dc:Description="Fujitsu Esprimo Q956 ultra small form factor PC" \
hw_fujitsu_esprimo.webp
# Copy from photo
exiftool -tagsFromFile DSCF2100.jpg logs-slide-rule-logarex-27205-spring.webp
OpenSCAD to series of images
1
2
3
4
5
6
7
8
#!/bin/python3
from subprocess import call
for frm in range(0,360):
cam = ("--camera=0,0,0,60,0,%d,10" % frm)
out = ("frame_%05d.png" % frm)
print(out)
call (["openscad.exe", "--imgsize=640,480", cam, "cube_without_corners2.scad", "--render", "-o", out])
Graphviz/dot
1
2
3
dot net-cf.dot -Tpng -O
dot net-cf.dot -Tsvg -O
dot world_to_screen_coordinates4.dot -O -Tpng -Gdpi=300