Skip to main content

Crate fovea

Crate fovea 

Source
Expand description

§fovea

Crates.io Documentation License: MIT

fovea is image processing for Rust where the compiler catches colorspace mistakes, channel-order bugs, and lossy conversions before your code runs.

If you have ever shipped a bug because someone passed BGR where RGB was expected, resized gamma-encoded pixels with bilinear interpolation, or treated a camera SDK byte buffer as “probably u16 grayscale”, fovea was built for you.

In fovea, BGR and RGB are different types. Blending gamma-encoded pixels is a compile error. Byte layout is a contract, not a comment.

§What fovea prevents

  • BGR/RGB confusionBgr8 and Rgb8 are distinct types. Passing one where the other is expected is a compile error.
  • Gamma-incorrect interpolationBilinear resize requires pixels in LinearSpace. It will not compile for Srgb8; linearize first, explicitly.
  • Silent data loss — there is no implicit “just convert this image” path. Lossy conversions are named strategies: Luminance, Narrow, FullRange, SrgbGamma, Clamp.
  • Raw byte misusePlainPixel is an unsafe layout contract. Once a pixel type implements it, byte-level access is guaranteed by the type system.
  • Over-promising APIs — traits describe exactly what an operation needs: random access, row access, contiguous storage, byte layout, or linear arithmetic.

§The compiler catches the image bug

Nearest-neighbor resize copies pixels, so it works for gamma-encoded sRGB. Bilinear resize blends neighboring samples, so fovea requires linear-light pixels first.

use fovea::Size;
use fovea::image::{Image, ImageView};
use fovea::pixel::{RgbF32, Srgb8};
use fovea::transform::{Bilinear, NearestNeighbor, SrgbGamma, convert_image, resize};

let srgb = Image::generate(4, 3, |x, y| {
    Srgb8::new((x * 40) as u8, (y * 60) as u8, 128)
});

// ✓ This compiles: nearest-neighbor copies samples without blending them.
let preview: Image<Srgb8> = resize(&srgb, Size::new(8, 6), NearestNeighbor);
assert_eq!(preview.size(), Size::new(8, 6));

// ✓ This compiles: explicit sRGB decode before interpolation.
let linear: Image<RgbF32> = convert_image(&srgb, SrgbGamma);
let resized: Image<RgbF32> = resize(&linear, Size::new(8, 6), Bilinear);
assert_eq!(resized.size(), Size::new(8, 6));

The version below does not compile, and that is the point:

use fovea::Size;
use fovea::image::Image;
use fovea::pixel::Srgb8;
use fovea::transform::{Bilinear, resize};

let srgb = Image::fill(4, 3, Srgb8::new(128, 64, 32));

// ✗ Bilinear interpolation blends samples. Srgb8 is gamma-encoded.
let _: Image<Srgb8> = resize(&srgb, Size::new(8, 6), Bilinear);

Linearize first with SrgbGamma, resize in RgbF32 or MonoF32, then encode back if you need an sRGB output image.

§When NOT to use fovea

  • You need real-time video decode: use FFmpeg bindings.
  • You need deep-learning tensor pipelines: use candle, ort, or your tensor runtime of choice.
  • You want the broadest possible codec support with the simplest API: use the image crate.
  • You only need to resize a JPEG in a web app and do not care about pixel semantics: use the image crate.

§When fovea is the right tool

  • You read pixel data directly from industrial, scientific, or machine-vision cameras.
  • Correctness matters more than convenience: inspection, metrology, robotics, medical, lab automation.
  • You want the compiler to enforce colorspace and pixel-format discipline across a team.
  • You need guaranteed byte layout for camera SDK buffers, memory-mapped images, GPU upload, or FFI.
  • You want algorithms to state their real requirements in trait bounds instead of runtime checks.

§Install

cargo add fovea

For PNG, JPEG, or BMP I/O, add fovea-io with the codec features you need:

cargo add fovea
cargo add fovea-io --features png

§Getting started

Create an image, decode sRGB samples into linear light, modify pixels through the contiguous slice, and encode back to sRGB:

use fovea::image::{ContiguousImageMut, Image, ImageView};
use fovea::pixel::{RgbF32, Srgb8};
use fovea::transform::{SrgbGamma, convert_image};

let srgb = Image::generate(2, 2, |x, y| {
    Srgb8::new((x * 120) as u8, (y * 120) as u8, 64)
});

let mut linear: Image<RgbF32> = convert_image(&srgb, SrgbGamma);
for px in linear.as_mut_slice() {
    px.r = (px.r * 1.2).min(1.0);
}

let display: Image<Srgb8> = convert_image(&linear, SrgbGamma);
assert_eq!(display.size(), srgb.size());

For a longer first pass, start with the docs.rs guide: fovea::guide.

§Core types

