ImageMagick-based image processing module.
Handles image operations using ImageMagick command-line tools:
identify- Extract image metadata (dimensions, format)convert/magick- Resize and format conversion
This replaces Vix with ImageMagick, which is more widely trusted and has better long-term support.
Summary
Functions
Extract both width and height from an image file.
Get the height of an image file using ImageMagick identify.
Get the width of an image file using ImageMagick identify.
Resize an image to fit within specified dimensions.
Resize and center-crop an image to exact dimensions.
Re-encodes an image to known-good bytes, discarding everything else.
Functions
Extract both width and height from an image file.
Uses ImageMagick's identify command to extract image dimensions.
Returns:
{:ok, {width, height}}- Dimensions in pixels{:error, reason}- If extraction fails
Get the height of an image file using ImageMagick identify.
Returns the height in pixels or nil if extraction fails.
Get the width of an image file using ImageMagick identify.
Returns the width in pixels or nil if extraction fails.
Resize an image to fit within specified dimensions.
Maintains aspect ratio by scaling to fit within bounds. Optionally converts format based on output_format parameter.
Parameters:
input_path- Path to input image fileoutput_path- Path to save resized imagewidth- Target width (nil to use original)height- Target height (nil to use original)opts- Additional options:quality- JPEG quality 1-100 (default: 85):format- Output format override (jpg, png, webp, etc)
Returns:
{:ok, output_path}- Success{:error, reason}- If resize fails
Resize and center-crop an image to exact dimensions.
Zooms into the image to fill the target dimensions completely, then center-crops to extract the exact target size. No padding borders - the entire output is filled with the image content.
This is ideal for thumbnails where you want perfect squares (e.g., 150x150) with the image zoomed in and centered, no white/black borders.
The algorithm:
- Resizes image to fill the target box (scales to cover both dimensions)
- Centers the image using gravity
- Crops from center to exact target dimensions
Parameters:
input_path- Path to input image fileoutput_path- Path to save cropped imagewidth- Target width (required)height- Target height (required)opts- Additional options:quality- JPEG quality 1-100 (default: 85):format- Output format override (jpg, png, webp, etc):background- Background color (rarely used, default: "white")
Returns:
{:ok, output_path}- Success{:error, reason}- If processing fails
Re-encodes an image to known-good bytes, discarding everything else.
For uploads from people you do not trust. The point is not to inspect the file — that is a losing game — but to stop serving the uploader's bytes at all: what ends up stored is this encoder's output, produced by decoding the input and writing a fresh image. A polyglot file (valid GIF, valid JavaScript), an EXIF payload, a trailing ZIP, an embedded colour profile — none of them survive being decoded to pixels and written out again.
Also enforces a maximum edge length, because a 30000×30000 PNG is a decompression bomb whatever its byte size, and rejects anything ImageMagick cannot read as an image regardless of what the upload claimed to be.
Returns {:ok, output_path} or {:error, reason}. Never raises.
⚠️ Nothing in core calls this yet
It is a primitive, not a policy: no upload path in PhoenixKit routes
through it, so adding it did not change what any existing endpoint
stores. A caller that accepts untrusted uploads — a public portal
submission, an avatar from an unauthenticated form — has to invoke it
itself and store the OUTPUT path, discarding the original. Wiring it
into Storage.upload/* wholesale is not a drop-in: it re-encodes, which
is right for images from strangers and wrong for a designer uploading a
master PNG they expect back byte-for-byte.
Options
:max_edge— longest side in pixels (default 2500); larger images are scaled down, never up:quality— output quality (default 82):format— output format (default "jpeg"; use "png" to keep transparency)
Example
ImageProcessor.sanitize(upload.path, dest, format: "png")