refimage/
genericimage.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
use std::collections::HashMap;
use std::time::{Duration, SystemTime};

use serde::Serialize;

use crate::imagetraits::ImageProps;
use crate::metadata::InsertValue;
use crate::{genericimageowned::GenericImageOwned, genericimageref::GenericImageRef};
use crate::{
    BayerError, CalcOptExp, ColorSpace, Debayer, DemosaicMethod, GenericLineItem, OptimumExposure,
    PixelType, ToLuma,
};

#[derive(Debug, PartialEq, Serialize)]
/// A serializable, generic image with metadata, backed by either
/// a [`GenericImageRef`] or a [`GenericImageOwned`].
pub enum GenericImage<'a> {
    /// Holds a [`GenericImageRef`].
    Ref(GenericImageRef<'a>),
    /// Holds a [`GenericImageOwned`].
    Own(GenericImageOwned),
}

impl Clone for GenericImage<'_> {
    fn clone(&self) -> Self {
        match self {
            GenericImage::Ref(data) => {
                let meta = data.metadata.clone();
                GenericImage::Own(GenericImageOwned {
                    metadata: meta,
                    image: (&data.image).into(),
                })
            }
            GenericImage::Own(data) => GenericImage::Own(data.clone()),
        }
    }
}

macro_rules! dynamic_map(
    ($dynimage: expr, $image: pat => $action: expr) => ({
        use GenericImage::*;
        match $dynimage {
            Ref($image) => Ref($action),
            Own($image) => Own($action),
        }
    });

    ($dynimage: expr, $image:pat_param, $action: expr) => (
        match $dynimage {
            GenericImage::Ref($image) => $action,
            GenericImage::Own($image) => $action,
        }
    );
);

impl GenericImage<'_> {
    /// Get the timestamp of the image.
    pub fn get_timestamp(&self) -> SystemTime {
        dynamic_map!(self, ref image, { image.get_timestamp() })
    }

    /// Get the exposure time of the image.
    pub fn get_exposure(&self) -> Option<Duration> {
        dynamic_map!(self, ref image, { image.get_exposure() })
    }

    /// Insert a metadata value into the [`GenericImage`].
    ///
    /// # Arguments
    /// - `name`: The name of the metadata value. The name must be non-empty and less than 80 characters.
    /// - `value`: The value to insert. The value is either a primitive type, a `String`, or a `std::time::Duration` or `std::time::SystemTime` or a tuple of a primitive type and a comment ().
    /// # Valid Types
    /// The valid types for the metadata value are:
    /// - [`u8`] | [`u16`] | [`u32`] | [`u64`]
    /// - [`i8`] | [`i16`] | [`i32`] | [`i64`]
    /// - [`f32`] | [`f64`]
    /// - [`ColorSpace`]
    /// - [`std::time::Duration`] | [`std::time::SystemTime`]
    /// - [`String`] | [`&str`]
    ///
    /// # Note
    /// - The metadata key is case-insensitive and is stored as an uppercase string.
    /// - Re-inserting a timestamp key will return an error.
    /// - When saving to a FITS file, the metadata comment may be truncated.
    /// - Metadata of type [`std::time::Duration`] or [`std::time::SystemTime`] is split
    ///   and stored as two consecutive metadata items, with the same key, split into
    ///   seconds ([`u64`]) and nanoseconds ([`u64`]).
    pub fn insert_key<T: InsertValue>(&mut self, name: &str, value: T) -> Result<(), &'static str> {
        dynamic_map!(self, ref mut image, { image.insert_key(name, value) })
    }

    /// Remove a metadata value from the [`GenericImageOwned`].
    ///
    /// # Arguments
    /// - `name`: The name of the metadata value to remove.
    ///
    /// # Returns
    /// - `Ok(())` if the key was removed successfully.
    /// - `Err("Can not remove timestamp key")` if the key is the timestamp key.
    /// - `Err("Key not found")` if the key was not found.
    /// - `Err("Key cannot be empty")` if the key is an empty string.
    /// - `Err("Key cannot be longer than 80 characters")` if the key is longer than 80 characters.
    pub fn remove_key(&mut self, name: &str) -> Result<(), &'static str> {
        dynamic_map!(self, ref mut image, { image.remove_key(name) })
    }

    /// Replace a metadata value in the [`GenericImageOwned`].
    ///
    /// # Arguments
    /// - `name`: The name of the metadata value to replace.
    /// - `value`: The new value to insert. The value is either a primitive type, a `String`, or a `std::time::Duration` or `std::time::SystemTime` or a tuple of a value type and a comment.
    ///
    /// # Returns
    /// - `Ok(())` if the key was replaced successfully.
    /// - `Err("Key not found")` if the key was not found.
    ///
    pub fn replace_key<T: InsertValue>(
        &mut self,
        name: &str,
        value: T,
    ) -> Result<(), &'static str> {
        dynamic_map!(self, ref mut image, { image.replace_key(name, value) })
    }

    // /// Get the underlying [`DynamicImageOwned`].
    // ///
    // /// # Returns
    // /// The underlying [`DynamicImageOwned`] of the [`GenericImageOwned`].
    // pub fn get_image(&self) -> &DynamicImageOwned {
    //     dynamic_map!(self, ref image => image.get_image())
    // }

    /// Get the contained metadata as a slice of [`GenericLineItem`]s.
    ///
    /// # Returns
    /// A slice of [`GenericLineItem`]s containing the metadata.
    pub fn get_metadata(&self) -> &HashMap<String, GenericLineItem> {
        dynamic_map!(self, ref image, { image.get_metadata() })
    }

    /// Get a specific metadata value by name.
    ///
    /// Returns the first metadata value with the given name.
    ///
    /// # Arguments
    /// - `name`: The name of the metadata value.
    pub fn get_key(&self, name: &str) -> Option<&GenericLineItem> {
        dynamic_map!(self, ref image, { image.get_key(name) })
    }
}

