1use binrw::{BinRead, BinResult, BinWrite, VecArgs, binrw};
2use getset::{Getters, Setters};
3use image::{DynamicImage, GenericImageView, ImageBuffer};
4
5use crate::{ColorId, PixelEndian};
6
7#[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#[derive(Clone)]
19pub enum Pixels {
20 Rgb8(Vec<u8>),
21 Rgb16(Vec<u16>),
22 Luma8(Vec<u8>),
23 Luma16(Vec<u16>),
24}
25
26#[derive(Clone)]
28pub struct Frame((u32, u32), Pixels);
29
30#[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 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}