1use core::marker::PhantomData;
13
14use crate::{Dimensions, Error, Pixel, Result};
15
16fn expected_len<P: Pixel>(dims: Dimensions) -> Result<usize> {
19 if dims.is_empty() {
20 return Err(Error::InvalidInput("zero-sized image"));
21 }
22 dims.sample_count(P::CHANNELS)
23 .ok_or(Error::InvalidInput("image dimensions overflow usize"))
24}
25
26#[derive(Debug, Clone, Copy)]
31pub struct ImageRef<'a, P: Pixel> {
32 data: &'a [P::Sample],
33 dims: Dimensions,
34 _p: PhantomData<P>,
35}
36
37impl<'a, P: Pixel> ImageRef<'a, P> {
38 pub fn new(data: &'a [P::Sample], dims: Dimensions) -> Result<Self> {
46 let want = expected_len::<P>(dims)?;
47 if data.len() != want {
48 return Err(Error::InvalidInput(
49 "image buffer length does not match dimensions",
50 ));
51 }
52 Ok(Self {
53 data,
54 dims,
55 _p: PhantomData,
56 })
57 }
58
59 #[must_use]
61 pub fn dimensions(self) -> Dimensions {
62 self.dims
63 }
64
65 #[must_use]
67 pub fn width(self) -> u32 {
68 self.dims.width
69 }
70
71 #[must_use]
73 pub fn height(self) -> u32 {
74 self.dims.height
75 }
76
77 #[must_use]
80 pub fn as_samples(self) -> &'a [P::Sample] {
81 self.data
82 }
83
84 #[must_use]
90 pub fn row(self, y: u32) -> &'a [P::Sample] {
91 let row_len = self.dims.width as usize * P::CHANNELS;
92 let start = y as usize * row_len;
93 &self.data[start..start + row_len]
94 }
95
96 #[must_use]
98 pub fn rows(self) -> impl ExactSizeIterator<Item = &'a [P::Sample]> {
99 let row_len = self.dims.width as usize * P::CHANNELS;
100 self.data.chunks_exact(row_len)
101 }
102
103 #[must_use]
109 pub fn pixel(self, x: u32, y: u32) -> &'a [P::Sample] {
110 let i = (y as usize * self.dims.width as usize + x as usize) * P::CHANNELS;
111 &self.data[i..i + P::CHANNELS]
112 }
113}
114
115#[derive(Debug, Clone)]
120#[must_use]
121pub struct ImageBuf<P: Pixel> {
122 data: Vec<P::Sample>,
123 dims: Dimensions,
124 _p: PhantomData<P>,
125}
126
127impl<P: Pixel> ImageBuf<P> {
128 pub fn new(data: Vec<P::Sample>, dims: Dimensions) -> Result<Self> {
136 ImageRef::<P>::new(&data, dims)?;
138 Ok(Self {
139 data,
140 dims,
141 _p: PhantomData,
142 })
143 }
144
145 pub fn zeroed(dims: Dimensions) -> Result<Self> {
151 let want = expected_len::<P>(dims)?;
152 Ok(Self {
153 data: vec![P::Sample::default(); want],
154 dims,
155 _p: PhantomData,
156 })
157 }
158
159 #[must_use]
161 pub fn as_ref(&self) -> ImageRef<'_, P> {
162 ImageRef {
163 data: &self.data,
164 dims: self.dims,
165 _p: PhantomData,
166 }
167 }
168
169 #[must_use]
171 pub fn dimensions(&self) -> Dimensions {
172 self.dims
173 }
174
175 #[must_use]
177 pub fn as_samples(&self) -> &[P::Sample] {
178 &self.data
179 }
180
181 #[must_use]
183 pub fn into_samples(self) -> Vec<P::Sample> {
184 self.data
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191 use crate::{Rgb8, Rgb16};
192
193 fn dims(w: u32, h: u32) -> Dimensions {
194 Dimensions {
195 width: w,
196 height: h,
197 }
198 }
199
200 #[test]
201 fn new_validates_length() {
202 let rgb = vec![0u8; 2 * 2 * 3];
203 let img = ImageRef::<Rgb8>::new(&rgb, dims(2, 2)).unwrap();
204 assert_eq!(img.dimensions(), dims(2, 2));
205 assert_eq!((img.width(), img.height()), (2, 2));
206 assert_eq!(img.as_samples().len(), 12);
207 assert!(ImageRef::<Rgb8>::new(&rgb[..11], dims(2, 2)).is_err());
209 let long = vec![0u8; 13];
210 assert!(ImageRef::<Rgb8>::new(&long, dims(2, 2)).is_err());
211 }
212
213 #[test]
214 fn new_rejects_zero_sized() {
215 let empty: [u8; 0] = [];
216 assert!(ImageRef::<Rgb8>::new(&empty, dims(0, 4)).is_err());
217 assert!(ImageRef::<Rgb8>::new(&empty, dims(4, 0)).is_err());
218 }
219
220 #[test]
221 fn rejects_overflowing_dimensions() {
222 let big = dims(u32::MAX, u32::MAX);
225 assert!(ImageRef::<Rgb8>::new(&[], big).is_err());
226 assert!(ImageBuf::<Rgb8>::zeroed(big).is_err());
227 }
228
229 #[test]
230 fn row_and_pixel_access() {
231 let mut rgb = vec![0u8; 3 * 2 * 3];
233 for y in 0..2u32 {
234 for x in 0..3u32 {
235 let i = (y as usize * 3 + x as usize) * 3;
236 rgb[i] = x as u8;
237 rgb[i + 1] = y as u8;
238 rgb[i + 2] = 0xAA;
239 }
240 }
241 let img = ImageRef::<Rgb8>::new(&rgb, dims(3, 2)).unwrap();
242 assert_eq!(img.row(1).len(), 9);
243 assert_eq!(img.pixel(2, 1), &[2, 1, 0xAA]);
244 assert_eq!(img.pixel(0, 0), &[0, 0, 0xAA]);
245 let rows: Vec<_> = img.rows().collect();
246 assert_eq!(rows.len(), 2);
247 assert!(rows.iter().all(|r| r.len() == 9));
248 }
249
250 #[test]
251 fn high_bit_depth_samples() {
252 let rgb16 = vec![1000u16; 6];
254 let img = ImageRef::<Rgb16>::new(&rgb16, dims(2, 1)).unwrap();
255 assert_eq!(img.as_samples().len(), 6);
256 assert_eq!(img.pixel(1, 0), &[1000, 1000, 1000]);
257 assert!(ImageRef::<Rgb16>::new(&[0u16; 5], dims(2, 1)).is_err());
259 }
260
261 #[test]
262 fn owned_buffer_roundtrips() {
263 let buf = ImageBuf::<Rgb8>::new(vec![7u8; 12], dims(2, 2)).unwrap();
264 assert_eq!(buf.dimensions(), dims(2, 2));
265 assert_eq!(buf.as_samples().len(), 12);
266 assert_eq!(buf.as_ref().pixel(0, 0), &[7, 7, 7]);
267 assert_eq!(buf.into_samples(), vec![7u8; 12]);
268 assert!(ImageBuf::<Rgb8>::new(vec![0u8; 11], dims(2, 2)).is_err());
270 }
271
272 #[test]
273 fn zeroed_is_all_default_and_correct_length() {
274 let buf = ImageBuf::<Rgb8>::zeroed(dims(4, 3)).unwrap();
275 assert_eq!(buf.as_samples().len(), 4 * 3 * 3);
276 assert!(buf.as_samples().iter().all(|&s| s == 0));
277 assert!(ImageBuf::<Rgb8>::zeroed(dims(0, 3)).is_err());
278 }
279}