image utility
Flag: 📍 Modified: February 14, 2026 7:58 PM Category: Coding Notes List: Work Created: November 20, 2025 6:10 PM Master Type: Notes Hide: No Starred: No Status: Unassigned
#!/bin/bash
# ImageTool.command — Simple batch image utility with folder drag-drop
# Includes: convert, resize, rotate, compress %, compress target size,
# zip (standard/target/split), thumbnails, batch rename.
shopt -s nullglob
SUPPORTED_EXT="psd PSD tif TIF tiff TIFF png PNG jpg JPG jpeg JPEG gif GIF bmp BMP"
##############################################
# FOLDER SELECTION (drag-and-drop aware)
##############################################
if [ -n "$1" ]; then
WORKDIR="$1"
else
WORKDIR=$(osascript <<EOF
set f to choose folder with prompt "Select a folder of images:"
POSIX path of f
EOF
)
fi
[ -d "$WORKDIR" ] || { echo "Folder not found."; exit 1; }
##############################################
# FILE SCAN
##############################################
scan_files() {
FILES=()
for ext in $SUPPORTED_EXT; do
for f in "$WORKDIR"/*.$ext; do FILES+=("$f"); done
done
}
scan_files
[ ${#FILES[@]} -gt 0 ] || { echo "No supported images found."; exit 1; }
##############################################
# MENU
##############################################
menu() {
clear
echo "==========================================="
echo " IMAGE TOOL"
echo "Folder: $WORKDIR"
echo "Files: ${#FILES[@]}"
echo "==========================================="
echo "1. Convert"
echo "2. Resize"
echo "3. Rotate"
echo "4. Compress"
echo "5. Zip"
echo "6. Thumbnails"
echo "7. Batch Rename"
echo "8. Quit"
echo "-------------------------------------------"
printf "Choose option: "
}
##############################################
# UTILITIES
##############################################
parse_size() {
local s="$1"
case "$s" in
*m|*M) echo $(( ${s%?} * 1024 * 1024 ));;
*k|*K) echo $(( ${s%?} * 1024 ));;
*) echo "$s";;
esac
}
png_lossy() {
local f="$1"
local out="$2"
local q="$3"
local colors=$(( 256 - (q * 2) ))
if [ $colors -lt 16 ]; then colors=16; fi
magick "$f" -colors "$colors" -define png:compression-level=9 "$out"
}
##############################################
# IMAGE OPERATIONS
##############################################
convert_menu() {
printf "Convert to format (psd/tif/png/jpg/gif): "
read -r OUTEXT
for f in "${FILES[@]}"; do
base="${f%.*}"
magick "$f" "$base.$OUTEXT"
done
scan_files
}
resize_menu() {
echo "1 = by width"
echo "2 = by height"
read -r mode
if [ "$mode" = "1" ]; then
printf "Width px: "
read -r W
geom="${W}x"
else
printf "Height px: "
read -r H
geom="x$H"
fi
for f in "${FILES[@]}"; do
base="${f%.*}"
magick "$f" -resize "$geom" "$base"_resized."${f##*.}"
done
}
rotate_menu() {
echo "1 = Landscape"
echo "2 = Portrait"
echo "3 = 90° CW"
echo "4 = 90° CCW"
echo "5 = 180°"
read -r opt
for f in "${FILES[@]}"; do
out="${f%.*}_rot.${f##*.}"
case "$opt" in
1)
w=$(magick identify -format "%w" "$f")
h=$(magick identify -format "%h" "$f")
if [ "$h" -gt "$w" ]; then magick "$f" -rotate 90 "$out"; else cp "$f" "$out"; fi
;;
2)
w=$(magick identify -format "%w" "$f")
h=$(magick identify -format "%h" "$f")
if [ "$w" -gt "$h" ]; then magick "$f" -rotate 90 "$out"; else cp "$f" "$out"; fi
;;
3) magick "$f" -rotate 90 "$out" ;;
4) magick "$f" -rotate -90 "$out" ;;
5) magick "$f" -rotate 180 "$out" ;;
esac
done
}
##############################################
# COMPRESSION (% and TARGET-SIZE)
##############################################
compress_percent() {
local Q="$1"
for f in "${FILES[@]}"; do
base="${f%.*}"
out="$base"_compressed."${f##*.}"
case "${f##*.}" in
png|PNG) png_lossy "$f" "$out" "$Q" ;;
*) magick "$f" -quality "$Q" "$out" ;;
esac
done
}
compress_target() {
printf "Target size (ex: 1m, 900k, 1500k): "
read -r TARGET_RAW
TARGET=$(parse_size "$TARGET_RAW")
TOL_PCT=5
for f in "${FILES[@]}"; do
ext="${f##*.}"
base="${f%.*}"
out="$base"_target."$ext"
low_q=5
high_q=95
best_q=$high_q
while [ $low_q -le $high_q ]; do
mid_q=$(( (low_q + high_q) / 2 ))
tmp="$base"_tmp."$ext"
if [[ "$ext" == "png" || "$ext" == "PNG" ]]; then
png_lossy "$f" "$tmp" "$mid_q"
else
magick "$f" -quality "$mid_q" "$tmp"
fi
size=$(stat -f%z "$tmp")
low_limit=$(( TARGET - (TARGET * TOL_PCT / 100) ))
high_limit=$(( TARGET + (TARGET * TOL_PCT / 100) ))
if [ $size -ge $low_limit ] && [ $size -le $high_limit ]; then
mv "$tmp" "$out"
break
fi
if [ $size -gt $TARGET ]; then
low_q=$(( mid_q + 1 ))
else
best_q=$mid_q
high_q=$(( mid_q - 1 ))
fi
rm -f "$tmp"
done
if [ ! -f "$out" ]; then
if [[ "$ext" == "png" || "$ext" == "PNG" ]]; then
png_lossy "$f" "$out" "$best_q"
else
magick "$f" -quality "$best_q" "$out"
fi
fi
done
}
compress_menu() {
echo "1 = Quality %"
echo "2 = Target File Size"
read -r mode
if [ "$mode" = "1" ]; then
printf "Quality %: "
read -r Q
compress_percent "$Q"
else
compress_target
fi
}
##############################################
# ZIP ROUTINES (cleaned)
##############################################
zip_standard() {
printf "Zip filename: "
read -r ZNAME
zip -9 -r "$ZNAME.zip" "$WORKDIR"
}
zip_target() {
printf "Target zip size (e.g. 50m, 200m): "
read -r TARGET_RAW
TARGET=$(parse_size "$TARGET_RAW")
tmpdir=$(mktemp -d)
basezip="$tmpdir/base.zip"
zip -9 -r "$basezip" "$WORKDIR" >/dev/null
original=$(stat -f%z "$basezip")
BEST_ZIP="./zip_target.zip"
BEST_DIFF=$(( original - TARGET ))
cp "$basezip" "$BEST_ZIP"
methods=("deflate" "bzip2" "lzma")
for m in "${methods[@]}"; do
tmp="$tmpdir/m_$m.zip"
zip -Z "$m" -9 -r "$tmp" "$WORKDIR" 2>/dev/null || continue
zsize=$(stat -f%z "$tmp")
diff=$(( zsize > TARGET ? zsize - TARGET : TARGET - zsize ))
if [ $diff -lt $BEST_DIFF ]; then
BEST_DIFF=$diff
cp "$tmp" "$BEST_ZIP"
fi
done
final=$(stat -f%z "$BEST_ZIP")
if [ $final -gt $(( TARGET * 2 )) ]; then
echo "⚠️ Could not approach target. Consider Zip + Split."
fi
rm -rf "$tmpdir"
}
zip_split() {
printf "Zip filename: "
read -r ZNAME
printf "Split size (e.g. 200m): "
read -r SIZE
zip -9 -r "$ZNAME.zip" "$WORKDIR"
split -b "$SIZE" "$ZNAME.zip" "$ZNAME.zip.part_"
}
zip_menu() {
echo "1 = Standard zip"
echo "2 = Zip to target size"
echo "3 = Zip + Split"
read -r mode
case "$mode" in
1) zip_standard ;;
2) zip_target ;;
3) zip_split ;;
esac
}
##############################################
# THUMBNAILS
##############################################
thumb_menu() {
printf "Thumbnail size px: "
read -r T
printf "Thumbnail quality: "
read -r Q
mkdir -p "$WORKDIR/thumbnails"
for f in "${FILES[@]}"; do
base=$(basename "$f")
magick "$f" -resize "${T}x${T}" -quality "$Q" \
"$WORKDIR/thumbnails/${base%.*}_thumb.jpg"
done
}
##############################################
# BATCH RENAME
##############################################
batch_rename() {
printf "Base name prefix: "
read -r BASE
printf "Start number: "
read -r START
num=$START
for f in "${FILES[@]}"; do
ext="${f##*.}"
mv "$f" "$WORKDIR/${BASE}${num}.$ext"
num=$(( num + 1 ))
done
scan_files
}
##############################################
# MAIN LOOP
##############################################
while true; do
menu
read -r choice
case "$choice" in
1) convert_menu ;;
2) resize_menu ;;
3) rotate_menu ;;
4) compress_menu ;;
5) zip_menu ;;
6) thumb_menu ;;
7) batch_rename ;;
8) exit 0 ;;
*) echo "Invalid." ;;
esac
printf "\nPress Enter to continue..."
read -r _
done