Server-Side Image Processing via ImageMagick
ImageMagick — de facto standard for server-side image processing. Supports 200+ formats, complex transformations, works via CLI or libraries (Wand for Python, Imagick for PHP, imagemagick for Node.js). Used where Sharp doesn't suffice: PDF conversion, PSD, complex color profiles, vector rasterization.
Installation
# Ubuntu/Debian
apt install imagemagick libmagickwand-dev
# Alpine (Docker)
apk add imagemagick
# macOS
brew install imagemagick
# Check supported formats
convert -list format | grep -E 'WEBP|AVIF|PDF'
Python: Wand (MagickWand Binding)
pip install Wand
from wand.image import Image
from wand.color import Color
import io
def process_image(input_path: str, output_dir: str, filename: str):
with Image(filename=input_path) as img:
# Auto-orient by EXIF
img.auto_orient()
# Normalize to RGB (PDF/CMYK → sRGB)
if img.colorspace == 'cmyk':
img.transform_colorspace('srgb')
# Remove transparency before JPEG
if img.alpha_channel and filename.endswith('.jpg'):
img.background_color = Color('white')
img.alpha_channel = 'remove'
# Generate variants
sizes = {
'thumb': (150, 150, 'cover'),
'medium': (800, 600, 'inside'),
'large': (1920, 1080, 'inside'),
}
results = {}
for name, (w, h, fit) in sizes.items():
with img.clone() as variant:
if fit == 'cover':
# Resize + crop by center
variant.transform(resize=f'{w}x{h}^')
variant.gravity = 'center'
variant.extent(w, h)
else:
variant.transform(resize=f'{w}x{h}>')
variant.strip() # remove metadata
variant.compression_quality = 85
out_path = f"{output_dir}/{name}.webp"
variant.format = 'webp'
variant.save(filename=out_path)
results[name] = out_path
return results
PHP: Imagick
<?php
class ImageProcessor
{
public function process(string $inputPath, string $outputDir): array
{
$imagick = new Imagick($inputPath);
// Auto-orient and normalize
$imagick->autoOrient();
// For multi-page files (PDF, GIF) — take first frame
$imagick = $imagick->coalesceImages()->current();
// CMYK → sRGB
if ($imagick->getColorspace() === Imagick::COLORSPACE_CMYK) {
$imagick->transformImageColorspace(Imagick::COLORSPACE_SRGB);
}
$sizes = [
'thumb' => [150, 150, Imagick::GRAVITY_CENTER],
'medium' => [800, 600, null],
'large' => [1920, 1080, null],
];
$results = [];
foreach ($sizes as $name => [$w, $h, $gravity]) {
$variant = clone $imagick;
if ($gravity) {
// Thumbnail with center crop
$variant->cropThumbnailImage($w, $h);
} else {
$variant->thumbnailImage($w, $h, true); // fit inside
}
$variant->stripImage(); // remove EXIF
$variant->setImageFormat('webp');
$variant->setImageCompressionQuality(82);
$outPath = "{$outputDir}/{$name}.webp";
$variant->writeImage($outPath);
$results[$name] = $outPath;
$variant->destroy();
}
$imagick->destroy();
return $results;
}
}
PDF and PSD Conversion
Unique capability of ImageMagick — convert PDF pages to images:
from wand.image import Image
def pdf_to_images(pdf_path: str, output_dir: str, dpi: int = 150):
"""Convert PDF to JPG preview of pages"""
with Image(filename=pdf_path, resolution=dpi) as pdf:
images = pdf.sequence
for i, page in enumerate(images):
with Image(page) as img:
img.format = 'jpeg'
img.compression_quality = 85
# White background (PDF may be transparent)
img.background_color = Color('white')
img.alpha_channel = 'flatten'
img.save(filename=f"{output_dir}/page_{i+1:04d}.jpg")
return len(images)
# PSD → PNG of layers
with Image(filename='design.psd') as psd:
for i, layer in enumerate(psd.sequence):
with Image(layer) as l:
l.format = 'png'
l.save(filename=f"layer_{i}.png")
Security Policy Configuration
ImageMagick has strict default limits in /etc/ImageMagick-6/policy.xml. PDF conversion is often blocked:
<!-- /etc/ImageMagick-6/policy.xml -->
<policymap>
<!-- Resource limits -->
<policy domain="resource" name="memory" value="512MiB"/>
<policy domain="resource" name="map" value="1GiB"/>
<policy domain="resource" name="width" value="16KP"/>
<policy domain="resource" name="height" value="16KP"/>
<policy domain="resource" name="area" value="128MP"/>
<policy domain="resource" name="disk" value="2GiB"/>
<policy domain="resource" name="time" value="120"/>
<!-- Allow PDF (disabled by default for security) -->
<policy domain="coder" rights="read|write" pattern="PDF"/>
<policy domain="coder" rights="read|write" pattern="LABEL"/>
<!-- Block dangerous formats -->
<policy domain="coder" rights="none" pattern="MVG"/>
<policy domain="coder" rights="none" pattern="MSL"/>
<policy domain="delegate" rights="none" pattern="URL"/>
</policymap>
CLI: Batch Processing
# Convert directory to WebP
for f in /uploads/raw/*.jpg; do
convert "$f" \
-auto-orient \
-strip \
-resize '1920x1080>' \
-quality 82 \
"${f%.jpg}.webp"
done
# Parallel processing via GNU Parallel
find /uploads/raw -name '*.jpg' | \
parallel -j4 convert {} \
-auto-orient -strip \
-resize '800x600>' \
-quality 82 \
{.}.webp
# Add watermark
convert input.jpg \
\( watermark.png -resize 200x -alpha on -evaluate multiply 0.7 \) \
-gravity SouthEast -composite \
output.jpg
When to Choose ImageMagick over Sharp
| Task | Sharp | ImageMagick |
|---|---|---|
| JPEG/WebP/AVIF processing | Better (3–5× faster) | Yes |
| PDF → image | No | Yes |
| PSD layers | No | Yes |
| CMYK → RGB | No | Yes |
| 200+ formats | No | Yes |
| SVG rasterization | Partial | Yes |
| Memory consumption | Low | High |
Timeline
Setting up server-side processing via ImageMagick/Wand with PDF and non-standard format support — 1–2 working days.







