zenresize
High-quality image resampling with 31 filters, streaming API, and SIMD acceleration.
Quick Start
use ;
let input = vec!; // RGBA pixels
let config = builder
.filter
.format
.build;
let output = new.resize;
assert_eq!;
Features
- 31 resampling filters (Lanczos, Mitchell, Robidoux, Ginseng, etc.)
- sRGB-aware linear-light processing for correct gamma handling
- Row-at-a-time streaming API for pipeline integration
Resizerstruct for amortizing weight computation across repeated resizes- Alpha premultiply/unpremultiply built into the pipeline
- Channel-order-agnostic: RGBA, BGRA, ARGB, BGRX all work without swizzling
- u8, u16, and f32 pixel I/O; cross-format resize (e.g., u8 in, f32 out)
no_std+alloccompatible (std optional)- SIMD-accelerated via archmage: AVX2+FMA on x86-64, NEON on ARM, WASM SIMD, scalar fallback
- Optional AVX-512 V-filter kernel (
avx512feature)
Resizer
Resizer pre-computes weight tables from the config. Reusing one across images with the same dimensions and filter saves the weight computation cost.
use ;
let config = builder
.filter
.format
.build;
let mut resizer = new;
// Allocating -- returns a new Vec<u8>
let output: = resizer.resize;
// Non-allocating -- writes into your buffer
let mut buf = vec!;
resizer.resize_into;
For pipelines that already work in linear f32:
let config = builder
.filter
.format
.build;
let mut resizer = new;
let output_f32: = resizer.resize_f32;
Cross-format resizing (u8 sRGB input, f32 linear output, or any combination):
let mut resizer = new;
let output_f32: = resizer.resize_u8_to_f32;
StreamingResize
Push input rows one at a time, pull output rows as they become available. Uses a V-first pipeline internally: the H-filter runs only out_height times (once per output row) instead of in_height times.
use ;
let config = builder
.filter
.format
.build;
let mut stream = new;
for y in 0..800
stream.finish;
// Drain remaining output rows
while let Some = stream.next_output_row
assert!;
assert_eq!;
Zero-copy output
Write output directly into an encoder's buffer:
let row_len = stream.output_row_len;
let mut enc_buf = vec!;
while stream.next_output_row_into
f32 streaming
stream.push_row_f32.unwrap;
// Or write directly into the resizer's internal buffer (saves a memcpy):
stream.push_row_f32_with.unwrap;
while let Some = stream.next_output_row_f32
ResizeConfig
All resize operations take a ResizeConfig built with the builder pattern.
use ;
let config = builder
.filter // resampling filter (default: Robidoux)
.format // sets both input and output format
.input // or set them separately
.output
.linear // resize in linear light (default)
.srgb // resize in sRGB space (faster, slight quality loss)
.resize_sharpen // sharpen during resampling (% negative lobe, default: 0)
.post_sharpen // post-resize unsharp mask (default: 0.0)
.in_stride // input row stride in elements (default: tightly packed)
.out_stride // output row stride in elements (default: tightly packed)
.build;
Defaults
If you call .build() with no other methods:
- Filter:
Robidoux - Format:
RGBA8_SRGBfor both input and output - Linear:
true(sRGB u8 -> linear f32 -> resize -> sRGB u8) - Resize sharpen:
0.0(natural filter ratio) - Post sharpen:
0.0 - Stride: tightly packed (width * channels)
Config fields
ResizeConfig fields are public (#[non_exhaustive]):
config.filter // Filter
config.in_width // u32
config.in_height // u32
config.out_width // u32
config.out_height // u32
config.input // PixelDescriptor
config.output // PixelDescriptor
config.linear // bool
config.post_sharpen // f32
config.post_blur_sigma // f32
config.kernel_width_scale // Option<f64>
config.lobe_ratio // LobeRatio
config.in_stride // usize (0 = tightly packed)
config.out_stride // usize (0 = tightly packed)
Pixel Descriptors
PixelDescriptor (from zenpixels) describes pixel format, channel layout, alpha mode, and transfer function in one value. Use the provided constants:
use PixelDescriptor;
// sRGB u8
RGBA8_SRGB // 4ch, straight alpha
RGBX8_SRGB // 4ch, no alpha (padding byte)
RGB8_SRGB // 3ch
GRAY8_SRGB // 1ch grayscale
BGRA8_SRGB // 4ch, BGR byte order
// Linear f32
RGBAF32_LINEAR
RGBF32_LINEAR
// sRGB u16
RGBA16_SRGB
RGB16_SRGB
Channel order doesn't matter. The sRGB transfer function is the same for R, G, and B, and the convolution kernels operate on N floats per pixel. Pass BGRA data as RGBA8_SRGB -- no swizzling needed. (Use BGRA8_SRGB if you want the descriptor to be semantically accurate, but the resize output is identical either way.)
Color space (.linear() / .srgb())
- Linear (default): sRGB u8 -> linear f32 -> resize -> sRGB u8. Correct on gradients, avoids darkening halos. Uses f32 intermediate buffers.
- sRGB: Resize directly in gamma space. Uses an i16 integer pipeline with 14-bit fixed-point weights for 4-channel formats. Faster; slightly incorrect on gradients; good enough for thumbnails.
Filters
31 filters covering a range of sharpness/smoothness tradeoffs:
| Filter | Category | Window | Notes |
|---|---|---|---|
Lanczos |
Sinc | 3.0 | Sharp, some ringing. Good for photos. |
Lanczos2 |
Sinc | 2.0 | Less ringing than Lanczos-3. |
Robidoux |
Cubic | 2.0 | Default. Balanced sharpness/smoothness. |
RobidouxSharp |
Cubic | 2.0 | More detail, slight ringing. |
Mitchell |
Cubic | 2.0 | Mitchell-Netravali (B=1/3, C=1/3). Balanced blur/ringing. |
CatmullRom |
Cubic | 2.0 | Catmull-Rom spline (B=0, C=0.5). |
Ginseng |
Jinc-sinc | 3.0 | Jinc-windowed sinc. Excellent for upscaling. |
Hermite |
Cubic | 1.0 | Smooth interpolation. |
CubicBSpline |
Cubic | 2.0 | Very smooth, blurs. B-spline (B=1, C=0). |
Triangle |
Linear | 1.0 | Bilinear interpolation. |
Box |
Nearest | 0.5 | Nearest neighbor. Fastest, blocky. |
Fastest |
Cubic | 0.74 | Minimal quality, maximum speed. |
Plus LanczosSharp, Lanczos2Sharp, RobidouxFast, GinsengSharp, CubicFast, Cubic, CubicSharp, CatmullRomFast, CatmullRomFastSharp, MitchellFast, NCubic, NCubicSharp, RawLanczos2, RawLanczos2Sharp, RawLanczos3, RawLanczos3Sharp, Jinc, Linear, LegacyIDCTFilter.
Sharp variants use a slightly reduced blur factor for tighter kernels. Fast variants use smaller windows.
use Filter;
let f = default; // Robidoux
let all = all; // &[Filter] -- all 31 variants
imgref Integration
Typed wrappers for the imgref + rgb crates. These accept any pixel type implementing ComponentSlice (RGBA, BGRA, etc. from the rgb crate).
use ;
use ;
use ImgVec;
use RGBA8;
let config = builder // dimensions overridden by imgref
.filter
.build;
// 4-channel: pass a PixelDescriptor to control alpha handling
let output: = resize_4ch;
// 3-channel
let output_rgb: = resize_3ch;
// Grayscale
let output_gray: = resize_gray8;
The imgref functions override the config's dimensions, formats, and stride. Filter, linear mode, and sharpen are preserved.
Feature Flags
| Feature | Default | Description |
|---|---|---|
std |
yes | Enables std library. Disable for no_std + alloc. |
layout |
yes | Layout negotiation and pipeline execution via zenlayout. |
avx512 |
no | Native AVX-512 V-filter kernel (x86-64 only). |
License
Sustainable, large-scale open source work requires a funding model, and I have been doing this full-time for 15 years. If you are using this for closed-source development AND make over $1 million per year, you'll need to buy a commercial license at https://www.imazen.io/pricing
Commercial licenses are similar to the Apache 2 license but company-specific, and on a sliding scale. You can also use this under the AGPL v3.