Image Pipeline
Image Pipeline — sharp and Its Relatives
Code that handles images shows up often. Resize an uploaded photo, convert formats, generate thumbnails.
1. About sharp
sharp is a Node image processing library that Lovell Fuller started in 2013. The core engine is libvips. libvips is a C library that began in the 1990s as part of an image analysis project at the National Gallery in the UK, and it is frequently praised for strong disk caching and streaming processing.
Characteristics:
- Async Promise-based API.
- Memory usage is reported to be smaller than ImageMagick.
- Supports formats by combining libwebp, libheif, libpng, libjpeg-turbo, libavif, and so on.
import sharp from 'sharp';
await sharp(input)
.rotate() // EXIF-based auto rotation
.resize({ width: 1200, withoutEnlargement: true })
.toFormat('webp', { quality: 80 })
.toFile('out.webp');
2. Pillow (Python)
Pillow is the friendly fork (2010~) of PIL (Python Imaging Library, 1995). It is the de facto standard image library on the Python side.
from PIL import Image
with Image.open(path) as im:
im.thumbnail((1200, 1200))
im.save('out.webp', quality=80)
The advantages are Python friendliness, rich filters, and a clean API. The limit is that for large images, memory and speed are reported as weaker than sharp/libvips.
3. ImageMagick, GraphicsMagick, Skia
ImageMagick is an old tool John Cristy started in 1987, offering both CLI (convert, magick) and library (MagickWand). Rich format support is the strength. Frequently raised limits include memory usage and security issues (the past ImageTragick incidents). GraphicsMagick is a 2002 fork emphasizing stability and performance.
Skia is a 2D graphics engine (2005) acquired by Google. Chrome, Android, and Flutter use it. It can also be used in Node via CanvasKit and skia-canvas. Strengths are canvas, text rendering, and vector shapes. The limit is that convenience features for general image processing (cropping, batch resizing) are sparse.
| Tool | Language | Engine | Strength | Limit |
|---|---|---|---|---|
| sharp | Node | libvips | Speed and memory | Some advanced filters are richer in ImageMagick |
| Pillow | Python | own | Friendliness and filters | Slow on large images |
| ImageMagick | CLI, multi-language | own | Formats and filters | Memory and security |
| GraphicsMagick | CLI, multi-language | ImageMagick fork | Stability | Smaller community |
| Skia | C++, bindings | own | Canvas and rendering | Small as a general image processing tool |
4. Resize algorithms
Resize is the biggest cost. Algorithm types.
- Nearest neighbor — fastest, low quality. Unsuitable except for pixel art.
- Bilinear — fast, smooth.
- Bicubic — good quality, standard.
- Lanczos — high quality, slow. Often used for downscaling.
sharp uses Lanczos3 (kernel: lanczos3) by default.
Libraries provide concepts similar to CSS's object-fit.
sharp(input).resize(400, 300, { fit: 'cover' }); // keep ratio, fill and crop
sharp(input).resize(400, 300, { fit: 'contain' }); // keep ratio, fit inside
5. Format conversion
| Format | Memo |
|---|---|
| JPEG | De facto standard for lossy compression. Carries EXIF. No alpha channel. |
| PNG | Lossless. Alpha channel. Inefficient for large photos. |
| WebP | Google, 2010. Lossy, lossless, alpha. Wide modern browser support. |
| AVIF | AOMedia, 2019. Better compression than WebP. High encoding cost. |
| JPEG XL (jxl) | Standardized 2021. Gradual adoption. Some browsers do not support it. |
| HEIF/HEIC | Apple Photos default. License and decoder support issues. |
For service-side use, photos as WebP or AVIF, icons as PNG/SVG, and JPEG fallback for compatibility-critical places is a common combination.
There is a quality factor (0~100). Empirical reports suggest visual loss on regular photos is barely noticeable between 75 and 85. AVIF achieves the same visual quality at lower numbers.
6. Build-time vs on-the-fly
Build-time — generate every variant (multiple sizes and formats) at build time as static files. The CDN serves them directly.
- Pros: zero runtime cost. 100% cache hit rate.
- Limits: longer builds. Unsuitable for user-uploaded content.
On-the-fly — convert the source at request time and cache the result.
- Pros: instant response when a new size is needed.
- Limits: first request bears conversion cost. Need to consider cache external-call blocking and signed URLs.
Hybrid — frequently used sizes pre-built, arbitrary sizes on-the-fly. The shape most commonly seen in production.
7. CDN image transformation
A trend has settled where the CDN itself provides transformation features.
| Service | Memo |
|---|---|
| Cloudflare Images | Upload plus transform plus global cache. Simple pricing. |
| Vercel Image Optimization | Combined with Next.js next/image. Dynamic transformation. |
| Imgix | The original transformation service. Powerful URL parameters. |
| Cloudinary | Transformation, DAM, AI transformations bundled. |
| AWS CloudFront + Lambda@Edge | A place to configure directly. |
The decision between a self-hosted sharp server and a CDN follows traffic, cost, operational burden, and security policy.
8. EXIF and color profiles
Capture info (GPS, camera, time) lives in EXIF. Exposing user photos as is to outside risks leaking location. Library defaults often preserve EXIF, so it is safer to remove explicitly.
sharp(input).rotate().withMetadata({ orientation: undefined }).toBuffer();
A flow that applies orientation only and removes the rest is a sound default.
iPhone photos may use P3, and some cameras may use non-standard color spaces like Adobe RGB. Converting to sRGB stabilizes color for web display.
sharp(input).toColorspace('srgb');
9. Progressive decode and responsive srcset
JPEG's progressive option and WebP's incremental option let first-screen pixels show early. There is a perceptible speed difference at the same cost.
Generate the same image at multiple resolutions and serve via <img srcset="...">. The browser picks the size that matches the device.
10. Common pitfalls
Memory blow-up — decoding very large images (tens of MP) at once causes OOM. Use sharp's streaming and limitInputPixels, plus pre-validate input size.
Missing EXIF rotation — landscape and portrait depend on the EXIF orientation. Skipping the auto-rotate call shows landscape photos as portrait.
Alpha channel loss — PNG → JPEG conversion fills transparent regions with black or white. Unintended color shifts.
Security on arbitrary input — when transforming uploads, decoder vulnerabilities become an exposure surface. Input size limits, format whitelists, isolated environments.
CPU usage — AVIF encoding is expensive. Worker and queue separation are needed for bulk conversion.
Missing cache key design — bad cache keys for on-the-fly transforms can deliver user A's variant to B. Include size, quality, format, and signature in the key.
Regeneration performance — when many clients request the same resource at once after cache expiry, conversion runs in duplicate. A distributed lock or request coalescing is needed.
Closing thoughts
Images are heavy on I/O and the format choices are many, so the start feels burdensome. Even so, sharp plus libvips covers more than 90% of places in one line. Once quality, memory, and security balance, ops becomes simple.
Next
- backup-restore
- vitest-philosophy
References: sharp official docs, libvips official, Pillow docs, WebP guide, AVIF FAQ, Cloudflare Images, Next.js Image Optimization.