zenblend is a row-level pixel blending library for premultiplied linear f32 RGBA compositing pipelines.
All operations work on &mut [f32] slices -- 4 interleaved floats per pixel, alpha pre-applied, in linear light. Designed as the inner loop for zenpipe strip pipelines, not a standalone compositing engine.
Porter-Duff (12): Clear, Src, Dst, SrcOver, DstOver, SrcIn, DstIn, SrcOut, DstOut, SrcAtop, DstAtop, Xor
Artistic (20): Multiply, Screen, Overlay, Darken, Lighten, HardLight, SoftLight, ColorDodge, ColorBurn, Difference, Exclusion, LinearBurn, LinearDodge, VividLight, LinearLight, PinLight, HardMix, Divide, Subtract, Plus
32 modes total. Artistic modes unpremultiply per-pixel, apply the blend function, then re-premultiply. Plus operates directly on premultiplied data.
use zenblend::{BlendMode, blend_row, blend_row_solid, blend_row_solid_opaque};
// fg and bg are premultiplied linear f32, 4ch RGBA, equal length, divisible by 4.
// fg is modified in place to contain the blended result.
let mut fg = vec![0.5, 0.0, 0.0, 0.5, 0.0, 0.3, 0.0, 1.0];
let bg = vec![0.0, 0.3, 0.0, 1.0, 0.0, 0.0, 0.5, 0.5];
blend_row(&mut fg, &bg, BlendMode::SrcOver);
// Blend against a solid color -- no row buffer needed for background.
let mut row = vec![0.5, 0.0, 0.0, 0.5, 0.0, 0.3, 0.0, 1.0];
blend_row_solid(&mut row, &[0.2, 0.0, 0.0, 0.5], BlendMode::Multiply);
// Optimized path when the background is opaque (alpha = 1.0).
let mut row2 = vec![0.5, 0.0, 0.0, 0.5, 0.0, 0.3, 0.0, 1.0];
blend_row_solid_opaque(&mut row2, &[0.2, 0.1, 0.05, 1.0], BlendMode::SrcOver);use zenblend::mask::{RoundedRectMask, MaskSource};
use zenblend::{mask_row, mask_row_constant, mask_row_rgb, apply_mask_spans};
let mut pixels = vec![0.5, 0.0, 0.0, 0.5, 0.0, 0.3, 0.0, 1.0];
// Per-pixel mask: one f32 per pixel, broadcast to all 4 channels.
let mask_values = vec![0.8, 1.0];
mask_row(&mut pixels, &mask_values);
// Uniform opacity -- no mask buffer needed.
mask_row_constant(&mut pixels, 0.7);
// RGB-only mask: multiplies R, G, B but leaves alpha untouched.
// Use case: gain map application, vignette without opacity change.
let rgb_mask = vec![0.9, 1.0];
mask_row_rgb(&mut pixels, &rgb_mask);
// Span-optimized masking -- skips fully opaque/transparent regions.
let width = 64;
let height = 64;
let mask = RoundedRectMask::new(width, height, [10.0, 10.0, 10.0, 10.0]);
let mut row = vec![0.5f32; (width as usize) * 4];
let mut mask_buf = vec![0.0f32; width as usize];
apply_mask_spans(&mut row, &mut mask_buf, &mask, 0);Built-in masks: RoundedRectMask, LinearGradientMask, RadialGradientMask.
Implement the MaskSource trait for custom masks.
use zenblend::lerp_row;
// Per-pixel blend factor t in [0, 1]. One f32 per pixel.
// t=0 -> a, t=1 -> b.
let a = vec![1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0];
let b = vec![0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0];
let t = vec![0.5, 0.5];
let mut out = vec![0.0f32; 8];
lerp_row(&a, &b, &t, &mut out);SrcOver, masking (mask_row, mask_row_rgb), and lerp_row are SIMD-accelerated via runtime CPU dispatch through archmage:
| Platform | ISA | Pixels/iter |
|---|---|---|
| x86_64 | AVX2 + FMA | 2 |
| AArch64 | NEON | 1 |
| WASM | simd128 | 1 |
| Fallback | scalar | 1 |
Other blend modes use scalar implementations. Mask span alignment is SIMD-aware (snaps partial spans to block boundaries).
#![forbid(unsafe_code)] -- all SIMD through safe abstractions.
- All data must be premultiplied linear f32 RGBA. There is no format conversion; bring your own linearization.
- No non-separable blend modes (Hue, Saturation, Color, Luminosity).
- Only SrcOver has a dedicated SIMD fast path for blending; the other 31 modes run scalar per-pixel loops.
- Row-level API only. There is no tile, buffer, or image-level compositing -- that belongs in zenpipe.
default = ["std"]
| State of the art codecs* | zenjpeg · zenpng · zenwebp · zengif · zenavif (rav1d-safe · zenrav1e · zenavif-parse · zenavif-serialize) · zenjxl (jxl-encoder · zenjxl-decoder) · zentiff · zenbitmaps · heic · zenraw · zenpdf · ultrahdr · mozjpeg-rs · webpx |
| Compression | zenflate · zenzop |
| Processing | zenresize · zenfilters · zenquant · zenblend |
| Metrics | zensim · fast-ssim2 · butteraugli · resamplescope-rs · codec-eval · codec-corpus |
| Pixel types & color | zenpixels · zenpixels-convert · linear-srgb · garb |
| Pipeline | zenpipe · zencodec · zencodecs · zenlayout · zennode |
| ImageResizer | ImageResizer (C#) — 24M+ NuGet downloads across all packages |
| Imageflow | Image optimization engine (Rust) — .NET · node · go — 9M+ NuGet downloads across all packages |
| Imageflow Server | The fast, safe image server (Rust+C#) — 552K+ NuGet downloads, deployed by Fortune 500s and major brands |
* as of 2026
archmage · magetypes · enough · whereat · zenbench · cargo-copter
And other projects · GitHub @imazen · GitHub @lilith · lib.rs/~lilith · NuGet (over 30 million downloads / 87 packages)
Dual-licensed: AGPL-3.0 or commercial.
I've maintained and developed open-source image server software -- and the 40+ library ecosystem it depends on -- full-time since 2011. Fifteen years of continual maintenance, backwards compatibility, support, and the (very rare) security patch. That kind of stability requires sustainable funding, and dual-licensing is how I make it work without venture capital or rug-pulls. Support sustainable and secure software; swap patch tuesday for patch leap-year.
Your options:
- Startup license -- $1 if your company has under $1M revenue and fewer than 5 employees. Get a key
- Commercial subscription -- Governed by the Imazen Site-wide Subscription License v1.1 or later. Apache 2.0-like terms, no source-sharing requirement. Sliding scale by company size. Pricing & 60-day free trial
- AGPL v3 -- Free and open. Share your source if you distribute.
See LICENSE-COMMERCIAL for details.