impl ImageProps for GenericImage<'_> {
    type OutputU8 = GenericImageOwned;

    fn width(&self) -> usize {
        dynamic_map!(self, ref image, { image.image.width() })
    }

    fn height(&self) -> usize {
        dynamic_map!(self, ref image, { image.image.height() })
    }

    fn channels(&self) -> u8 {
        dynamic_map!(self, ref image, { image.image.channels() })
    }

    fn color_space(&self) -> ColorSpace {
        dynamic_map!(self, ref image, { image.image.color_space() })
    }

    fn pixel_type(&self) -> PixelType {
        dynamic_map!(self, ref image, { image.image.pixel_type() })
    }

    fn len(&self) -> usize {
        dynamic_map!(self, ref image, { image.image.len() })
    }

    fn is_empty(&self) -> bool {
        dynamic_map!(self, ref image, { image.image.is_empty() })
    }

    fn cast_u8(&self) -> Self::OutputU8 {
        let meta = self.get_metadata().clone();
        match self {
            GenericImage::Ref(image) => GenericImageOwned {
                metadata: meta,
                image: image.image.into_u8(),
            },
            GenericImage::Own(image) => GenericImageOwned {
                metadata: meta,
                image: image.image.clone().into_u8(),
            },
        }
    }
}

impl From<GenericImageOwned> for GenericImage<'_> {
    fn from(img: GenericImageOwned) -> Self {
        Self::Own(img)
    }
}

impl<'a> From<GenericImageRef<'a>> for GenericImage<'a> {
    fn from(img: GenericImageRef<'a>) -> Self {
        Self::Ref(img)
    }
}

impl From<GenericImage<'_>> for GenericImageOwned {
    fn from(val: GenericImage<'_>) -> Self {
        match val {
            GenericImage::Own(data) => data,
            GenericImage::Ref(data) => data.into(),
        }
    }
}

impl<'a> TryInto<GenericImageRef<'a>> for GenericImage<'a> {
    type Error = &'static str;

    fn try_into(self) -> Result<GenericImageRef<'a>, Self::Error> {
        match self {
            GenericImage::Ref(data) => Ok(data),
            _ => Err("Image is not GenericImageRef."),
        }
    }
}

impl<'a: 'b, 'b> ToLuma<'a, 'b> for GenericImage<'b> {
    type Output = GenericImageOwned;

