Pixel types for Rust image code. Keep samples, dimensions, stride and color meaning together.
Start with the pixel types guide. It goes from owning an image to borrowing a subimage, then covers color, conversion and native samples. The interactive explorer shows the bytes and runs the real converter.
This is the unpublished 0.3.1 candidate. The guide follows this source; docs.rs describes the published crate. Publication still requires release approval.
PixelBuffer owns the storage. PixelSlice borrows an image or rectangular
subimage. PixelDescriptor explains the samples.
use zenpixels::{PixelBuffer, PixelDescriptor};
let decoded = vec![255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255];
let image = PixelBuffer::from_vec(decoded, 2, 2, PixelDescriptor::RGB8_SRGB)?;
let view = image.as_slice();
let right_column = view.crop_view(1, 0, 1, 2);
assert_eq!(right_column.width(), 1);
assert_eq!(right_column.stride(), image.stride()); // Parent stride: six bytes.
assert_eq!(right_column.row(0), &[0, 255, 0]); // Only the cropped pixel.
# Ok::<(), Box<dyn std::error::Error>>(())No pixels are copied when you borrow or crop. For a fresh zero-filled image,
use PixelBuffer::try_new(width, height, descriptor).
# Storage, views and descriptions
zenpixels = "0.3.1"
# Conversion; also re-exports the core types
zenpixels-convert = "0.3.1"zenpixels describes and carries samples. zenpixels-convert changes sample
values: transfer curves, gamut, depth, layout, alpha and explicit HDR rendering.
Both support no_std + alloc and forbid unsafe code.
| Need | API |
|---|---|
| Own tightly packed decoded bytes | PixelBuffer::from_vec |
| Allocate a zero-filled image | PixelBuffer::try_new |
| Allocate aligned storage with padded rows | PixelBuffer::<(), AlignedSamples<u8>>::try_new_aligned |
| Borrow someone else's storage | PixelSlice::new |
| Borrow a rectangular subimage | crop_view |
| Borrow full-width rows | sub_rows on a view; rows on a buffer |
| Write pixels | as_slice_mut().row_mut(y) |
| Copy a view into packed owned storage | try_to_tight_buffer |
| Move storage and metadata together | into_parts / try_from_parts |
Width and height count pixels. Stride counts bytes between row starts.
row(y) returns only active pixels, including for crops. An erased byte view
requires sample-aligned row starts, not a whole number of pixels per stride.
The final row needs no trailing padding. Typed constructors also enforce their
Rust pixel type's alignment; new_typed takes its stride in pixels.
from_vec accepts tight rows. U8 storage can be adopted without a pixel copy;
wide-sample bytes are copied into typed storage when necessary for alignment.
Use try_from_parts for an explicit offset/stride and rejected-ownership
recovery. Packed rows do not necessarily imply a zero offset.
Use as_contiguous_bytes() when you specifically require packed rows; it
returns None for a strided view. Use try_contiguous_bytes() to borrow when
packed or make a fallible packed copy otherwise.
A PixelFormat describes storage and layout. A PixelDescriptor adds transfer
function, primaries, alpha and signal range. For example, RGB8_SRGB is ordinary
web RGB; RGBF32_LINEAR is linear F32 RGB. Declaring a different descriptor
does not convert samples.
Most code can use dynamic PixelBuffer and PixelSlice. With rgb, concrete
pixel types are available too, such as PixelBuffer<rgb::Rgb<u8>>. Their Rust
type establishes layout; declare the color separately. .erase() retains the
storage and metadata while dropping the concrete Rust pixel type.
Signed and wide native samples use PixelFormat::Generic, constructed by
PixelFormat::from_parts. The existing owners/views carry their exact bytes;
general signed/wide numerical conversion remains unsupported. See the
sample contract.
ColorContextdescribes the current samples, including an attached ICC or CICP and luminance anchors. An attached ICC is authoritative even if a companion disagrees or the ICC is unusable; unsupported operations refuse or preserve the original interpretation.ColorOriginkeeps original file declarations separately. Converted samples must be emitted with matching current metadata.SampleEncodingdescribes native integer code depth and bit placement.CodePlanereads U8/U16 component codes from typed planes. Ten-bit white 1023 is not ordinary full-range U16 white 65535: canonicalization is explicit.ExactDepthis evidence about the current U8/U16 code grid. Mutable access invalidates it. Original source depth is not a lossless-output proof.
plane::Plane carries a strided sample plane. PlanarLayout / PlanarSlice
combine validated roles, geometry and interpretation. The old planar module
is removed; its feature spelling remains a no-op. Native code extraction is
separate from chroma reconstruction, range expansion and color conversion.
use zenpixels_convert::{PixelBuffer, PixelDescriptor, PixelSliceConvertExt};
let image = PixelBuffer::from_vec(vec![128, 128, 128], 1, 1, PixelDescriptor::RGB8_SRGB)?;
let linear = image.as_slice().convert_to(PixelDescriptor::RGBF32_LINEAR)?;
assert_eq!(linear.descriptor(), PixelDescriptor::RGBF32_LINEAR);
# Ok::<(), Box<dyn std::error::Error>>(())Use RowConverter for rows and prepared scratch, or OutputPlan for
caller-supplied output rows and matching encoder metadata. EncodeReady::finalize
creates independent owned output. Known descriptor-only conversions need no
CMS; arbitrary ICC transforms need a selected backend. HDR rendering requires
explicit luminance/rendering inputs.
Loss policies are explicit with ConvertOptions. retain_channels prevents
alpha/depth loss but still defaults to gamut clipping; it is not a general
lossless guarantee. RowConverter::new has legacy permissive defaults, including
unchecked alpha discard. Use explicit options or new_preserving_samples when
preservation is required.
Conversion guide · Design contracts
Fallible image constructors return located BufferError errors. new and
other documented convenience paths panic on failure. Some control objects,
lazy caches and dependency allocations remain infallible: see the
allocation audit.
into_contiguous() compacts rows in the current allocation. Orientation can
reuse the pixel allocation, but rectangular in-place transpose needs scratch.
Reusing pixel storage is not a promise that every operation allocates nothing.
| Core feature | Effect |
|---|---|
std |
Default; omit for no_std + alloc |
icc |
Default; paired-fingerprint recognition with scoped substitution permission |
rgb |
Concrete rgb pixel types |
imgref |
imgref adapters; enables rgb |
image |
image 0.25 interop; requires Rust 1.88 |
hash |
Pixel Hash and configurable inclusions via zenhash; requires Rust 1.89 |
allocator_api |
Allocator-parameterized storage on supported compilers |
planar, serde |
Retained compatibility spellings; no API |
The converter's feature list covers the ICC database, CMS backends, interop and HDR options. Resource-estimation APIs are retired; their compatibility feature does not restore them.
Core requires Rust 1.85. The converter and core hash feature require 1.89;
core image requires 1.88. Both crates use edition 2024.
Apache-2.0 OR MIT
| Codecs ¹ | zenjpeg · zenpng · zenwebp · zengif · zenavif · zenjxl · zenjxl-decoder · jxl-encoder · zenbitmaps · heic · zentiff · zenpdf · zensvg · zenjp2 · zenraw · ultrahdr |
| Codec internals | zenrav1e · rav1d-safe · zenravif · zenavif-parse · zenavif-serialize |
| Compression | zenflate · zenzop · zenzstd |
| Processing | zenresize · zenquant · zenblend · zenfilters · zensally · zentone |
| Pixels & color | zenpixels · zenpixels-convert · linear-srgb · garb · zenyuv |
| Pipeline & framework | zenpipe · zencodec · zencodecs · zenlayout · zennode · zenwasm · zentract |
| Metrics | zensim · fast-ssim2 · butteraugli · zenmetrics · resamplescope-rs |
| Pickers & ML | zenanalyze · zenpredict · zenpicker · zenanalyze-api |
| Test corpora | codec-corpus · imazen-26 |
| Products | Imageflow image engine (.NET · Node · Go) · Imageflow Server · ImageResizer (C#) |
¹ pure-Rust, #![forbid(unsafe_code)] codecs, as of 2026
zenbench · archmage · magetypes · enough · whereat · cargo-copter · zenutils