Skip to main content

rawshift_image/processing/demosaic/
mod.rs

1use crate::core::image::{RawImage, RgbImage};
2
3/// Error type for demosaicing operations.
4#[derive(Debug, Clone)]
5pub enum DemosaicError {
6    /// Output buffer size does not match expected size
7    BufferSizeMismatch {
8        /// Expected buffer size in u16 elements
9        expected: usize,
10        /// Actual buffer size provided
11        actual: usize,
12    },
13    /// Invalid image dimensions
14    InvalidDimensions,
15    /// The requested demosaicing algorithm is not yet implemented
16    UnsupportedAlgorithm(&'static str),
17}
18
19impl std::fmt::Display for DemosaicError {
20    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21        match self {
22            DemosaicError::BufferSizeMismatch { expected, actual } => {
23                write!(
24                    f,
25                    "buffer size mismatch: expected {} elements, got {}",
26                    expected, actual
27                )
28            }
29            DemosaicError::InvalidDimensions => write!(f, "invalid image dimensions"),
30            DemosaicError::UnsupportedAlgorithm(name) => {
31                write!(f, "demosaicing algorithm '{}' is not yet implemented", name)
32            }
33        }
34    }
35}
36
37impl std::error::Error for DemosaicError {}
38
39// =============================================================================
40// High-Level API: DemosaicMethod enum
41// =============================================================================
42
43/// High-level selection of demosaicing based on sensor architecture.
44///
45/// This enum provides a user-friendly API for selecting demosaicing algorithms.
46/// Use [`to_demosaic()`](Self::to_demosaic) to get a trait object that implements
47/// the actual algorithm.
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
49#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
50pub enum DemosaicMethod {
51    /// Automatically detects sensor pattern (Bayer vs X-Trans) from metadata
52    /// and chooses the best algorithm for the ISO/exposure.
53    #[default]
54    Auto,
55
56    /// Algorithms strictly for 2x2 Bayer patterns (Sony, Canon, Nikon, DNG, etc.)
57    Bayer(BayerAlgorithm),
58
59    /// Algorithms strictly for 6x6 Fujifilm X-Trans patterns.
60    XTrans(XTransAlgorithm),
61
62    /// Returns the raw monochrome CFA data without color reconstruction.
63    None,
64}
65
66impl std::fmt::Display for DemosaicMethod {
67    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68        match self {
69            DemosaicMethod::Auto => write!(f, "Auto"),
70            DemosaicMethod::Bayer(algo) => write!(f, "Bayer({})", algo),
71            DemosaicMethod::XTrans(algo) => write!(f, "XTrans({})", algo),
72            DemosaicMethod::None => write!(f, "None"),
73        }
74    }
75}
76
77impl DemosaicMethod {
78    /// Get the demosaicing algorithm implementation.
79    ///
80    /// For `Auto`, this defaults to AMaZE for Bayer sensors.
81    /// For `None`, this returns a no-op passthrough.
82    pub fn to_demosaic(&self) -> Box<dyn Demosaic + Send + Sync> {
83        match self {
84            DemosaicMethod::Auto => {
85                // Default to AMaZE for Bayer sensors (best quality)
86                Box::new(bayer::Amaze)
87            }
88            DemosaicMethod::Bayer(algo) => algo.to_demosaic(),
89            DemosaicMethod::XTrans(algo) => algo.to_demosaic(),
90            DemosaicMethod::None => Box::new(NoDemosaic),
91        }
92    }
93}
94
95/// Valid algorithms for standard Bayer sensors.
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
97#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
98pub enum BayerAlgorithm {
99    /// Industry standard for high-detail, low-noise images.
100    #[default]
101    Amaze,
102    /// High-ISO specialist; treats noise as a statistical probability.
103    Lmmse,
104    /// Fast, high-quality alternative to AMaZE; great for organic shapes.
105    Rcd,
106    /// Very fast; low-quality. Suitable for previews.
107    Bilinear,
108}
109
110impl std::fmt::Display for BayerAlgorithm {
111    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112        match self {
113            BayerAlgorithm::Amaze => write!(f, "AMaZE"),
114            BayerAlgorithm::Lmmse => write!(f, "LMMSE"),
115            BayerAlgorithm::Rcd => write!(f, "RCD"),
116            BayerAlgorithm::Bilinear => write!(f, "Bilinear"),
117        }
118    }
119}
120
121impl BayerAlgorithm {
122    /// Get the demosaicing algorithm implementation.
123    pub fn to_demosaic(&self) -> Box<dyn Demosaic + Send + Sync> {
124        match self {
125            BayerAlgorithm::Amaze => Box::new(bayer::Amaze),
126            BayerAlgorithm::Lmmse => Box::new(bayer::Lmmse),
127            BayerAlgorithm::Rcd => Box::new(bayer::Rcd),
128            BayerAlgorithm::Bilinear => Box::new(Bilinear),
129        }
130    }
131}
132
133/// Valid algorithms for Fujifilm X-Trans sensors.
134#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
135#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
136pub enum XTransAlgorithm {
137    /// The standard for X-Trans; handles the complex 6x6 grid.
138    #[default]
139    Markesteijn,
140    /// Slower 3-pass version that aggressively reduces moiré/false colors.
141    Markesteijn3Pass,
142    /// Faster, simpler interpolation for X-Trans previews.
143    Fast,
144}
145
146impl std::fmt::Display for XTransAlgorithm {
147    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148        match self {
149            XTransAlgorithm::Markesteijn => write!(f, "Markesteijn"),
150            XTransAlgorithm::Markesteijn3Pass => write!(f, "Markesteijn 3-Pass"),
151            XTransAlgorithm::Fast => write!(f, "Fast"),
152        }
153    }
154}
155
156impl XTransAlgorithm {
157    /// Get the demosaicing algorithm implementation.
158    pub fn to_demosaic(&self) -> Box<dyn Demosaic + Send + Sync> {
159        match self {
160            XTransAlgorithm::Markesteijn => Box::new(xtrans::Markesteijn),
161            XTransAlgorithm::Markesteijn3Pass => Box::new(xtrans::Markesteijn3Pass),
162            XTransAlgorithm::Fast => Box::new(xtrans::XTransFast),
163        }
164    }
165}
166
167// =============================================================================
168// Demosaic Trait (Low-Level Implementation Interface)
169// =============================================================================
170
171/// Trait for demosaicing algorithms.
172///
173/// Implementors should override [`demosaic_into`](Self::demosaic_into) as the primary method.
174/// The [`demosaic`](Self::demosaic) method provides a convenience wrapper that allocates output.
175///
176/// # Example
177///
178/// ```ignore
179/// use rawshift::processing::demosaic::{Bilinear, Demosaic};
180///
181/// let demosaiced = Bilinear.demosaic(&raw_image);
182/// ```
183pub trait Demosaic {
184    /// Demosaic a raw image into a pre-allocated RGB buffer.
185    ///
186    /// This is the primary method that implementors must override.
187    /// The output buffer must have exactly `width * height * 3` elements.
188    ///
189    /// # Arguments
190    /// * `raw` - The raw image to demosaic
191    /// * `output` - Pre-allocated buffer for RGB output (interleaved R, G, B, R, G, B, ...)
192    ///
193    /// # Errors
194    /// Returns [`DemosaicError::BufferSizeMismatch`] if buffer size is incorrect.
195    fn demosaic_into(&self, raw: &RawImage, output: &mut [u16]) -> Result<(), DemosaicError>;
196
197    /// Demosaic a raw image into a newly allocated RGB image.
198    ///
199    /// This is a convenience wrapper that allocates the output buffer
200    /// and calls [`demosaic_into`](Self::demosaic_into).
201    #[must_use]
202    fn demosaic(&self, raw: &RawImage) -> RgbImage {
203        let width = raw.active_area().size.width;
204        let height = raw.active_area().size.height;
205        let mut data = vec![0u16; (width as usize) * (height as usize) * 3];
206        self.demosaic_into(raw, &mut data)
207            .expect("demosaic_into failed with correctly sized buffer");
208        RgbImage::new(width, height, data)
209    }
210}
211
212// =============================================================================
213// Submodules
214// =============================================================================
215
216mod bilinear;
217pub use bilinear::Bilinear;
218
219pub mod bayer;
220pub mod xtrans;
221
222// =============================================================================
223// No-op Demosaic (for DemosaicMethod::None)
224// =============================================================================
225
226/// No-op demosaicing that copies raw values to the first channel.
227///
228/// This is used when `DemosaicMethod::None` is selected. It produces
229/// a grayscale image from the raw CFA data without any interpolation.
230pub struct NoDemosaic;
231
232impl Demosaic for NoDemosaic {
233    fn demosaic_into(&self, raw: &RawImage, output: &mut [u16]) -> Result<(), DemosaicError> {
234        let width = raw.active_area().size.width as usize;
235        let height = raw.active_area().size.height as usize;
236        let x_offset = raw.active_area().origin.x as usize;
237        let y_offset = raw.active_area().origin.y as usize;
238        let raw_width = raw.width() as usize;
239
240        let expected_size = width * height * 3;
241        if output.len() != expected_size {
242            return Err(DemosaicError::BufferSizeMismatch {
243                expected: expected_size,
244                actual: output.len(),
245            });
246        }
247
248        // Copy raw values to all three channels (grayscale output)
249        for y in 0..height {
250            for x in 0..width {
251                let raw_idx = (y + y_offset) * raw_width + (x + x_offset);
252                let out_idx = (y * width + x) * 3;
253                let value = raw.data[raw_idx];
254                output[out_idx] = value;
255                output[out_idx + 1] = value;
256                output[out_idx + 2] = value;
257            }
258        }
259
260        Ok(())
261    }
262}
263
264#[cfg(test)]
265mod tests {
266    use super::*;
267
268    #[test]
269    fn test_demosaic_error_display() {
270        let err = DemosaicError::BufferSizeMismatch {
271            expected: 300,
272            actual: 100,
273        };
274        let msg = format!("{}", err);
275        assert!(msg.contains("300"));
276        assert!(msg.contains("100"));
277
278        let err = DemosaicError::InvalidDimensions;
279        let msg = format!("{}", err);
280        assert!(msg.contains("dimension"));
281    }
282
283    #[test]
284    fn test_demosaic_method_default() {
285        let method = DemosaicMethod::default();
286        assert_eq!(method, DemosaicMethod::Auto);
287    }
288
289    #[test]
290    fn test_bayer_algorithm_default() {
291        let algo = BayerAlgorithm::default();
292        assert_eq!(algo, BayerAlgorithm::Amaze);
293    }
294
295    #[test]
296    fn test_xtrans_algorithm_default() {
297        let algo = XTransAlgorithm::default();
298        assert_eq!(algo, XTransAlgorithm::Markesteijn);
299    }
300
301    #[test]
302    fn test_demosaic_method_variants() {
303        // Just ensure all variants can be created
304        let _ = DemosaicMethod::Auto;
305        let _ = DemosaicMethod::Bayer(BayerAlgorithm::Bilinear);
306        let _ = DemosaicMethod::XTrans(XTransAlgorithm::Fast);
307        let _ = DemosaicMethod::None;
308    }
309}