    fn to_luma(&'b self) -> Result<Self::Output, &'static str> {
        match self {
            GenericImage::Ref(image) => Ok(image.to_luma()?),
            GenericImage::Own(image) => Ok(image.to_luma()?),
        }
    }

    fn to_luma_alpha(&'b self) -> Result<Self::Output, &'static str> {
        match self {
            GenericImage::Ref(image) => Ok(image.to_luma_alpha()?),
            GenericImage::Own(image) => Ok(image.to_luma_alpha()?),
        }
    }

    fn to_luma_custom(&'b self, coeffs: [f64; 3]) -> Result<Self::Output, &'static str> {
        match self {
            GenericImage::Ref(image) => Ok(image.to_luma_custom(coeffs)?),
            GenericImage::Own(image) => Ok(image.to_luma_custom(coeffs)?),
        }
    }

    fn to_luma_alpha_custom(&'b self, coeffs: [f64; 3]) -> Result<Self::Output, &'static str> {
        match self {
            GenericImage::Ref(image) => Ok(image.to_luma_alpha_custom(coeffs)?),
            GenericImage::Own(image) => Ok(image.to_luma_alpha_custom(coeffs)?),
        }
    }
}

macro_rules! impl_toluma {
    ($inp: ty, $mid: ty) => {
        impl<'a: 'b, 'b> ToLuma<'a, 'b> for $inp {
            type Output = GenericImageOwned;

            fn to_luma(&'a self) -> Result<Self::Output, &'static str> {
                let img = self.get_image().to_luma()?;
                let meta = self.metadata.clone();
                Ok(Self::Output {
                    metadata: meta,
                    image: img,
                })
            }

            fn to_luma_alpha(&'a self) -> Result<Self::Output, &'static str> {
                let img = self.get_image().to_luma_alpha()?;
                let meta = self.metadata.clone();
                Ok(Self::Output {
                    metadata: meta,
                    image: img,
                })
            }

            fn to_luma_custom(&'a self, coeffs: [f64; 3]) -> Result<Self::Output, &'static str> {
                let img = self.get_image().to_luma_custom(coeffs)?;
                let meta = self.metadata.clone();
                Ok(Self::Output {
                    metadata: meta,
                    image: img,
                })
            }

            fn to_luma_alpha_custom(
                &'a self,
                coeffs: [f64; 3],
            ) -> Result<Self::Output, &'static str> {
                let img = self.get_image().to_luma_alpha_custom(coeffs)?;
                let meta = self.metadata.clone();
                Ok(Self::Output {
                    metadata: meta,
                    image: img,
                })
            }
        }
    };
}

impl_toluma!(GenericImageRef<'_>, DynamicImageRef<'_>);
impl_toluma!(GenericImageOwned, DynamicImageOwned);

impl<'a: 'b, 'b> Debayer<'a, 'b> for GenericImage<'b> {
    type Output = GenericImage<'a>;
    fn debayer(&'b self, method: DemosaicMethod) -> Result<Self::Output, BayerError> {
        match self {
            GenericImage::Ref(image) => Ok(image.debayer(method)?.into()),
            GenericImage::Own(image) => Ok(image.debayer(method)?.into()),
        }
    }
}

impl CalcOptExp for GenericImage<'_> {
    fn calc_opt_exp(
        self,
        eval: &OptimumExposure,
        exposure: Duration,
        bin: u8,
    ) -> Result<(Duration, u16), &'static str> {
        match self {
            GenericImage::Ref(img) => img.calc_opt_exp(eval, exposure, bin),
            GenericImage::Own(img) => img.calc_opt_exp(eval, exposure, bin),
        }
    }
}

impl GenericImage<'_> {
    /// Get the data as a slice of `u8`, regardless of the underlying type.
    pub fn as_raw_u8(&self) -> &[u8] {
        dynamic_map!(self, ref image, { image.image.as_raw_u8() })
    }

    /// Get the data as a slice of `u8`, regardless of the underlying type.
    pub fn as_raw_u8_checked(&self) -> Option<&[u8]> {
        dynamic_map!(self, ref image, { image.image.as_raw_u8_checked() })
    }

    /// Get the data as a slice of `u8`.
    pub fn as_slice_u8(&self) -> Option<&[u8]> {
        dynamic_map!(self, ref image, { image.image.as_slice_u8() })
    }

    /// Get the data as a mutable slice of `u8`.
    pub fn as_mut_slice_u8(&mut self) -> Option<&mut [u8]> {
        dynamic_map!(self, ref mut image, { image.image.as_mut_slice_u8() })
    }

    /// Get the data as a slice of `u16`.
    pub fn as_slice_u16(&self) -> Option<&[u16]> {
        dynamic_map!(self, ref image, { image.image.as_slice_u16() })
    }

    /// Get the data as a mutable slice of `u16`.
    pub fn as_mut_slice_u16(&mut self) -> Option<&mut [u16]> {
        dynamic_map!(self, ref mut image, { image.image.as_mut_slice_u16() })
    }

    /// Get the data as a slice of `f32`.
    pub fn as_slice_f32(&self) -> Option<&[f32]> {
        dynamic_map!(self, ref image, { image.image.as_slice_f32() })
    }

    /// Get the data as a mutable slice of `f32`.
    pub fn as_mut_slice_f32(&mut self) -> Option<&mut [f32]> {
        dynamic_map!(self, ref mut image, { image.image.as_mut_slice_f32() })
    }
}