Post

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.

ENG | Image Processing Cheat sheet for Linux

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
This post is licensed under CC BY 4.0 by the author.