fovea-io 0.2.0

Feature-gated PNG, JPEG, and BMP codecs for fovea images
Documentation

fovea-io

Crates.io Documentation License: MIT

fovea-io is the file boundary for fovea: decode PNG, JPEG, and BMP bytes into typed fovea::Image<P> values, then let the core crate enforce pixel semantics from there.

The key idea is simple: I/O tells you what the file contains. It does not silently decide how to process it. Metadata travels with pixels, color conversions are explicit, and unsupported encodes are compile-time errors where possible.

Install

No codecs are enabled by default. Enable only the formats you need:

cargo add fovea

cargo add fovea-io --features png

Enable every current codec with:

cargo add fovea-io --features all-codecs

Features

Feature Enables Pulls in
png PNG decode + encode png
jpeg JPEG decode + encode jpeg-decoder, jpeg-encoder
bmp BMP decode + encode no external codec crate
all-codecs PNG, JPEG, and BMP all optional codec dependencies

The fovea I/O model

bytes → detect/decode → per-codec enum → concrete Image<P> → explicit transforms → encode

load is convenient when you do not know the file format. Per-codec APIs are better when you do know the format, because their image enums are exhaustive spec sheets.

use fovea_io::{ImageFormat, detect_format};

let png_sig = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
assert_eq!(detect_format(&png_sig), Some(ImageFormat::Png));
assert_eq!(detect_format(&[0x00, 0x00]), None);

Decode once, then work with types

use fovea::image::ImageView;
use fovea_io::png::{self, PngImage};

let bytes = std::fs::read("input.png")?;
let decoded = png::decode(&bytes)?;

match decoded.image {
    PngImage::Srgb8(img) => {
        println!("8-bit sRGB PNG: {}x{}", img.width(), img.height());
        // img is Image<Srgb8>
    }
    PngImage::Srgba8(img) => {
        println!("8-bit sRGBA PNG: {}x{}", img.width(), img.height());
        // img is Image<Srgba8>
    }
    other => {
        println!("supported PNG, different pixel format: {other:?}");
    }
}

After the match, downstream code has concrete pixel types. A resize pipeline can reject gamma-incorrect interpolation; a display path can require display-ready pixels; a camera pipeline can keep raw linear data linear.

Decode → transform → encode

This is the common shape for file tools:

use fovea::Size;
use fovea::image::Image;
use fovea::pixel::{RgbF32, Srgb8};
use fovea::transform::{Bilinear, SrgbGamma, convert_image, resize};
use fovea_io::png::{self, PngEncodeOptions, PngImage};

let bytes = std::fs::read("photo.png")?;
let decoded = png::decode(&bytes)?;

let srgb: Image<Srgb8> = match decoded.image {
    PngImage::Srgb8(img) => img,
    other => return Err(format!("expected Srgb8 PNG, got {other:?}").into()),
};

// Bilinear resize is done in linear light.
let linear: Image<RgbF32> = convert_image(&srgb, SrgbGamma);
let resized: Image<RgbF32> = resize(&linear, Size::new(800, 600), Bilinear);
let output: Image<Srgb8> = convert_image(&resized, SrgbGamma);

let out_bytes = png::encode(&output, &PngEncodeOptions::default())?;
std::fs::write("photo_800x600.png", out_bytes)?;

The I/O crate does not hide the SrgbGamma step. That is deliberate: decoding surfaces information; your pipeline chooses what to do with it.

Which entry point?

You know... Use Why
Nothing except "these are image bytes" load / load_reader Detects PNG/JPEG/BMP by magic bytes and returns DecodedImage.
The file is PNG png::decode / png::encode Exhaustive PNG pixel enum and PNG metadata.
The file is JPEG jpeg::decode / jpeg::encode JPEG-specific metadata and supported JPEG pixel shapes.
The file is BMP bmp::decode / bmp::encode BMP-specific representation without pulling in PNG/JPEG dependencies.
Only the signature matters detect_format Cheap format detection without codec features.

Metadata is not policy

Decoded values carry metadata alongside pixel data. fovea-io reports what the file said; it does not silently linearize, premultiply alpha, apply profiles, or drop ancillary data on your behalf.

That policy keeps I/O predictable:

  • The decode boundary preserves file facts.
  • The transform boundary names semantic changes.
  • The encode boundary checks whether the target format can represent the requested pixel type.

Crate ecosystem

Crate Purpose
fovea Core image types, pixels, transforms, and analysis.
fovea-io File codecs for typed fovea images.
fovea-display Display strategies and debug windows.
fovea-examples Repo-only end-to-end programs using all crates together.

License

fovea-io itself is licensed under the MIT License.

When the jpeg feature is enabled, this crate depends on jpeg-encoder, which carries an additional IJG (Independent JPEG Group) license requiring this acknowledgement:

This software is based in part on the work of the Independent JPEG Group.

See THIRD-PARTY-LICENSES.txt for full license texts of dependencies and attribution requirements.