Testing Image Compression and Resizing Features
Image pipelines (resize, compress, convert, strip metadata) look simple and fail in specific, repeatable ways: photos rotated sideways, transparent logos on black backgrounds, location data left in "anonymised" images, a single huge image that exhausts a worker's memory. Each failure has a matching input file. This post lists the inputs we use, how to make each one, and what to check in the output.
Two kinds of test image
It helps to separate the inputs by what they test:
- Size and type inputs check the upload rules: maximum file size, accepted formats. Content does not matter. The padded JPG, PNG, GIF and WebP files from our generator are built for this; they are valid images at exact byte counts (for example a JPG one byte over 10 MiB), but their pixel dimensions are tiny.
- Content inputs check the processing: orientation, colour, transparency, dimensions. These need real image properties, so we build them with ImageMagick and exiftool, or use real photos.
Do not use a padded dummy image to judge resize quality; a 1x1 image scaled down is still 1x1.
The content inputs
1. EXIF orientation
Phone cameras often store pixels in sensor orientation and add an EXIF Orientation tag (values 1 to 8) telling viewers how to rotate. A resizer that ignores the tag and also strips metadata produces a sideways image.
# landscape image with text so rotation is obvious
magick -size 1200x800 xc:white -pointsize 120 -gravity center -annotate 0 "TOP" orient.jpg
# tag it as "rotate 90 CW to display"
exiftool -Orientation=6 -n -overwrite_original orient.jpg
Pass: the processed image shows "TOP" upright, and either it has no Orientation tag or the tag is 1. Fail: sideways text, or upright pixels with the tag still set to 6 (some viewers then rotate it twice).
2. GPS and other metadata
exiftool -GPSLatitude=51.5 -GPSLatitudeRef=N -GPSLongitude=0.12 -GPSLongitudeRef=W \
-Artist="Test Person" -overwrite_original gps.jpg
# after processing
exiftool -a -G1 -gps:all -Artist processed.jpg
Pass: no GPS fields (and no personal fields you intend to remove) in every variant you serve: original, thumbnails and any CDN derivatives. Check each one; it is common for thumbnails to be clean while the "original" download still carries location data.
3. Transparency
magick -size 600x600 xc:none -fill "#e11" -draw "circle 300,300 300,80" alpha.png
Pass: PNG and WebP outputs keep the alpha channel; JPEG outputs (which cannot store transparency) are flattened onto white or your chosen background. Fail: a black square behind the circle, which happens when alpha is simply discarded.
4. CMYK JPEG
Print workflows produce CMYK JPEGs. Browsers display them, but many resizers convert them naively, producing oversaturated or inverted colours.
magick rose: -resize 800x -colorspace CMYK cmyk.jpg
identify -format "%[colorspace]\n" cmyk.jpg # CMYK
Pass: output is sRGB and looks like the input when viewed side by side.
5. Wide-gamut colour profiles
Recent phones save photos with a Display P3 ICC profile. Stripping the profile without converting to sRGB makes colours look duller. Use a real phone photo for this one. Pass: output either carries an sRGB profile after conversion, or keeps the original profile.
6. Very large dimensions
File size and pixel count are different limits. A PNG of a single colour can be small on disk and enormous in memory once decoded: a 20,000 x 20,000 image is 400 million pixels, about 1.6 GB at 4 bytes per pixel.
magick -size 20000x20000 xc:white huge.png
ls -l huge.png # small on disk, because a single colour compresses well
Pass: the upload is rejected with a clear message based on pixel dimensions, read from the header before full decoding. Libraries have guards for this (Pillow's Image.MAX_IMAGE_PIXELS, sharp's limitInputPixels); make sure yours is set deliberately rather than left at a default nobody chose.
7. Animation
magick -delay 50 -size 200x200 xc:red xc:green xc:blue -loop 0 anim.gif
Decide what should happen (keep animation, or take the first frame) and test for it. An animated GIF or WebP that becomes a single frame is fine if intended; one that becomes a corrupted file is not.
8. Progressive JPEG and 16-bit PNG
magick orient.jpg -interlace Plane progressive.jpg
magick alpha.png -depth 16 deep.png
Both are valid and less common; older or hand-rolled decoders sometimes fail on them.
Checking the output
For every input, record dimensions, format, file size and metadata of each output variant:
identify -format "%f %wx%h %m %b %[colorspace]\n" out/*
exiftool -a -G1 -Orientation -gps:all out/*
For compression quality, compare the output with a reference resized version using a similarity metric instead of eyeballing:
magick reference.png processed.png -compare -metric SSIM -format "%[distortion]\n" info:
Record the score for a known-good build and fail the test if a change drops it noticeably. Pick the threshold from your own images; there is no universal number.
A reference implementation to compare against
If you are building the pipeline yourself, sharp in Node.js handles several of these cases with explicit calls:
import sharp from 'sharp';
await sharp('in.jpg', { limitInputPixels: 50_000_000 })
.rotate() // apply EXIF orientation
.resize({ width: 1600, withoutEnlargement: true })
.flatten({ background: '#ffffff' }) // only for JPEG output
.toColourspace('srgb')
.jpeg({ quality: 80, mozjpeg: true })
.toFile('out.jpg'); // metadata is dropped by default
Whatever library you use, run the same input set through it and compare.
Size and type rules
Finally, the upload rules in front of the pipeline: files just under and just over the size limit, for each accepted format, and a renamed file (a PNG named .jpg). Generated files are ideal here; see JPG, PNG, WebP and GIF for format details, or pick a standard size from /download, for example /download/10mb-jpg.
Checklist
- Orientation tag 6 image comes out upright.
- No GPS or personal metadata in any served variant.
- Transparency kept, or flattened onto the intended background.
- CMYK and wide-gamut inputs come out as correct sRGB.
- Huge pixel dimensions rejected before full decode.
- Animation handled as designed.
- Quality score recorded and tracked.
- Size limits tested at exact byte counts for each format.
