Media Imagemagick
Context: $ARGUMENTS
Quick start
- Resize / thumbnail: → Step 2
resize - Change format (PNG→JPG/WebP/HEIC): → Step 2
convert - Overlay logo / watermark: → Step 2
composite - Contact-sheet grid: → Step 2
montage - PDF → PNGs, GIF → frames: → Step 2 format-specific recipes
- Probe dimensions / metadata: → Step 4
identify - Batch a folder: → Step 2
mogrify/batch-resize
When to use
- Resize / compress / re-encode one image or a whole folder.
- Convert between JPEG, PNG, WebP, HEIC, AVIF, JXL, TIFF, GIF, BMP, ICO, PDF, PSD.
- Compose / watermark / annotate / make contact sheets or social-media multi-size bundles.
- Script-level image plumbing where Photoshop-class apps are overkill and plain Python PIL is too limited.
Not for: raster painting (use Krita), RAW development (use darktable/RawTherapee), video (use ffmpeg — see ffmpeg-* skills).
Step 1 — Install + verify
# macOS
brew install imagemagick
# Debian/Ubuntu
sudo apt install imagemagick
# Verify. On v7 the command is `magick`. v6 uses `convert`/`mogrify`/`composite` as separate binaries.
magick -version
magick -list format | head # enumerate supported formats on this build
magick -list format | grep -Ei '^ *(HEIC|AVIF|JXL|WEBP) ' # confirm optional formats
- v7+ is
magick <subcommand>(e.g.magick convert,magick identify,magick mogrify,magick composite,magick montage,magick compare). Bareconvertstill works on v7 (aliased), but ALWAYS prefermagick—convertalso collides with a Windows built-in. - HEIC / AVIF / JXL support depends on the build shipping with libheif / libavif / libjxl. If missing, rebuild from source or use a Homebrew tap / apt backport that bundles them.
- ImageMagick ships with a security policy at
/etc/ImageMagick-7/policy.xml(or/opt/homebrew/etc/ImageMagick-7/policy.xmlon macOS) that by default can blockPDF,MVG,LABEL,HTTPS, and set low memory/disk limits. If you seenot authorizederrors, edit that file or run with-define registry:temporary-path=.../--security-policyoverride.
Step 2 — Pick your operation
All recipes below are v7 magick form. Chain multiple ops by listing them in order; ImageMagick applies left-to-right.
Format conversion
magick in.png out.jpg # format decided by extension
magick in.heic out.webp
magick in.tif -quality 85 out.jpg # JPEG quality 85
magick in.png -define webp:lossless=true out.webp
magick in.jpg -define heic:speed=2 out.heic # slower = smaller
magick in.jpg -strip -interlace Plane out.jpg # strip EXIF + progressive JPEG (web-friendly)
Resize / thumbnail / fit
Geometry suffix decides behavior — see references/imagemagick.md for the full matrix.
magick in.jpg -resize 1920x1080 out.jpg # fit inside, keep aspect
magick in.jpg -resize 1920x1080! out.jpg # force exact, ignore aspect (distorts)
magick in.jpg -resize 1920x1080^ -gravity center -extent 1920x1080 out.jpg # fill + center-crop
magick in.jpg -resize '1920x1080>' out.jpg # shrink only if larger
magick in.jpg -resize 50% out.jpg # percent
magick in.jpg -thumbnail 200x200 out.jpg # -thumbnail = -resize + -strip, much faster
Crop / rotate / orient
magick in.jpg -crop 800x600+100+50 +repage out.jpg # WxH+X+Y from top-left; +repage clears virtual canvas
magick in.jpg -rotate 90 out.jpg
magick in.jpg -rotate '90>' out.jpg # only if wider than tall (quirk: use sparingly)
magick in.jpg -auto-orient out.jpg # apply EXIF Orientation and clear tag
magick in.jpg -flop out.jpg # horizontal mirror
magick in.jpg -flip out.jpg # vertical mirror
Text / annotate
magick in.jpg -font DejaVu-Sans -pointsize 72 -fill white \
-gravity SouthEast -annotate +20+20 '© 2026' out.jpg
magick in.jpg -pointsize 48 -fill 'rgba(255,255,255,0.8)' \
-draw "text 10,100 'Hello'" out.jpg
magick -list font | head # discover available fonts (fontconfig on Linux, system on macOS)
Unicode glyphs need a font that ships them — Arial often lacks CJK/emoji; prefer DejaVu-Sans, Noto-Sans-CJK, or Apple-Color-Emoji depending on the glyph set.
Composite / overlay / watermark
magick base.png logo.png -geometry +10+10 -composite out.png # top-left @ 10,10
magick base.png logo.png -gravity SouthEast -geometry +20+20 -composite out.png
magick base.png \( logo.png -alpha set -channel A -evaluate set 40% \) \
-gravity SouthEast -geometry +20+20 -composite out.png # 40% opacity watermark
Montage / contact sheet
magick montage *.jpg -geometry 200x200+5+5 -tile 4x4 grid.jpg
magick montage -label '%f' *.jpg -geometry 200x200+5+5 -tile 4x4 \
-background '#222' -fill white grid.jpg # labeled
Color / tone
magick in.jpg -modulate 110,120,100 out.jpg # brightness, saturation, hue (100 = no change)
magick in.jpg -level 10%,90% out.jpg # black point / white point stretch
magick in.jpg -brightness-contrast 10x20 out.jpg # +10 brightness, +20 contrast
magick in.jpg -colorspace Gray out.jpg # desaturate
magick in.jpg -profile sRGB.icc out.jpg # embed / convert ICC profile
Animated GIF ↔ frames
magick in.gif -coalesce frame_%04d.png # explode (keep full frames)
magick -delay 10 -loop 0 frame_*.png -layers Optimize out.gif # assemble + optimize
PDF ↔ images
magick -density 300 in.pdf page_%03d.png # ALWAYS put -density BEFORE -input for vector/PDF
magick page_*.png out.pdf # images → multi-page PDF
If you get attempt to perform an operation not allowed by the security policy 'PDF', edit policy.xml (see Gotchas).
In-place batch (mogrify)
mogrify -resize 1080 *.jpg # OVERWRITES originals
mogrify -path thumbs/ -resize 400x400 -quality 82 *.jpg # safer: write into thumbs/
Or from Python via scripts/image.py batch-resize.
Step 3 — Run
Prefer the Python wrapper for repeatable, scriptable work — it echoes the exact magick command and supports --dry-run.
python3 ${CLAUDE_SKILL_DIR}/scripts/image.py check
python3 ${CLAUDE_SKILL_DIR}/scripts/image.py resize --input in.jpg --output out.jpg --width 1920 --fit inside --quality 85
python3 ${CLAUDE_SKILL_DIR}/scripts/image.py batch-resize --indir photos/ --outdir thumbs/ --width 1080 --verbose
python3 ${CLAUDE_SKILL_DIR}/scripts/image.py identify --input in.jpg
For one-off exploration, run magick directly. For anything you will repeat, put it behind a subcommand in scripts/image.py.
Step 4 — Verify with identify
magick identify in.jpg # brief
magick identify -format "%w x %h %m Q=%Q %[colorspace]\n" in.jpg # scriptable one-liner
magick identify -verbose in.jpg | less # full EXIF / channels / histogram
magick identify -format "%[EXIF:*]" in.jpg # just EXIF
magick compare -metric RMSE a.png b.png null: # pixel-diff two images (lower = more similar)
Always identify the output after a transform to confirm dimensions, colorspace, and quality landed where you expected.
Reference docs
- Read
references/imagemagick.mdfor the full geometry / gravity / format / color-op / security-policy cheat-sheet and recipe book.
Gotchas
- v7 vs v6 commands. v7 uses the unified
magickbinary (magick convert …,magick identify …). v6 exposesconvert,mogrify,composite,identify,montage,compareas separate binaries. On v7, bareconvertstill works but is deprecated and collides with a Windows system tool — ALWAYS usemagick. - Security policy blocks formats.
/etc/ImageMagick-7/policy.xml(or the Homebrew path) restrictsPDF,PS,EPS,MVG,LABEL,HTTPS, and caps memory/disk.not authorizederrors come from here. Either edit the policy (comment out the offending<policy domain="coder" rights="none" pattern="PDF" />line) or pass--security-policy /path/to/loose.xml. - Geometry suffix matters.
100x100(fit inside),100x100!(force exact — distorts),100x100^(fill, larger of the two),100x100>(shrink only if larger),100x100<(enlarge only if smaller),50%(scale). Mix up!and^and you either stretch or crop by accident. - Coordinate system is top-left origin.
+X+Yin-crop,-geometry,-annotate,-drawoffsets are from top-left.-gravityre-anchors which corner offsets are measured from (NorthWest default). mogrifyoverwrites originals in place. ALWAYS pass-path outdir/to redirect, or back up first. This has eaten people's photo libraries.-densitymust come BEFORE the input for vector/PDF.magick -density 300 in.pdf out.pngrasterizes at 300 DPI. Put it after and it's a no-op on the input.-stripdeletes all EXIF/XMP/IPTC. Great for web (privacy + filesize), terrible if the client needs camera metadata.-thumbnailimplicitly strips.-interlace Plane= progressive JPEG / interlaced PNG. Smaller perceived-load times on the web; slightly larger file.- Fonts need fontconfig on Linux.
-font Arialmay silently fall back if Arial isn't installed.magick -list fontshows what's usable. On macOS, system fonts work; on Alpine/Docker, installfontconfig+ttf-dejavu. - Unicode / emoji rendering. Default font won't render CJK or emoji. Use
-font Noto-Sans-CJK-Regular,-font Apple-Color-Emoji, or a font that covers your glyphs, or text falls back to tofu boxes. - HEIC/AVIF/JXL are optional. Build must include libheif / libavif / libjxl. Check with
magick -list format | grep HEIC. Homebrew's build includes them; Debian's defaultimagemagickdoes not always. - Q8 vs Q16 build. Q8 stores 8 bits/channel internally; Q16 stores 16. For HDR, ICC profile-accurate work, or 16-bit TIFF / PNG, install the Q16 build (
brew install imagemagickships Q16 HDRI by default; checkmagick -version). - Memory limits. Large images (huge PSDs, giant PDFs) can exhaust policy-defined memory limits and spill to disk. Raise with
export MAGICK_MEMORY_LIMIT=8GiB MAGICK_MAP_LIMIT=16GiB MAGICK_DISK_LIMIT=32GiBor edit policy.xml. - Parallel batch. ImageMagick itself is mostly single-threaded per image. Use GNU
parallelorxargs -Pfor folder-level parallelism:find . -name '*.jpg' | parallel magick {} -resize 1080 out/{/.}.jpg. +repageafter crop.-cropleaves a "virtual canvas" so the cropped piece remembers its original offset — multi-page formats or GIFs will act weird. Append+repageto clear it.
Examples
Example 1 — Web thumbnail batch
Input: photos/*.jpg full-res DSLR output.
Run:
mkdir -p thumbs
mogrify -path thumbs/ -thumbnail 400x400^ -gravity center -extent 400x400 \
-quality 82 -strip -interlace Plane photos/*.jpg
Result: 400×400 center-cropped JPEGs, EXIF stripped, progressive, in thumbs/.
Example 2 — Watermarked social bundle
Input: hero.jpg, logo.png.
Run:
for size in 1080x1080 1080x1350 1920x1080 1200x630; do
magick hero.jpg -resize "${size}^" -gravity center -extent "$size" \
\( logo.png -resize 10% \) -gravity SouthEast -geometry +24+24 -composite \
"hero_${size}.jpg"
done
Result: one square (IG feed), one portrait (IG/TikTok), one landscape (YouTube thumb), one OG-image (og:image), each watermarked bottom-right.
Example 3 — Render a PDF for review
magick -density 200 report.pdf -background white -alpha remove -alpha off report_%03d.png
Result: one PNG per page at 200 DPI, flattened over white (prevents transparent pages from looking black).
Troubleshooting
Error: attempt to perform an operation not allowed by the security policy 'PDF'
Cause: ImageMagick's policy.xml blocks PDF (and PS/EPS) by default.
Solution: Edit /etc/ImageMagick-7/policy.xml (or $(brew --prefix)/etc/ImageMagick-7/policy.xml), comment out <policy domain="coder" rights="none" pattern="PDF" />. Or run with --security-policy /path/to/loose.xml pointing at an edited copy.
Error: no decode delegate for this image format 'HEIC'
Cause: Build lacks libheif. Check with magick -list format | grep HEIC.
Solution: brew reinstall imagemagick on macOS (Homebrew builds with libheif). On Debian, install imagemagick from bookworm-backports or build from source with --with-heic.
Error: convert: unable to read font or output has wrong font
Cause: Named font not installed / fontconfig cache stale.
Solution: magick -list font to see what's available. Install the font (macOS: drop into ~/Library/Fonts; Linux: apt install fonts-dejavu then fc-cache -fv). Then reference by the name magick -list font prints (e.g. DejaVu-Sans, not DejaVu Sans.ttf).
Error: cache resources exhausted / width or height exceeds limit
Cause: policy.xml memory/disk/area caps too low for your image (common with huge PSDs, scanned TIFFs, multi-page PDFs).
Solution: Edit policy.xml <policy domain="resource" name="memory" value="…"/> entries upward, or set env vars MAGICK_MEMORY_LIMIT, MAGICK_DISK_LIMIT, MAGICK_AREA_LIMIT, MAGICK_WIDTH_LIMIT, MAGICK_HEIGHT_LIMIT.
Error: output image looks stretched after -resize
Cause: Used ! suffix (force exact) instead of default fit-inside.
Solution: Drop the !. If you need exact WxH without distortion, use -resize WxH^ -gravity center -extent WxH (fill + crop).
Error: cropped PNG/GIF has wrong size / stray offset
Cause: -crop set a virtual canvas; writer preserved it.
Solution: Add +repage after -crop.