garb/lib.rs
1//! # garb
2//!
3//! *Dress your pixels for the occasion.*
4//!
5//! You can't show up to a function in the wrong style. Swap your BGR for your RGB, your ARGB for your RGBA, and tie up loose
6//! ends like that unreliable alpha BGRX.
7//!
8//! SIMD-optimized pixel format conversions for row-level and whole-image
9//! operations. Supports x86-64 AVX2, ARM NEON, and WASM SIMD128 with
10//! automatic fallback to scalar code.
11//!
12//! ## Modules
13//!
14//! - [`bytes`] — Core `&[u8]` conversions (contiguous and strided).
15//! - [`typed_rgb`] — Type-safe wrappers using `rgb` crate pixel types (feature `rgb`).
16//! - [`imgref`] — Whole-image conversions on `ImgVec` / `ImgRef` (feature `imgref`).
17//!
18//! ## Naming convention
19//!
20//! All functions follow the pattern `{src}_to_{dst}` for copy operations and
21//! `{src}_to_{dst}_inplace` for in-place mutations. Append `_strided` for
22//! multi-row buffers with stride.
23//!
24//! ## Strides
25//!
26//! A **stride** (also called "pitch" or "row pitch") is the distance between
27//! the start of one row and the start of the next, measured in units of the
28//! slice's element type:
29//!
30//! - For `&[u8]` functions: stride is in **bytes**.
31//! - For typed APIs (`imgref`, `typed_rgb`): stride is in **elements** of the
32//! slice's item type — e.g. pixel count for `ImgRef<Rgba<u8>>`, but byte
33//! count for `ImgRef<u8>`.
34//!
35//! When `stride == width` (in the appropriate unit) the image is contiguous.
36//! When `stride > width` the gap at the end of each row is padding — garb
37//! never reads or writes it.
38//!
39//! The required buffer length (in elements) for a strided image is:
40//! `(height - 1) * stride + width`
41//!
42//! All `_strided` functions take dimensions *before* strides:
43//! - In-place: `(buf, width, height, stride)`
44//! - Copy: `(src, dst, width, height, src_stride, dst_stride)`
45//!
46//! ## Feature flags
47//!
48//! - **`std`** — Enables `std` on dependencies (e.g. `archmage`).
49//! - **`experimental`** — Gray layout, weighted luma, depth conversion, f32
50//! alpha premultiply/unpremultiply. API may change between minor versions.
51//! - **`rgb`** — Type-safe conversions using [`rgb`] crate pixel types
52//! via bytemuck. Zero-copy in-place swaps return reinterpreted references.
53//! - **`imgref`** — Multi-row conversions using `ImgRef` / `ImgRefMut`
54//! from the [`imgref`](https://docs.rs/imgref) crate. No allocation — caller owns all buffers.
55
56#![no_std]
57#![forbid(unsafe_code)]
58
59#[cfg(feature = "imgref")]
60extern crate alloc;
61
62pub mod bytes;
63
64#[cfg(feature = "rgb")]
65pub mod typed_rgb;
66
67#[cfg(feature = "imgref")]
68pub mod imgref;
69
70/// Pixel buffer size or alignment error.
71///
72/// Returned when a buffer's length is not a multiple of the pixel size,
73/// a destination buffer is too small, or stride/dimensions are inconsistent.
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75#[non_exhaustive]
76pub enum SizeError {
77 /// Buffer length is not a multiple of the expected bytes per pixel (or is empty).
78 NotPixelAligned,
79 /// Destination buffer has fewer pixels than the source.
80 PixelCountMismatch,
81 /// Stride, dimensions, or total buffer size are inconsistent.
82 InvalidStride,
83}
84
85impl core::fmt::Display for SizeError {
86 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
87 match self {
88 Self::NotPixelAligned => f.write_str("buffer length is not pixel-aligned"),
89 Self::PixelCountMismatch => f.write_str("destination has fewer pixels than source"),
90 Self::InvalidStride => {
91 f.write_str("stride, dimensions, or buffer size are inconsistent")
92 }
93 }
94 }
95}
96
97impl core::error::Error for SizeError {}
98
99// ===========================================================================
100// Generic conversion traits
101// ===========================================================================
102
103/// Copy-convert a pixel slice: `&[Src]` → `&mut [Dst]`.
104///
105/// Implemented for every valid `(Src, Dst)` pair when `feature = "rgb"`.
106/// Use [`convert`] for the free-function form.
107#[cfg(feature = "rgb")]
108pub trait ConvertTo<Dst>: Sized {
109 /// Convert pixels from `src` into `dst`.
110 fn convert_to(src: &[Self], dst: &mut [Dst]) -> Result<(), SizeError>;
111}
112
113/// In-place pixel conversion: `&mut [Src]` → `&mut [Dst]` (same pixel size).
114///
115/// Only implemented for same-size pixel pairs (e.g. 4bpp↔4bpp, 3bpp↔3bpp).
116/// Use [`convert_inplace`] for the free-function form.
117#[cfg(feature = "rgb")]
118pub trait ConvertInplace<Dst>: Sized {
119 /// Convert pixels in-place and return the buffer reinterpreted as `Dst`.
120 fn convert_inplace(buf: &mut [Self]) -> &mut [Dst];
121}
122
123/// Copy-convert pixel slices. Type inference selects the right conversion.
124///
125/// ```rust
126/// use rgb::{Rgb, Bgra};
127/// use garb::convert;
128///
129/// let rgb = vec![Rgb::new(255u8, 0, 128); 2];
130/// let mut bgra = vec![Bgra::default(); 2];
131/// convert(&rgb, &mut bgra).unwrap();
132/// assert_eq!(bgra[0], Bgra { b: 128, g: 0, r: 255, a: 255 });
133/// ```
134#[cfg(feature = "rgb")]
135#[inline(always)]
136pub fn convert<S: ConvertTo<D>, D>(src: &[S], dst: &mut [D]) -> Result<(), SizeError> {
137 S::convert_to(src, dst)
138}
139
140/// In-place pixel conversion. Returns the buffer reinterpreted as the target type.
141///
142/// ```rust
143/// use rgb::{Rgba, Bgra};
144/// use garb::convert_inplace;
145///
146/// let mut pixels = vec![Rgba::new(255u8, 0, 128, 255); 2];
147/// let bgra: &mut [Bgra<u8>] = convert_inplace(&mut pixels);
148/// assert_eq!(bgra[0], Bgra { b: 128, g: 0, r: 255, a: 255 });
149/// ```
150#[cfg(feature = "rgb")]
151#[inline(always)]
152pub fn convert_inplace<S: ConvertInplace<D>, D>(buf: &mut [S]) -> &mut [D] {
153 S::convert_inplace(buf)
154}
155
156/// Copy-convert an image: `ImgRef<Src>` → `ImgRefMut<Dst>`.
157///
158/// Implemented for every valid `(Src, Dst)` pair when `feature = "imgref"`.
159/// Use [`convert_imgref`] for the free-function form.
160#[cfg(feature = "imgref")]
161pub trait ConvertImage<Dst>: Sized {
162 /// Convert pixels from `src` image into `dst` image.
163 fn convert_image(
164 src: ::imgref::ImgRef<'_, Self>,
165 dst: ::imgref::ImgRefMut<'_, Dst>,
166 ) -> Result<(), SizeError>;
167}
168
169/// In-place image conversion: consumes `ImgVec<Src>`, returns `ImgVec<Dst>`.
170///
171/// Only implemented for same-size pixel pairs.
172/// Use [`convert_imgref_inplace`] for the free-function form.
173#[cfg(feature = "imgref")]
174pub trait ConvertImageInplace<Dst>: Sized {
175 /// Convert an image in-place and return it reinterpreted as `Dst`.
176 fn convert_image_inplace(img: ::imgref::ImgVec<Self>) -> ::imgref::ImgVec<Dst>;
177}
178
179/// Copy-convert an image. Type inference selects the right conversion.
180///
181/// ```rust
182/// use rgb::{Rgb, Bgra};
183/// use imgref::{ImgVec, ImgRefMut};
184/// use garb::convert_imgref;
185///
186/// let src = ImgVec::new(vec![Rgb::new(255u8, 0, 128); 4], 2, 2);
187/// let mut dst_buf = vec![Bgra::default(); 4];
188/// let dst = ImgRefMut::new(&mut dst_buf, 2, 2);
189/// convert_imgref(src.as_ref(), dst).unwrap();
190/// ```
191#[cfg(feature = "imgref")]
192#[inline(always)]
193pub fn convert_imgref<S: ConvertImage<D>, D>(
194 src: ::imgref::ImgRef<'_, S>,
195 dst: ::imgref::ImgRefMut<'_, D>,
196) -> Result<(), SizeError> {
197 S::convert_image(src, dst)
198}
199
200/// In-place image conversion. Consumes and returns the image with reinterpreted pixels.
201///
202/// ```rust
203/// use rgb::{Rgba, Bgra};
204/// use imgref::ImgVec;
205/// use garb::convert_imgref_inplace;
206///
207/// let img = ImgVec::new(vec![Rgba::new(255u8, 0, 128, 200); 4], 2, 2);
208/// let bgra_img: ImgVec<Bgra<u8>> = convert_imgref_inplace(img);
209/// ```
210#[cfg(feature = "imgref")]
211#[inline(always)]
212pub fn convert_imgref_inplace<S: ConvertImageInplace<D>, D>(
213 img: ::imgref::ImgVec<S>,
214) -> ::imgref::ImgVec<D> {
215 S::convert_image_inplace(img)
216}