1use crate::{Color, Size};
4use std::hash::{Hash, Hasher};
5use std::sync::atomic::{AtomicU64, Ordering};
6use std::sync::Arc;
7use thiserror::Error;
8
9static NEXT_IMAGE_BITMAP_ID: AtomicU64 = AtomicU64::new(1);
10
11#[derive(Debug, Clone, PartialEq, Eq, Error)]
13pub enum ImageBitmapError {
14 #[error("image dimensions must be greater than zero")]
15 InvalidDimensions,
16 #[error("image dimensions are too large")]
17 DimensionsTooLarge,
18 #[error("pixel data length mismatch: expected {expected} bytes, got {actual}")]
19 PixelDataLengthMismatch { expected: usize, actual: usize },
20}
21
22#[derive(Clone, Debug)]
24pub struct ImageBitmap {
25 id: u64,
26 width: u32,
27 height: u32,
28 pixels: Arc<[u8]>,
29}
30
31#[derive(Clone, Copy, Debug, PartialEq)]
33pub enum ColorFilter {
34 Tint(Color),
36}
37
38impl ColorFilter {
39 pub fn tint(color: Color) -> Self {
41 Self::Tint(color)
42 }
43
44 pub fn apply_rgba(self, rgba: [f32; 4]) -> [f32; 4] {
45 match self {
46 Self::Tint(tint) => [
47 rgba[0] * tint.r(),
48 rgba[1] * tint.g(),
49 rgba[2] * tint.b(),
50 rgba[3] * tint.a(),
51 ],
52 }
53 }
54}
55
56impl ImageBitmap {
57 pub fn from_rgba8(width: u32, height: u32, pixels: Vec<u8>) -> Result<Self, ImageBitmapError> {
59 Self::from_rgba8_slice(width, height, &pixels)
60 }
61
62 pub fn from_rgba8_slice(
64 width: u32,
65 height: u32,
66 pixels: &[u8],
67 ) -> Result<Self, ImageBitmapError> {
68 if width == 0 || height == 0 {
69 return Err(ImageBitmapError::InvalidDimensions);
70 }
71 let expected = (width as usize)
72 .checked_mul(height as usize)
73 .and_then(|value| value.checked_mul(4))
74 .ok_or(ImageBitmapError::DimensionsTooLarge)?;
75
76 if pixels.len() != expected {
77 return Err(ImageBitmapError::PixelDataLengthMismatch {
78 expected,
79 actual: pixels.len(),
80 });
81 }
82
83 Ok(Self {
84 id: NEXT_IMAGE_BITMAP_ID.fetch_add(1, Ordering::Relaxed),
85 width,
86 height,
87 pixels: Arc::from(pixels),
88 })
89 }
90
91 pub fn id(&self) -> u64 {
93 self.id
94 }
95
96 pub fn width(&self) -> u32 {
98 self.width
99 }
100
101 pub fn height(&self) -> u32 {
103 self.height
104 }
105
106 pub fn pixels(&self) -> &[u8] {
108 &self.pixels
109 }
110
111 pub fn intrinsic_size(&self) -> Size {
113 Size {
114 width: self.width as f32,
115 height: self.height as f32,
116 }
117 }
118}
119
120impl PartialEq for ImageBitmap {
121 fn eq(&self, other: &Self) -> bool {
122 self.id == other.id
123 }
124}
125
126impl Eq for ImageBitmap {}
127
128impl Hash for ImageBitmap {
129 fn hash<H: Hasher>(&self, state: &mut H) {
130 self.id.hash(state);
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn from_rgba8_accepts_valid_data() {
140 let bitmap = ImageBitmap::from_rgba8(2, 1, vec![255, 0, 0, 255, 0, 255, 0, 255])
141 .expect("valid bitmap");
142
143 assert_eq!(bitmap.width(), 2);
144 assert_eq!(bitmap.height(), 1);
145 assert_eq!(bitmap.pixels().len(), 8);
146 }
147
148 #[test]
149 fn from_rgba8_rejects_zero_dimensions() {
150 let err = ImageBitmap::from_rgba8(0, 2, vec![]).expect_err("must fail");
151 assert_eq!(err, ImageBitmapError::InvalidDimensions);
152 }
153
154 #[test]
155 fn from_rgba8_rejects_wrong_pixel_length() {
156 let err = ImageBitmap::from_rgba8(2, 2, vec![0; 15]).expect_err("must fail");
157 assert_eq!(
158 err,
159 ImageBitmapError::PixelDataLengthMismatch {
160 expected: 16,
161 actual: 15,
162 }
163 );
164 }
165
166 #[test]
167 fn from_rgba8_slice_accepts_valid_data() {
168 let pixels = [255u8, 0, 0, 255];
169 let bitmap = ImageBitmap::from_rgba8_slice(1, 1, &pixels).expect("valid bitmap");
170 assert_eq!(bitmap.pixels(), &pixels);
171 }
172
173 #[test]
174 fn ids_are_unique() {
175 let a = ImageBitmap::from_rgba8(1, 1, vec![0, 0, 0, 255]).expect("bitmap a");
176 let b = ImageBitmap::from_rgba8(1, 1, vec![0, 0, 0, 255]).expect("bitmap b");
177 assert_ne!(a.id(), b.id());
178 }
179
180 #[test]
181 fn intrinsic_size_matches_dimensions() {
182 let bitmap = ImageBitmap::from_rgba8(3, 4, vec![255; 3 * 4 * 4]).expect("bitmap");
183 assert_eq!(bitmap.intrinsic_size(), Size::new(3.0, 4.0));
184 }
185
186 #[test]
187 fn tint_filter_multiplies_channels() {
188 let filter = ColorFilter::tint(Color::from_rgba_u8(128, 255, 64, 128));
189 let tinted = filter.apply_rgba([1.0, 0.5, 1.0, 1.0]);
190 assert!((tinted[0] - (128.0 / 255.0)).abs() < 1e-5);
191 assert!((tinted[1] - 0.5).abs() < 1e-5);
192 assert!((tinted[2] - (64.0 / 255.0)).abs() < 1e-5);
193 assert!((tinted[3] - (128.0 / 255.0)).abs() < 1e-5);
194 }
195
196 #[test]
197 fn tint_constructor_matches_variant() {
198 let color = Color::from_rgba_u8(10, 20, 30, 40);
199 assert_eq!(ColorFilter::tint(color), ColorFilter::Tint(color));
200 }
201}