refimage/
imageowned.rs

1use std::time::Duration;
2
3use crate::{
4    coretraits::cast_u8,
5    demosaic::{run_demosaic_imageowned, Debayer, RasterMut},
6    imagetraits::ImageProps,
7    BayerError, CalcOptExp, ColorSpace, DemosaicMethod, Enlargeable, ImageRef, OptimumExposure,
8    PixelStor, PixelType, ToLuma,
9};
10use bytemuck::{AnyBitPattern, PodCastError};
11
12/// A structure that holds image data backed by a vector.
13///
14/// This represents a _matrix_ of _pixels_ which are composed of primitive and common
15/// types, i.e. `u8`, `u16`, and `f32`. The matrix is stored in a _row-major_ order.
16///
17/// [`ImageOwned`] supports arbitrary color spaces and number of channels, but the number
18/// of channels must be consistent across the image. The data is stored in a single
19/// contiguous buffer.
20///
21/// Alpha channels are not natively supported.
22///
23/// # Usage
24/// ```
25/// use refimage::{ImageOwned, ColorSpace};
26///
27/// let data = vec![1u8, 2, 3, 4, 5, 6];
28/// let img = ImageOwned::from_owned(data, 3, 2, ColorSpace::Gray).unwrap();
29/// ```
30#[derive(Debug, PartialEq, Clone)]
31pub struct ImageOwned<T: PixelStor> {
32    pub(crate) data: Vec<T>,
33    pub(crate) width: u16,
34    pub(crate) height: u16,
35    pub(crate) channels: u8,
36    pub(crate) cspace: ColorSpace,
37}
38
39impl<T: PixelStor> ImageOwned<T> {
40    pub(crate) fn new(
41        data: Vec<T>,
42        width: usize,
43        height: usize,
44        cspace: ColorSpace,
45    ) -> Result<Self, &'static str> {
46        if height > u16::MAX as usize || width > u16::MAX as usize {
47            return Err("Image too large.");
48        }
49        if data.is_empty() {
50            return Err("Data is empty");
51        }
52        if width == 0 {
53            return Err("Width is zero");
54        }
55        if height == 0 {
56            return Err("Height is zero");
57        }
58        let channels = match cspace {
59            ColorSpace::Gray | ColorSpace::Bayer(_) => 1,
60            ColorSpace::Rgb => 3,
61            ColorSpace::Custom(ch, _) => ch as usize,
62        };
63        let len = data.len();
64        let tot = width
65            .checked_mul(height)
66            .ok_or("Image too large.")?
67            .checked_mul(channels)
68            .ok_or("Image too large.")?;
69        if tot > len {
70            return Err("Not enough data for image.");
71        }
72        let mut img = ImageOwned {
73            data,
74            width: width as u16,
75            height: height as u16,
76            channels: channels as u8,
77            cspace,
78        };
79        img.data.truncate(tot);
80        Ok(img)
81    }
82
83    /// Create a new [`ImageOwned`] from a slice of data.
84    ///
85    /// Images can not be larger than 65535x65535 pixels.
86    ///
87    /// # Arguments
88    /// - `data`: The data slice. It is copied into the image.
89    /// - `width`: The width of the image.
90    /// - `height`: The height of the image.
91    /// - `cspace`: The color space of the image ([`ColorSpace`]).
92    ///
93    /// # Errors
94    /// - If the image is too large.
95    /// - If the data is empty.
96    /// - If the width is zero.
97    /// - If the height is zero.
98    /// - If there are too many channels for grayscale/Bayer pattern images.
99    /// - If color space is RGB and number of channels is not 3.
100    pub fn from_ref(
101        data: &[T],
102        width: usize,
103        height: usize,
104        cspace: ColorSpace,
105    ) -> Result<Self, &'static str> {
106        Self::new(data.into(), width, height, cspace)
107    }
108
109    /// Create a new [`ImageOwned`] from owned data.
110    ///
111    /// Images can not be larger than 65535x65535 pixels.
112    ///
113    /// # Arguments
114    /// - `data`: Owned data ([`Vec`]).
115    /// - `width`: The width of the image.
116    /// - `height`: The height of the image.
117    /// - `cspace`: The color space of the image ([`ColorSpace`]).
118    ///
119    /// # Errors
120    /// - If the image is too large.
121    /// - If the data is empty.
122    /// - If the width is zero.
123    /// - If the height is zero.
124    /// - If there are too many channels for grayscale/Bayer pattern images.
125    /// - If color space is RGB and number of channels is not 3.
126    pub fn from_owned(
127        data: Vec<T>,
128        width: usize,
129        height: usize,
130        cspace: ColorSpace,
131    ) -> Result<Self, &'static str> {
132        Self::new(data, width, height, cspace)
133    }
134
135    /// Get the underlying data as a slice.
136    pub fn as_slice(&self) -> &[T] {
137        self.data.as_slice()
138    }
139
140    /// Get the underlying data as a mutable slice.
141    pub fn as_mut_slice(&mut self) -> &mut [T] {
142        self.data.as_mut_slice()
143    }
144
145    /// Get the underlying data as a vector.
146    ///
147    /// Note: This function returns a copy of the data.
148    pub fn into_vec(self) -> Vec<T> {
149        self.data.clone()
150    }
151
152    /// Get a raw pointer to the data.
153    pub fn as_ptr(&self) -> *const T {
154        self.data.as_ptr()
155    }
156
157    /// Get a raw mutable pointer to the data.
158    pub fn as_mut_ptr(&mut self) -> *mut T {
159        self.data.as_mut_ptr()
160    }
161
162    /// Get an iterator over the data.
163    pub fn iter(&self) -> std::slice::Iter<T> {
164        self.data.iter()
165    }
166
167    /// Get a mutable iterator over the data.
168    pub fn iter_mut(&mut self) -> std::slice::IterMut<T> {
169        self.data.iter_mut()
170    }
171
172    /// Get a u8 slice of the data.
173    ///
174    /// # Safety
175    /// This function uses [`bytemuck::cast_slice`] to cast the data to a slice of u8.
176    /// As such, it is unsafe, but it is safe to use since the data is vector of
177    /// primitive types.
178    pub fn as_u8_slice(&self) -> &[u8] {
179        bytemuck::cast_slice(self.as_slice())
180    }
181
182    /// Safely get a u8 slice of the data.
183    pub fn as_u8_slice_checked(&self) -> Option<&[u8]> {
184        bytemuck::try_cast_slice(self.as_slice()).ok()
185    }
186}
187
188impl<T: PixelStor> ImageProps for ImageOwned<T> {
189    type OutputU8 = ImageOwned<u8>;
190
191    fn width(&self) -> usize {
192        self.width as usize
193    }
194
195    fn height(&self) -> usize {
196        self.height as usize
197    }
198
199    fn channels(&self) -> u8 {
200        self.channels
201    }
202
203    fn color_space(&self) -> ColorSpace {
204        self.cspace.clone()
205    }
206
207    fn pixel_type(&self) -> PixelType {
208        T::PIXEL_TYPE
209    }
210
211    fn len(&self) -> usize {
212        self.data.len()
213    }
214
215    fn is_empty(&self) -> bool {
216        self.data.is_empty()
217    }
218
219    fn cast_u8(&self) -> Self::OutputU8 {
220        let out = cast_u8(self.data.as_slice());
221        Self::OutputU8 {
222            data: out,
223            width: self.width() as _,
224            height: self.height() as _,
225            cspace: self.cspace.clone(),
226            channels: self.channels(),
227        }
228    }
229}
230
231impl<T: PixelStor + AnyBitPattern> ImageOwned<T> {
232    /// Create a new [`ImageOwned`] from a mutable slice of `u8` data.
233    ///
234    /// Images can not be larger than 65535x65535 pixels.
235    ///
236    /// `data` is cast to the pixel type `T` using [`bytemuck::try_cast_slice_mut`].
237    /// `data` must have length (`width` * `height` * `channels` * `sizeof(T)`), and
238    /// aligned to the size of `T`.
239    ///
240    /// # Safety
241    /// The endianness of the data is determined by the system, and the data is assumed
242    /// to be in native endianness. This function is not safe to use in a cross-platform
243    /// environment.
244    ///
245    /// # Arguments
246    /// - `data`: The [`&mut [u8]`] data slice.
247    /// - `width`: The width of the image.
248    /// - `height`: The height of the image.
249    /// - `cspace`: The color space of the image ([`ColorSpace`]).
250    ///
251    /// # Errors
252    /// - Byte casting errors: [`PodCastError`].
253    /// - If the image is too large.
254    /// - If the data is empty.
255    /// - If the width is zero.
256    /// - If the height is zero.
257    /// - If the data length does not match the image size.
258    /// - If there are too many channels for grayscale/Bayer pattern images.
259    /// - If color space is RGB and number of channels is not 3.
260    pub fn from_u8(
261        data: &[u8],
262        width: usize,
263        height: usize,
264        cspace: ColorSpace,
265    ) -> Result<Self, &'static str> {
266        let data = bytemuck::try_cast_slice(data).map_err(|e| {
267            use PodCastError::*;
268            match e {
269                TargetAlignmentGreaterAndInputNotAligned => {
270                    "Target alignment greater and input not aligned"
271                }
272                OutputSliceWouldHaveSlop => "Output slice would have slop",
273                SizeMismatch => "Size mismatch",
274                AlignmentMismatch => "Alignment mismatch",
275            }
276        })?;
277        Self::from_ref(data, width, height, cspace)
278    }
279}
280
281impl<T: PixelStor + Enlargeable> ToLuma for ImageOwned<T> {
282    fn to_luma(&mut self) -> Result<(), &'static str> {
283        self.to_luma_custom(&[0.299, 0.587, 0.114])
284    }
285
286    fn to_luma_custom(&mut self, coeffs: &[f64]) -> Result<(), &'static str> {
287        // at this point, number of channels must match number of weights
288        match self.cspace {
289            ColorSpace::Gray => Err("Image is already grayscale."),
290            ColorSpace::Rgb | ColorSpace::Custom(_, _) => {
291                crate::coreimpls::run_luma(
292                    self.channels.into(),
293                    self.data.len(),
294                    self.data.as_mut_slice(),
295                    coeffs,
296                )?;
297                self.cspace = ColorSpace::Gray;
298                let len = self.width as usize * self.height as usize;
299                self.channels = 1;
300                self.data.truncate(len);
301                Ok(())
302            }
303            ColorSpace::Bayer(_) => Err("Image is not debayered."),
304        }
305    }
306}
307
308impl<T: PixelStor + Enlargeable> Debayer for ImageOwned<T> {
309    type Output = ImageOwned<T>;
310    fn debayer(&self, alg: DemosaicMethod) -> Result<Self::Output, BayerError> {
311        let cfa = self
312            .cspace
313            .clone()
314            .try_into()
315            .map_err(BayerError::InvalidColorSpace)?;
316        if self.channels != 1 {
317            return Err(BayerError::WrongDepth);
318        }
319        let mut dst = vec![T::zero(); self.width() * self.height() * 3];
320        let mut raster = RasterMut::new(self.width(), self.height(), &mut dst);
321        run_demosaic_imageowned(self, cfa, alg, &mut raster)?;
322        Ok(Self::Output {
323            data: dst,
324            width: self.width,
325            height: self.height,
326            channels: 3,
327            cspace: ColorSpace::Rgb,
328        })
329    }
330}
331
332impl<'a, T: PixelStor> From<&ImageRef<'a, T>> for ImageOwned<T> {
333    fn from(data: &ImageRef<'a, T>) -> Self {
334        Self {
335            data: data.data.to_vec(),
336            width: data.width,
337            height: data.height,
338            channels: data.channels,
339            cspace: data.cspace.clone(),
340        }
341    }
342}
343
344impl<T: PixelStor + Ord> CalcOptExp for ImageOwned<T> {
345    fn calc_opt_exp(
346        mut self,
347        eval: &OptimumExposure,
348        exposure: Duration,
349        bin: u8,
350    ) -> Result<(Duration, u16), &'static str> {
351        let len = self.data.len();
352        eval.calculate(self.data.as_mut_slice(), len, exposure, bin)
353    }
354}
355
356mod test {
357
358    #[test]
359    fn test_into_luma() {
360        use crate::{ColorSpace, ImageOwned, ToLuma};
361        let data = vec![
362            181u8, 178, 118, 183, 85, 131, 82, 143, 196, 108, 64, 33, 174, 43, 18, 236, 19, 179,
363            178, 132, 14, 32, 82, 1, 185, 221, 160, 112, 67, 179, 248, 104, 31, 105, 33, 100, 73,
364            108, 241, 108, 208, 44, 138, 91, 188, 251, 132, 25, 233, 5, 51, 189, 41, 39, 62, 236,
365            71, 150, 85, 11, 46, 95, 108, 228, 36, 187, 144, 203, 34, 218, 116, 207, 111, 168, 181,
366            172, 186, 245, 223, 187, 203, 64, 70, 160, 23, 112, 11, 149, 76, 182, 206, 203, 137,
367            60, 83, 94, 103, 91, 146, 176, 186, 244, 59, 144, 171, 120, 79, 144, 143, 184, 41, 137,
368            4, 141, 70, 167, 51, 212, 39, 219, 102, 206, 124, 10, 92, 159, 193, 115, 132, 156, 58,
369            1, 41, 89, 145, 111, 225, 177, 233, 18, 221, 20, 199, 34, 2, 189, 214, 101, 170, 33,
370            223, 95, 127, 106, 169, 198, 195, 23, 29, 202, 68, 31, 127, 210, 77, 229, 204, 132, 45,
371            70, 241, 160, 14, 25, 125, 10, 25, 171, 1, 13, 212, 188, 143, 139, 13, 138, 17, 128,
372            226, 78, 84, 212, 230, 201, 22, 27, 189, 225, 141, 115, 64, 99, 103, 109, 173, 234,
373            115, 172, 169, 208, 137, 203, 59, 108, 52, 160, 102, 185, 186, 251, 23, 185, 242, 219,
374            195, 242, 75, 202, 153, 198, 102, 103, 151, 228, 211, 57, 178, 26, 254, 38, 47, 189,
375            118, 246, 184, 104, 195, 40, 108, 155, 158, 47, 27, 138, 212, 61, 113, 24, 111, 171,
376            47, 0, 57, 91, 213, 155, 254, 241, 58, 60, 204, 235, 37, 130, 6, 125, 185, 64, 228,
377            242, 117, 52, 215, 126, 115, 50, 147, 203, 220, 192, 175, 137, 40, 191, 17, 191, 122,
378            136, 168, 215, 220, 153, 179, 123, 189, 1, 45, 68, 108, 234, 98, 236, 178, 32, 141, 5,
379            46, 191, 1, 81, 169, 48, 138, 89, 208, 88, 217, 183, 105, 87, 94, 53, 125, 6, 86, 201,
380            11, 65, 227, 101, 221, 47, 97, 15, 192, 191, 231, 199, 119, 47, 24, 44, 33, 207, 100,
381            147, 116, 60, 104, 215, 36, 95, 61, 133, 4, 89, 71, 0, 98, 82, 210, 179, 193, 29, 59,
382            148, 209, 172, 231, 206, 46, 103, 106, 37, 128, 104, 201, 143, 249, 251, 18, 92, 114,
383            92, 211, 129, 153, 168, 90, 133, 78, 254, 169, 125, 36, 26, 190, 126, 212, 77, 219,
384            163, 61, 46, 79, 167, 50, 49, 126, 154, 105, 21, 212, 92, 5, 125, 163, 84, 35, 40, 150,
385            121, 127, 37, 149, 240, 75, 56, 81, 79, 163, 153, 182, 123, 17, 64, 57, 134, 162, 179,
386            148, 228, 179, 71, 15, 116, 249, 39, 15, 39, 2, 171, 103, 64, 19, 192, 101, 235, 119,
387            241, 181, 117, 118, 68, 137, 33, 88, 203, 30, 127, 126, 62, 182, 247, 10, 96, 77, 109,
388            183, 223, 129, 216, 76, 141, 43, 232, 169, 100, 147, 196, 182, 155, 196, 50, 211, 252,
389            220, 231, 60, 252, 64, 230, 193, 29, 217, 164, 137, 113, 149, 93, 20, 86, 10, 220, 54,
390            161, 198, 119, 231, 235, 89, 23, 88, 167, 116, 133, 74, 244, 64, 1, 131, 106, 130, 44,
391            248, 152, 79, 82, 237, 113, 137, 228, 17, 31, 244, 28, 38, 32, 69, 215, 215, 81, 12,
392            215, 172, 73, 199, 219, 74, 103, 244, 217, 171, 60, 50, 252, 147, 100, 26, 28, 72, 162,
393            215, 136, 192, 166, 178, 108, 194, 48, 37, 153, 51, 10, 169, 238, 173, 209, 189, 133,
394            164, 93, 111, 156, 129, 171, 54, 157, 13, 46, 9, 201, 23, 234, 87, 175, 168, 133, 230,
395            114, 90, 214, 240, 69, 90, 27, 199, 158, 150, 100, 94, 204, 35, 103, 216, 120, 122, 43,
396            117, 204, 59, 88, 185, 128, 161, 87, 71, 179, 154, 39, 7, 183, 17, 138, 95, 178, 133,
397            196, 249, 210, 68, 64, 230, 250, 181, 230, 34, 101, 154, 247, 171, 254, 254, 205, 147,
398            54, 250, 48, 174, 237, 81, 201, 170, 28, 166, 185, 52, 57, 128, 110, 64, 64, 64, 204,
399            58, 73, 55, 101, 94, 180, 232, 172, 126, 45, 242, 185, 49, 146, 203, 152, 198, 176,
400            174, 44, 17, 26, 140, 117, 32, 186, 233, 213, 8, 135, 199, 218, 5, 16, 114, 170, 13,
401            91, 171, 247, 88, 158, 95, 220, 127, 126, 12, 3, 124, 198, 134, 151, 21, 98, 200, 157,
402            131, 82, 216, 142, 218, 19, 142, 73, 108, 155, 51, 254, 221, 41, 85, 57, 60, 176,
403        ];
404        let mut img = ImageOwned::from_owned(data, 16, 16, ColorSpace::Rgb).unwrap();
405        img.to_luma().unwrap();
406        let expected = vec![
407            172, 119, 130, 73, 79, 102, 132, 57, 203, 93, 138, 62, 112, 159, 116, 155, 78, 85, 165,
408            95, 81, 110, 166, 156, 152, 188, 199, 78, 73, 109, 196, 77, 100, 189, 121, 98, 155, 59,
409            124, 111, 165, 75, 140, 80, 81, 185, 105, 126, 135, 133, 136, 153, 75, 103, 170, 203,
410            82, 58, 46, 53, 190, 64, 105, 96, 189, 144, 116, 102, 202, 174, 166, 81, 160, 109, 223,
411            139, 173, 145, 116, 161, 138, 193, 94, 144, 113, 87, 138, 43, 183, 112, 203, 56, 118,
412            146, 151, 124, 198, 86, 131, 163, 175, 147, 65, 154, 88, 50, 67, 105, 138, 126, 73, 75,
413            67, 165, 59, 215, 65, 56, 129, 103, 73, 52, 32, 168, 81, 186, 195, 97, 122, 217, 72,
414            166, 154, 114, 128, 133, 133, 89, 127, 106, 67, 44, 102, 113, 76, 122, 89, 166, 49,
415            155, 198, 43, 99, 32, 70, 143, 197, 112, 70, 92, 94, 90, 107, 167, 110, 179, 179, 167,
416            236, 133, 176, 154, 124, 49, 138, 177, 217, 77, 121, 110, 116, 176, 98, 140, 51, 34,
417            171, 55, 116, 120, 219, 76, 105, 69, 166, 166, 90, 76, 209, 188, 116, 141, 109, 41,
418            154, 166, 145, 212, 65, 146, 151, 171, 75, 105, 148, 88, 69, 80, 148, 228, 84, 207, 87,
419            203, 213, 168, 200, 163, 164, 104, 63, 103, 86, 209, 91, 100, 172, 159, 36, 74, 195,
420            182, 23, 68, 206, 128, 113, 96, 131, 164, 111, 172, 97, 105, 99, 72,
421        ];
422        assert_eq!(img.as_slice(), &expected[..]);
423    }
424
425    #[test]
426    fn test_u8_src() {
427        let mut data = vec![181u16, 178, 118, 183, 85, 131];
428        let img =
429            crate::ImageOwned::from_owned(data.clone(), 3, 2, crate::ColorSpace::Gray).unwrap();
430        let data = bytemuck::cast_slice_mut(&mut data);
431        let img2 = crate::ImageOwned::<u16>::from_u8(data, 3, 2, crate::ColorSpace::Gray).unwrap();
432        assert_eq!(img.as_slice(), img2.as_slice());
433    }
434
435    #[test]
436    fn test_optimum_exposure() {
437        use crate::CalcOptExp;
438        let opt_exp = crate::OptimumExposureBuilder::default()
439            .pixel_exclusion(1)
440            .build()
441            .unwrap();
442        let img = vec![0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9];
443        let img = crate::ImageOwned::from_owned(img, 5, 2, crate::ColorSpace::Gray)
444            .expect("Failed to create ImageOwned");
445        let exp = std::time::Duration::from_secs(10); // expected exposure
446        let bin = 1; // expected binning
447        let res = img.calc_opt_exp(&opt_exp, exp, bin).unwrap();
448        assert_eq!(res, (exp, bin as u16));
449    }
450}