Skip to main content

ser_file/
format.rs

1use binrw::{BinRead, BinResult, BinWrite, VecArgs, binrw};
2use getset::{Getters, Setters};
3use image::{DynamicImage, GenericImageView, ImageBuffer};
4
5use crate::{ColorId, PixelEndian};
6
7/// Describes the primitive type in which each pixel channel is stored.
8#[binrw]
9#[br(map(i32::into))]
10#[bw(map(i32::from))]
11#[derive(Debug, Clone)]
12pub enum PixelDepth {
13    U8(i32),
14    U16(i32),
15}
16
17/// Container for different pixel formats
18#[derive(Clone)]
19pub enum Pixels {
20    Rgb8(Vec<u8>),
21    Rgb16(Vec<u16>),
22    Luma8(Vec<u8>),
23    Luma16(Vec<u16>),
24}
25
26/// A tuple struct containing the dimensions of an image and its pixels
27#[derive(Clone)]
28pub struct Frame((u32, u32), Pixels);
29
30/// The format defining all images in a SER file.
31///
32/// Only one format per SER file is supported. All images must have the same
33/// width, height, bytes per pixel, etc.
34///
35/// Once a format is defined, SER frames may be converted to/from
36/// [image::DynamicImage]s. Furthermore, a [FrameFormat] may be computed from an
37/// existing [DynamicImage] using [TryFrom].
38#[derive(Clone, Getters, Setters)]
39#[getset(get = "pub", set = "pub")]
40pub struct FrameFormat {
41    color: ColorId,
42    depth: PixelDepth,
43    endian: PixelEndian,
44    width: u32,
45    height: u32,
46}
47
48#[derive(Debug, Clone)]
49enum PixelChannels {
50    Rgb,
51    Luma,
52}
53
54impl FrameFormat {
55    pub fn new(
56        color: ColorId,
57        depth: PixelDepth,
58        endian: PixelEndian,
59        width: u32,
60        height: u32,
61    ) -> Self {
62        Self {
63            color,
64            depth,
65            endian,
66            width,
67            height,
68        }
69    }
70
71    pub fn raw_len(&self) -> usize {
72        let channels: PixelChannels = (&self.color).into();
73        channels.len() * self.width as usize * self.height as usize
74    }
75
76    /// Attempt to convert a [DynamicImage] into a [Frame] that is compatible
77    /// with this [FrameFormat].
78    pub fn try_into_frame(&self, img: DynamicImage) -> Result<Frame, &'static str> {
79        if img.width() != self.width || img.height() != self.height {
80            return Err(
81                "Incompatible image dimensions. All frames must have the same width and height.",
82            );
83        }
84
85        let pixels = match ((&self.color).into(), &self.depth) {
86            (PixelChannels::Luma, PixelDepth::U8(_)) => Pixels::Luma8(img.to_luma8().into_raw()),
87            (PixelChannels::Luma, PixelDepth::U16(_)) => Pixels::Luma16(img.to_luma16().into_raw()),
88            (PixelChannels::Rgb, PixelDepth::U8(_)) => Pixels::Rgb8(img.to_rgb8().into_raw()),
89            (PixelChannels::Rgb, PixelDepth::U16(_)) => Pixels::Rgb16(img.to_rgb16().into_raw()),
90        };
91        Ok(Frame((self.width, self.height), pixels))
92    }
93}
94
95impl PixelChannels {
96    fn len(&self) -> usize {
97        match self {
98            PixelChannels::Rgb => 3,
99            PixelChannels::Luma => 1,
100        }
101    }
102}
103
104impl Pixels {
105    fn len(&self) -> usize {
106        match self {
107            Pixels::Rgb8(items) => items.len(),
108            Pixels::Rgb16(items) => items.len(),
109            Pixels::Luma8(items) => items.len(),
110            Pixels::Luma16(items) => items.len(),
111        }
112    }
113}
114
115impl TryFrom<&DynamicImage> for FrameFormat {
116    type Error = &'static str;
117
118    fn try_from(value: &DynamicImage) -> Result<FrameFormat, Self::Error> {
119        let (width, height) = value.dimensions();
120        match value {
121            DynamicImage::ImageRgb8(_) => Ok(FrameFormat::new(
122                ColorId::RGB,
123                PixelDepth::U8(8),
124                PixelEndian::host_endian(),
125                width,
126                height,
127            )),
128            DynamicImage::ImageRgb16(_) => Ok(FrameFormat::new(
129                ColorId::RGB,
130                PixelDepth::U16(16),
131                PixelEndian::host_endian(),
132                width,
133                height,
134            )),
135            DynamicImage::ImageLuma8(_) => Ok(FrameFormat::new(
136                ColorId::MONO,
137                PixelDepth::U8(8),
138                PixelEndian::host_endian(),
139                width,
140                height,
141            )),
142            DynamicImage::ImageLuma16(_) => Ok(FrameFormat::new(
143                ColorId::MONO,
144                PixelDepth::U16(16),
145                PixelEndian::host_endian(),
146                width,
147                height,
148            )),
149            _ => Err("Unsupported image type."),
150        }
151    }
152}
153
154impl TryFrom<Frame> for DynamicImage {
155    type Error = &'static str;
156
157    fn try_from(value: Frame) -> Result<Self, Self::Error> {
158        let Frame((width, height), pixels) = value;
159        match match pixels {
160            Pixels::Rgb8(p) => ImageBuffer::<image::Rgb<u8>, Vec<u8>>::from_raw(width, height, p)
161                .and_then(|buf| Some(buf.into())),
162            Pixels::Rgb16(p) => {
163                ImageBuffer::<image::Rgb<u16>, Vec<u16>>::from_raw(width, height, p)
164                    .and_then(|buf| Some(buf.into()))
165            }
166            Pixels::Luma8(p) => ImageBuffer::<image::Luma<u8>, Vec<u8>>::from_raw(width, height, p)
167                .and_then(|buf| Some(buf.into())),
168            Pixels::Luma16(p) => {
169                ImageBuffer::<image::Luma<u16>, Vec<u16>>::from_raw(width, height, p)
170                    .and_then(|buf| Some(buf.into()))
171            }
172        } {
173            Some(img) => Ok(img),
174            None => Err("Unable to convert frame"),
175        }
176    }
177}
178
179impl BinRead for Frame {
180    type Args<'a> = FrameFormat;
181
182    fn read_options<R: std::io::Read + std::io::Seek>(
183        reader: &mut R,
184        endian: binrw::Endian,
185        args: Self::Args<'_>,
186    ) -> BinResult<Self> {
187        let channels: PixelChannels = (&args.color).into();
188        let vec_args = VecArgs::builder()
189            .count(channels.len() * args.width as usize * args.height as usize)
190            .finalize();
191        let pixels = match (channels, args.depth) {
192            (PixelChannels::Luma, PixelDepth::U8(_)) => {
193                Pixels::Luma8(<Vec<u8>>::read_options(reader, endian, vec_args)?)
194            }
195            (PixelChannels::Luma, PixelDepth::U16(_)) => {
196                Pixels::Luma16(<Vec<u16>>::read_options(reader, endian, vec_args)?)
197            }
198            (PixelChannels::Rgb, PixelDepth::U8(_)) => {
199                Pixels::Rgb8(<Vec<u8>>::read_options(reader, endian, vec_args)?)
200            }
201            (PixelChannels::Rgb, PixelDepth::U16(_)) => {
202                Pixels::Rgb16(<Vec<u16>>::read_options(reader, endian, vec_args)?)
203            }
204        };
205
206        Ok(Frame((args.width, args.height), pixels))
207    }
208}
209
210impl BinWrite for Frame {
211    type Args<'a> = ();
212
213    fn write_options<W: std::io::Write + std::io::Seek>(
214        &self,
215        writer: &mut W,
216        endian: binrw::Endian,
217        _args: Self::Args<'_>,
218    ) -> BinResult<()> {
219        let Frame(_, pixels) = self;
220        match pixels {
221            Pixels::Rgb8(p) => p.write_options(writer, endian, ()),
222            Pixels::Rgb16(p) => p.write_options(writer, endian, ()),
223            Pixels::Luma8(p) => p.write_options(writer, endian, ()),
224            Pixels::Luma16(p) => p.write_options(writer, endian, ()),
225        }
226    }
227}
228
229impl From<&ColorId> for PixelChannels {
230    fn from(value: &ColorId) -> Self {
231        match value {
232            ColorId::RGB => PixelChannels::Rgb,
233            ColorId::BGR => PixelChannels::Rgb,
234            _ => PixelChannels::Luma,
235        }
236    }
237}
238
239impl From<i32> for PixelDepth {
240    fn from(value: i32) -> Self {
241        match value {
242            v if v > 8 => PixelDepth::U16(v),
243            v => PixelDepth::U8(v),
244        }
245    }
246}
247
248impl From<&PixelDepth> for i32 {
249    fn from(value: &PixelDepth) -> Self {
250        match value {
251            PixelDepth::U8(v) => *v,
252            PixelDepth::U16(v) => *v,
253        }
254    }
255}
256
257impl PartialEq<Frame> for FrameFormat {
258    fn eq(&self, frame: &Frame) -> bool {
259        let Frame((w, h), p) = frame;
260        &self.width == w && &self.height == h && self.raw_len() == p.len() && p == self.depth
261    }
262}
263
264impl PartialEq<PixelDepth> for &Pixels {
265    fn eq(&self, depth: &PixelDepth) -> bool {
266        match self {
267            Pixels::Rgb8(_) => matches!(depth, PixelDepth::U8(_)),
268            Pixels::Rgb16(_) => matches!(depth, PixelDepth::U16(_)),
269            Pixels::Luma8(_) => matches!(depth, PixelDepth::U8(_)),
270            Pixels::Luma16(_) => matches!(depth, PixelDepth::U16(_)),
271        }
272    }
273}