Type or traitUse it when
Image<P>You own a heap-allocated image with runtime dimensions.
ImageArray<P, W, H>Width and height are compile-time constants.
ImageRef<'a, P> / ImageRefMut<'a, P>You want a borrowed view over existing storage, including strided ROIs.
ImageView / ImageViewMutAn algorithm only needs random pixel access.
RasterImage / RasterImageMutAn algorithm should process row slices efficiently.
ContiguousImage / ContiguousImageMutThe whole image is one dense pixel slice.
PlainImage / PlainImageMutYou need byte access to contiguous PlainPixel storage.
SubView / SubViewMutYou need zero-copy regions of interest, tiles, or sliding windows.

The image traits intentionally mirror Rust’s slice model: borrow views when you can, allocate only when you mean to.

§Pixel types

FamilyExamplesMeaning
MonoMono8, Mono16, MonoF32, Mono<12>One-channel intensity pixels.
RGB/BGRRgb8, Bgr8, RgbF32Linear color pixels with explicit channel order.
sRGBSrgb8, Srgba8, SrgbMono8Gamma-encoded display/file pixels. Not linear-light.
AlphaRgba8, Bgra8, MonoA16Pixels with explicit alpha channels.
Indexed/labelsIndexed8, Label32, boolPalette indices, connected-component labels, binary masks.

Important distinction: Rgb8 and Srgb8 may both store three u8 channels, but they do not mean the same thing. Rgb8 is linear-light RGB. Srgb8 is gamma-encoded sRGB. Algorithms that blend pixels can require the former and reject the latter.

§Modules

ModuleStart hereOne job
imageImage, ImageView, SubViewStorage, views, rows, ROIs, tiles, and neighborhoods.
pixelSrgb8, RgbF32, PlainPixel, LinearSpacePixel vocabulary and the traits that make illegal operations unrepresentable.
transformconvert_image, resize, combine_imagesImage-producing operations: conversion, resize, geometry, convolution, morphology.
analyzehistogram, integral_image, connected_componentsImage analysis that produces data about an image.
borderClamp, Mirror, SkipBoundary behavior for neighborhood operations.
guideguide::faq, guide::pixel_typesTask-oriented docs.rs pages for common questions.

§FAQ

Where do I start if I just want to load a PNG and resize it? Use fovea-io to decode, match the returned pixel enum once, convert sRGB images to linear pixels with SrgbGamma, call resize(..., Bilinear), then encode.

Why does Bilinear fail for Srgb8? Because bilinear interpolation blends samples, and blending gamma-encoded samples is physically wrong. Use NearestNeighbor if you are only copying samples; otherwise linearize first.

What type should I use for a 12-bit monochrome camera? Use Mono<12> when you want the bit depth represented in the pixel type. Use Mono16 when the camera SDK already expands samples to full 16-bit storage and you want simpler integration.

How do I process a large image in parallel? For contiguous per-pixel work, use as_slice() / as_mut_slice() and choose your own parallel runtime. For region-local work, split into tiles. For in-place mutation, into_tiles_mut() yields disjoint mutable tiles.

More answers are in fovea::guide::faq.

§Crate ecosystem

CratePublished?Purpose
foveacrates.io + docs.rsCore image types, pixels, analysis, and transforms.
fovea-iocrates.io + docs.rsFeature-gated PNG, JPEG, and BMP codecs.
fovea-displaycrates.io + docs.rsDisplay conversion strategies, texture metadata, and debug windows.
fovea-derivecrates.io + docs.rsDerive macros re-exported by fovea.
fovea-examplesrepo onlyEnd-to-end programs that combine the crates.

§Design principles

fovea is designed around a small set of explicit principles: types are the spec, concerns are orthogonal, traits layer progressively, conversions are named, and layout is a contract. The short version is this:

The compiler is the first reviewer.

§License

Licensed under the MIT License.

§docs.rs guide pages

The crate root above is the quick evaluation path. For task-oriented docs, use the documentation-only guide module on docs.rs:

Modules§

analyze
Image analysis operations (histograms, statistics, descriptors).
border
Border policies for out-of-bounds pixel access in neighborhood operations.
guide
Getting started
image
Image types, views, and containers.
pixel
Pixel types and the traits that make image operations type-safe.
transform
Image-producing operations: conversion, resize, geometry, filters, morphology.

Structs§

Coordinate
The Coordinate struct represents a coordinate in 2D space with x and y coordinates.
Rectangle
The Rectangle struct represents a rectangle defined by an offset coordinate and size.
Size
The Size struct represents the dimensions of an image.
Stride
A step size for sliding window iteration, wrapping a Size.

Enums§

Error
Errors returned by fallible image operations.

Derive Macros§

HomogeneousPixel
Derive macro for HomogeneousPixel trait.
LinearPixel
Derive macro for LinearPixel trait.
PlainPixel
Derive macro for PlainPixel trait.
ZeroablePixel
Derive macro for ZeroablePixel trait.