diff --git a/README.md b/README.md
index 9dfe6a4..bcc01dc 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,11 @@ kill every wine process including processes hanging
### webptogif.sh
convert animated .webp at $1 (path) to a gif
+### image_compress.sh
+Usage: `./image_compress.sh `
+Compress an image until it's below a specified target size. Output format is JPEG, regardless of the input format.
+(requires ImageMagick)
+
### convert_md_to_pdf.sh
(need to be used with listings-setup.tex in the same folder, xelatex and pandoc are required)
convert infile.md in the current folder to a pdf file (outfile.pdf) with code blocks in red boxes.
diff --git a/image_compress.sh b/image_compress.sh
new file mode 100755
index 0000000..938242d
--- /dev/null
+++ b/image_compress.sh
@@ -0,0 +1,68 @@
+#!/bin/bash
+
+input="$1"
+output="$2"
+target_size=$3
+
+if [[ -z "$input" || -z "$output" || -z "$target_size" ]]; then
+ echo "Usage: $0 input output.jpg "
+ exit 1
+fi
+
+compress_and_get_size() {
+ local q=$1
+ magick "$work" -strip -interlace Plane -quality "$q" "$output"
+ stat -c%s "$output"
+}
+
+
+work=$(mktemp --suffix=.jpg)
+cp "$input" "$work"
+
+magick "$input" \
+ -auto-orient \
+ -colorspace sRGB \
+ -strip \
+ -background white -alpha remove -alpha off \
+ "$work"
+
+echo "Trying to fit below $target_size bytes"
+
+while true; do
+
+ # Binary search
+ low=1
+ high=95
+ best_q=1
+
+ while (( low <= high )); do
+ mid=$(((low + high) / 2))
+ size=$(compress_and_get_size "$mid")
+
+ echo " Quality $mid -> $size bytes"
+
+ best_size=$size
+
+ if (( size <= target_size )); then
+ best_q=$mid
+ low=$((mid + 1))
+ else
+ high=$((mid - 1))
+ fi
+ done
+
+ echo "Best quality this round: $best_q ($best_size bytes)"
+
+ if (( best_size <= target_size )); then
+ echo "DONE: $output is $best_size bytes"
+ break
+ fi
+
+ echo " Reducing resolution"
+ tmp=$(mktemp --suffix=.jpg)
+
+ magick "$work" -resize 75% -quality 85 "$tmp"
+ mv "$tmp" "$work"
+done
+
+rm -f "$work"