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