5.5 KiB
id, title, type, created_date, tags
| id | title | type | created_date | tags | |||||
|---|---|---|---|---|---|---|---|---|---|
| doc-29 | Lossless Vix/libvips compression options for asset endpoints | other | 2026-05-27 08:28 |
|
Lossless Vix/libvips compression options for asset endpoints
Scope
This note covers image assets served through MusicLibraryWeb.AssetController:
/assets/:transform_payload/public/assets/:transform_payload/api/v1/assets/:transform_payload
Static compiled files under /assets/app.css, /assets/app.js, etc. do not use Vix/libvips. They are served by Plug.Static, with gzip enabled in production when code reloading is disabled.
Current behaviour
MusicLibrary.Assets.Image currently writes transformed images with Vix defaults:
Image.write_to_buffer(image, ".jpg")
Image.write_to_buffer(image, ".webp")
Relevant libvips defaults:
- JPEG output uses lossy JPEG encoding (
Q: 75by default). - WebP output is lossy unless
lossless: trueis set. - Saver metadata defaults keep EXIF, XMP, IPTC, ICC, and other metadata.
AssetController.pick_format/1chooses WebP when the requestAcceptheader containsimage/webp; otherwise it chooses JPEG.
Strictly lossless compression is therefore not possible for all current dynamic asset responses without changing the fallback format strategy, because JPEG saves are inherently lossy.
Important constraint: resize vs compression
Width transforms use Operation.thumbnail_buffer/2, which resamples pixels. Resizing is not bit-for-bit preservation of the original asset. The flags below avoid additional lossy codec compression of the transformed pixels; they do not make resizing itself reversible.
For untransformed assets, serving the original stored bytes is the only bit-for-bit lossless option.
Useful non-lossy flags
WebP lossless
Best candidate for browser-targeted lossless compression:
Image.write_to_buffer(image, ".webp",
lossless: true,
effort: 6,
keep: [:VIPS_FOREIGN_KEEP_ICC]
)
Notes:
lossless: trueis the required flag.effort: 6is the maximum WebP compression effort documented by libvips; it trades CPU time for smaller output.keep: [:VIPS_FOREIGN_KEEP_ICC]strips EXIF/XMP/IPTC/other metadata while retaining the ICC colour profile. If metadata must also be preserved, omitkeepor use[:VIPS_FOREIGN_KEEP_ALL].- Avoid
near-lossless: true; despite the name, it preprocesses pixels and is not strictly lossless. - Avoid
target-size,mixed, lowalpha-q, or lossy WebP presets when strict losslessness is required.
PNG lossless
Suitable as a strict lossless fallback when WebP is unavailable:
Image.write_to_buffer(image, ".png",
compression: 9,
filter: [:VIPS_FOREIGN_PNG_FILTER_ALL],
keep: [:VIPS_FOREIGN_KEEP_ICC]
)
Notes:
compression: 9increases DEFLATE effort without changing pixels.filter: [:VIPS_FOREIGN_PNG_FILTER_ALL]enables adaptive PNG row filters, which are lossless and often reduce size.- Avoid
palette: true, loweringbitdepth,Q, ordither; those can quantize or otherwise change pixel data.
JPEG: only metadata/Huffman optimisation is safe, but the result is still JPEG-lossy
If JPEG output remains necessary, the only useful non-quality-oriented saver option is Huffman table optimisation:
Image.write_to_buffer(image, ".jpg",
"optimize-coding": true,
keep: [:VIPS_FOREIGN_KEEP_ICC]
)
Optional:
interlace: true
Notes:
"optimize-coding": truecomputes optimal Huffman tables. It can reduce file size without intentionally changing quality settings.interlace: truecreates progressive JPEGs. This is not inherently lossy, but may increase size for small thumbnails.- This does not make JPEG output lossless. JPEG encoding remains lossy.
- Avoid changing
Q,"subsample-mode",trellis-quant,quant-table, or similar JPEG options for this requirement; they affect quality/compression tradeoffs.
Thumbnail generation
This avoids upscaling small source images:
Operation.thumbnail_buffer(cover_data, size,
size: :VIPS_SIZE_DOWN
)
Notes:
:VIPS_SIZE_DOWNprevents enlargement when the requested width is larger than the source.- This can reduce bytes and avoid interpolation blur.
- It changes current semantics for requests that ask for a width larger than the stored image.
AVIF/HEIF note
libvips also exposes heifsave_buffer/2 with lossless: true, and AVIF can be selected by an .avif suffix or HEIF compression options. Example shape:
Image.write_to_buffer(image, ".avif",
lossless: true,
effort: 9,
keep: [:VIPS_FOREIGN_KEEP_ICC]
)
This may be useful later, but it requires adding AVIF negotiation and checking production libvips/libheif codec support. WebP lossless is the safer near-term option.
Recommendation
For strict non-lossy compression of dynamic image responses:
- Use lossless WebP for clients that accept WebP.
- Use PNG as the non-WebP fallback if strict losslessness is required.
- Do not use JPEG for strict lossless transformed output. JPEG can only be mildly optimised with
"optimize-coding": true; it remains lossy. - Keep ICC metadata unless colour-profile changes are acceptable.
- Consider
size: :VIPS_SIZE_DOWNfor thumbnails if preventing upscaling is acceptable.
A conservative implementation would add format-specific saver options in MusicLibrary.Assets.Image, then change AssetController.pick_format/1 so the strict-lossless path returns either image/webp with lossless: true or image/png, not JPEG.