fovea_derive/lib.rs
1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3#![warn(unreachable_pub)]
4#![deny(rustdoc::broken_intra_doc_links)]
5
6use proc_macro::TokenStream;
7use syn::{DeriveInput, parse_macro_input};
8
9mod homogeneous_pixel;
10mod linear_pixel;
11mod plain_pixel;
12mod white_channel;
13mod zeroable_pixel;
14
15/// Derive macro for `PlainPixel` trait.
16///
17/// # Requirements
18/// - Struct must be `#[repr(C)]`
19/// - Struct must have named fields
20/// - All fields must be `PlainPixel` types themselves
21///
22/// # Example
23/// ```ignore
24/// #[derive(Clone, Copy, PlainPixel)]
25/// #[repr(C)]
26/// pub struct Rgb8 {
27/// pub r: Saturating<u8>,
28/// pub g: Saturating<u8>,
29/// pub b: Saturating<u8>,
30/// }
31/// ```
32///
33/// # Generated Implementation
34/// ```ignore
35/// unsafe impl PlainPixel for Rgb8 {
36/// const CHANNELS: &'static [usize] = &[1, 1, 1];
37/// }
38/// ```
39#[proc_macro_derive(PlainPixel)]
40pub fn derive_plain_pixel(input: TokenStream) -> TokenStream {
41 let input = parse_macro_input!(input as DeriveInput);
42
43 plain_pixel::derive(input)
44 .unwrap_or_else(|err| err.to_compile_error())
45 .into()
46}
47
48/// Derive macro for `HomogeneousPixel` trait.
49///
50/// # Requirements
51/// - Struct must be `#[repr(C)]` or `#[repr(transparent)]`
52/// - For `#[repr(C)]`: all fields must have the **same type** (the channel type)
53/// - For `#[repr(transparent)]`: exactly one field, treated as a single-channel pixel
54/// - The struct must also implement `PlainPixel` (typically via `#[derive(PlainPixel)]`)
55///
56/// # Example
57/// ```ignore
58/// #[derive(Clone, Copy, PlainPixel, HomogeneousPixel)]
59/// #[repr(C)]
60/// pub struct Rgb8 {
61/// pub r: Saturating<u8>,
62/// pub g: Saturating<u8>,
63/// pub b: Saturating<u8>,
64/// }
65/// ```
66///
67/// # Generated Implementation
68/// ```ignore
69/// unsafe impl HomogeneousPixel for Rgb8 {
70/// type Channel = Saturating<u8>;
71/// type Channels = [Saturating<u8>; 3];
72/// }
73/// ```
74#[proc_macro_derive(HomogeneousPixel)]
75pub fn derive_homogeneous_pixel(input: TokenStream) -> TokenStream {
76 let input = parse_macro_input!(input as DeriveInput);
77
78 homogeneous_pixel::derive(input)
79 .unwrap_or_else(|err| err.to_compile_error())
80 .into()
81}
82
83/// Derive macro for `ZeroablePixel` trait.
84///
85/// Generates `zero()` by calling `ZeroablePixel::zero()` on each field's type.
86///
87/// # Requirements
88/// - Must be a struct (not enum or union)
89/// - Must have at least one field
90/// - By default, all field types must implement `ZeroablePixel`
91///
92/// # Per-Field Control with `#[zero(...)]`
93///
94/// You can override the zero-initialization strategy for individual fields:
95///
96/// - **No attribute** (default): uses `<T as ZeroablePixel>::zero()`
97/// - `#[zero(default)]`: uses `<T as Default>::default()`
98/// - `#[zero(<expr>)]`: uses the given expression verbatim
99///
100/// # Example
101/// ```ignore
102/// #[derive(Clone, Copy, PlainPixel, ZeroablePixel)]
103/// #[repr(C)]
104/// pub struct Rgb8 {
105/// pub r: Saturating<u8>,
106/// pub g: Saturating<u8>,
107/// pub b: Saturating<u8>,
108/// }
109/// ```
110///
111/// # Example with `#[zero(...)]` attributes
112/// ```ignore
113/// #[derive(Clone, Copy, ZeroablePixel)]
114/// #[repr(C)]
115/// pub struct PixelWithMeta {
116/// pub r: u8,
117/// pub g: u8,
118/// pub b: u8,
119/// #[zero(default)]
120/// pub metadata: SomeDefaultType,
121/// #[zero(MyTag::new(42))]
122/// pub tag: MyTag,
123/// }
124/// ```
125///
126/// # Generated Implementation
127/// ```ignore
128/// impl ZeroablePixel for Rgb8 {
129/// fn zero() -> Self {
130/// Rgb8 {
131/// r: <Saturating<u8> as ZeroablePixel>::zero(),
132/// g: <Saturating<u8> as ZeroablePixel>::zero(),
133/// b: <Saturating<u8> as ZeroablePixel>::zero(),
134/// }
135/// }
136/// }
137/// ```
138#[proc_macro_derive(ZeroablePixel, attributes(zero))]
139pub fn derive_zeroable_pixel(input: TokenStream) -> TokenStream {
140 let input = parse_macro_input!(input as DeriveInput);
141
142 zeroable_pixel::derive(input)
143 .unwrap_or_else(|err| err.to_compile_error())
144 .into()
145}
146
147/// Derive macro for `LinearPixel` trait.
148///
149/// Generates channel-wise `Add`, `LinearPixel`, and optionally `FromLinear` impls,
150/// plus the `LinearSpace` marker trait.
151///
152/// # Requirements
153/// - Must be a struct with at least one field (named or tuple)
154/// - Must have `#[linear(accumulator = AccType)]` attribute
155///
156/// # Attribute
157/// - `accumulator = Type`: the intermediate-precision accumulator type.
158/// - For storage pixels (e.g. `Rgb8`), use the float pixel (e.g. `RgbF32`).
159/// - For float/accumulator pixels (e.g. `RgbF32`), use `Self`.
160/// - When `Self` is used, no `FromLinear` impl is generated (blanket identity covers it).
161///
162/// # Example (storage pixel → float accumulator)
163/// ```ignore
164/// #[derive(Clone, Copy, LinearPixel)]
165/// #[repr(C)]
166/// #[linear(accumulator = RgbF32)]
167/// pub struct Rgb8 {
168/// pub r: Saturating<u8>,
169/// pub g: Saturating<u8>,
170/// pub b: Saturating<u8>,
171/// }
172/// ```
173///
174/// # Example (self-accumulating float pixel)
175/// ```ignore
176/// #[derive(Clone, Copy, LinearPixel)]
177/// #[repr(C)]
178/// #[linear(accumulator = Self)]
179/// pub struct RgbF32 {
180/// pub r: f32,
181/// pub g: f32,
182/// pub b: f32,
183/// }
184/// ```
185///
186/// # Generated impls
187/// - `impl Add for T` — channel-wise addition
188/// - `impl LinearPixel for T` — delegates `scale()` to each field's `LinearPixel::scale`
189/// - `impl FromLinear<Acc> for T` — delegates per-field `FromLinear` conversion (only when accumulator ≠ `Self`)
190/// - `impl LinearSpace for T` — marker trait asserting values are in a linear space
191#[proc_macro_derive(LinearPixel, attributes(linear))]
192pub fn derive_linear_pixel(input: TokenStream) -> TokenStream {
193 let input = parse_macro_input!(input as DeriveInput);
194
195 linear_pixel::derive(input)
196 .unwrap_or_else(|err| err.to_compile_error())
197 .into()
198}
199
200/// Derive macro for `WhiteChannel` trait.
201///
202/// Emits an impl delegating to the channel type's `BoundedChannel::MAX`.
203/// This is the correct answer for every homogeneous pixel in the library
204/// whose channel type is `BoundedChannel`.
205///
206/// **Reduced-range pixels (`Mono<BITS>`) must not use this derive.**
207/// They implement `WhiteChannel` manually, returning the pixel's own
208/// invariant-respecting maximum (e.g. `Saturating(1023)` for `Mono<10>`)
209/// rather than the channel type's storage maximum
210/// (`<Saturating<u16> as BoundedChannel>::MAX == Saturating(65535)`).
211///
212/// # Requirements
213/// - Must be a struct (named or tuple).
214/// - The struct must also implement `HomogeneousPixel` (typically via
215/// `#[derive(HomogeneousPixel)]`).
216/// - The struct's channel type (`<Self as HomogeneousPixel>::Channel`)
217/// must implement `BoundedChannel`.
218///
219/// # Example
220/// ```ignore
221/// #[derive(Clone, Copy, PlainPixel, HomogeneousPixel, WhiteChannel)]
222/// #[repr(C)]
223/// pub struct Rgb8 {
224/// pub r: Saturating<u8>,
225/// pub g: Saturating<u8>,
226/// pub b: Saturating<u8>,
227/// }
228/// ```
229#[proc_macro_derive(WhiteChannel)]
230pub fn derive_white_channel(input: TokenStream) -> TokenStream {
231 let input = parse_macro_input!(input as DeriveInput);
232
233 white_channel::derive(input)
234 .unwrap_or_else(|err| err.to_compile_error())
235 .into()
236}