fovea 0.2.0

A high-precision, type-safe computer vision library guaranteeing absolute image correctness at compile time
Documentation

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 trait Use 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 / ImageViewMut An algorithm only needs random pixel access.
RasterImage / RasterImageMut An algorithm should process row slices efficiently.
ContiguousImage / ContiguousImageMut The whole image is one dense pixel slice.
PlainImage / PlainImageMut You need byte access to contiguous PlainPixel storage.
SubView / SubViewMut You 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

Family Examples Meaning
Mono Mono8, Mono16, MonoF32, Mono<12> One-channel intensity pixels.
RGB/BGR Rgb8, Bgr8, RgbF32 Linear color pixels with explicit channel order.
sRGB Srgb8, Srgba8, SrgbMono8 Gamma-encoded display/file pixels. Not linear-light.
Alpha Rgba8, Bgra8, MonoA16 Pixels with explicit alpha channels.
Indexed/labels Indexed8, Label32, bool Palette 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

Module Start here One job
image Image, ImageView, SubView Storage, views, rows, ROIs, tiles, and neighborhoods.
pixel Srgb8, RgbF32, PlainPixel, LinearSpace Pixel vocabulary and the traits that make illegal operations unrepresentable.
transform convert_image, resize, combine_images Image-producing operations: conversion, resize, geometry, convolution, morphology.
analyze histogram, integral_image, connected_components Image analysis that produces data about an image.
border Clamp, Mirror, Skip Boundary behavior for neighborhood operations.
guide guide::faq, guide::pixel_types Task-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

Crate Published? Purpose
fovea crates.io + docs.rs Core image types, pixels, analysis, and transforms.
fovea-io crates.io + docs.rs Feature-gated PNG, JPEG, and BMP codecs.
fovea-display crates.io + docs.rs Display conversion strategies, texture metadata, and debug windows.
fovea-derive crates.io + docs.rs Derive macros re-exported by fovea.
fovea-examples repo only End-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.