Skip to main content

colorutils_rs/
buffer.rs

1/*
2 * // Copyright 2026 (c) the Radzivon Bartoshyk. All rights reserved.
3 * //
4 * // Use of this source code is governed by a BSD-style
5 * // license that can be found in the LICENSE file.
6 */
7use crate::err::ColorError;
8use std::borrow::Cow;
9use std::fmt::Debug;
10use std::ops::{Index, Range, RangeFrom};
11
12pub struct ImageBuffer<'a, F>
13where
14    [F]: ToOwned<Owned = Vec<F>>,
15{
16    pub data: std::borrow::Cow<'a, [F]>,
17    pub width: u32,
18    pub height: u32,
19    pub stride: u32,
20    pub channels: u32,
21}
22
23impl<'a, F: Sized> ImageBuffer<'a, F>
24where
25    [F]: ToOwned<Owned = Vec<F>>,
26{
27    pub fn new(data: &'a [F], width: u32, height: u32, channels: u32) -> Result<Self, ColorError> {
28        let min_stride = width * channels;
29        let required = min_stride as usize * height as usize;
30        if data.len() < required {
31            return Err(ColorError::BufferTooSmall {
32                expected: required,
33                got: data.len(),
34            });
35        }
36        Ok(Self {
37            data: Cow::Borrowed(data),
38            width,
39            height,
40            stride: min_stride,
41            channels,
42        })
43    }
44
45    pub fn from_vec(
46        data: Vec<F>,
47        width: u32,
48        height: u32,
49        channels: u32,
50    ) -> Result<Self, ColorError> {
51        let required = width as usize * channels as usize * height as usize;
52        if data.len() < required {
53            return Err(ColorError::BufferTooSmall {
54                expected: required,
55                got: data.len(),
56            });
57        }
58        Ok(Self {
59            data: Cow::Owned(data),
60            width,
61            height,
62            stride: width * channels,
63            channels,
64        })
65    }
66
67    pub fn try_match(&self, other: &ImageBuffer<'_, F>) -> Result<(), ColorError> {
68        if self.width != other.width || self.height != other.height {
69            return Err(ColorError::DimensionMismatch {
70                expected: (self.width, self.height),
71                got: (other.width, other.height),
72            });
73        }
74        Ok(())
75    }
76
77    pub fn validate(&self) -> Result<(), ColorError> {
78        let min_stride = self
79            .width
80            .checked_mul(self.channels)
81            .ok_or(ColorError::DimensionOverflow)?;
82
83        if self.stride < min_stride {
84            return Err(ColorError::StrideTooNarrow {
85                min: min_stride,
86                given: self.stride,
87            });
88        }
89
90        let required = self
91            .stride()
92            .checked_mul(self.height as usize)
93            .ok_or(ColorError::DimensionOverflow)?;
94
95        if required == 0 {
96            return Err(ColorError::ZeroImageSize);
97        }
98
99        if self.data.len() < required {
100            return Err(ColorError::BufferTooSmall {
101                expected: required,
102                got: self.data.len(),
103            });
104        }
105
106        Ok(())
107    }
108
109    pub fn stride(&self) -> usize {
110        if self.stride == 0 {
111            return self.width as usize * self.channels as usize;
112        }
113        self.stride as usize
114    }
115}
116
117#[derive(Debug)]
118pub enum BufferStore<'a, T: Debug> {
119    Borrowed(&'a mut [T]),
120    Owned(Vec<T>),
121}
122
123impl<T: Copy + Debug> BufferStore<'_, T> {
124    #[allow(clippy::should_implement_trait)]
125    pub fn borrow(&self) -> &[T] {
126        match self {
127            Self::Borrowed(p_ref) => p_ref,
128            Self::Owned(vec) => vec,
129        }
130    }
131
132    #[allow(clippy::should_implement_trait)]
133    pub fn borrow_mut(&mut self) -> &mut [T] {
134        match self {
135            Self::Borrowed(p_ref) => p_ref,
136            Self::Owned(vec) => vec,
137        }
138    }
139}
140
141impl<T: Copy + Debug> Index<usize> for BufferStore<'_, T> {
142    type Output = T;
143
144    fn index(&self, index: usize) -> &Self::Output {
145        match self {
146            Self::Borrowed(p_ref) => &p_ref[index],
147            Self::Owned(vec) => &vec[index],
148        }
149    }
150}
151
152impl<T: Copy + Debug> Index<Range<usize>> for BufferStore<'_, T> {
153    type Output = [T];
154
155    fn index(&self, index: Range<usize>) -> &Self::Output {
156        match self {
157            Self::Borrowed(p_ref) => &p_ref[index],
158            Self::Owned(vec) => &vec[index],
159        }
160    }
161}
162
163impl<T: Copy + Debug> Index<RangeFrom<usize>> for BufferStore<'_, T> {
164    type Output = [T];
165
166    fn index(&self, index: RangeFrom<usize>) -> &Self::Output {
167        match self {
168            Self::Borrowed(p_ref) => &p_ref[index],
169            Self::Owned(vec) => &vec[index],
170        }
171    }
172}
173
174#[derive(Debug)]
175pub struct ImageBufferMut<'a, F: Copy + Debug> {
176    pub data: BufferStore<'a, F>,
177    pub width: u32,
178    pub height: u32,
179    pub stride: u32,
180    pub channels: u32,
181}
182
183impl<'a, F: Copy + Debug + Sized> ImageBufferMut<'a, F> {
184    pub fn new(
185        data: BufferStore<'a, F>,
186        width: u32,
187        height: u32,
188        channels: u32,
189    ) -> Result<Self, ColorError> {
190        let buf = Self {
191            data,
192            width,
193            height,
194            stride: width * channels,
195            channels,
196        };
197        buf.validate()?;
198        Ok(buf)
199    }
200
201    pub fn validate(&self) -> Result<(), ColorError> {
202        let min_stride = self
203            .width
204            .checked_mul(self.channels)
205            .ok_or(ColorError::DimensionOverflow)?;
206
207        if self.stride < min_stride {
208            return Err(ColorError::StrideTooNarrow {
209                min: min_stride,
210                given: self.stride,
211            });
212        }
213
214        let required = self
215            .stride()
216            .checked_mul(self.height as usize)
217            .ok_or(ColorError::DimensionOverflow)?;
218
219        if required == 0 {
220            return Err(ColorError::ZeroImageSize);
221        }
222
223        if self.data.borrow().len() < required {
224            return Err(ColorError::BufferTooSmall {
225                expected: required,
226                got: self.data.borrow().len(),
227            });
228        }
229
230        Ok(())
231    }
232
233    pub fn try_match(&self, other: &ImageBufferMut<'_, F>) -> Result<(), ColorError> {
234        if self.width != other.width || self.height != other.height {
235            return Err(ColorError::DimensionMismatch {
236                expected: (self.width, self.height),
237                got: (other.width, other.height),
238            });
239        }
240        Ok(())
241    }
242
243    pub fn try_match_immutable<Z>(&self, other: &ImageBuffer<'_, Z>) -> Result<(), ColorError>
244    where
245        [Z]: ToOwned<Owned = Vec<Z>>,
246    {
247        if self.width != other.width || self.height != other.height {
248            return Err(ColorError::DimensionMismatch {
249                expected: (self.width, self.height),
250                got: (other.width, other.height),
251            });
252        }
253        Ok(())
254    }
255
256    pub fn try_match_immutable_with_channels<Z>(
257        &self,
258        other: &ImageBuffer<'_, Z>,
259    ) -> Result<(), ColorError>
260    where
261        [Z]: ToOwned<Owned = Vec<Z>>,
262    {
263        if self.width != other.width || self.height != other.height {
264            return Err(ColorError::DimensionMismatch {
265                expected: (self.width, self.height),
266                got: (other.width, other.height),
267            });
268        }
269        if self.channels != other.channels {
270            return Err(ColorError::ChannelCountMismatch {
271                expected: self.channels,
272                got: other.channels,
273            });
274        }
275        Ok(())
276    }
277
278    pub fn stride(&self) -> usize {
279        if self.stride == 0 {
280            return self.width as usize * self.channels as usize;
281        }
282        self.stride as usize
283    }
284}