1use std::{
4 borrow::Cow,
5 hash::{BuildHasher, Hash, Hasher},
6 sync::Arc,
7};
8
9use thiserror::Error;
10
11use crate::{BlendMode, Color, Size};
12
13#[derive(Debug, Clone, PartialEq, Eq, Error)]
15pub enum ImageBitmapError {
16 #[error("image dimensions must be greater than zero")]
17 InvalidDimensions,
18 #[error("image dimensions are too large")]
19 DimensionsTooLarge,
20 #[error("pixel data length mismatch: expected {expected} bytes, got {actual}")]
21 PixelDataLengthMismatch { expected: usize, actual: usize },
22}
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
26pub enum ImagePixelFormat {
27 Rgba8,
29 Alpha8 {
31 color: [u8; 3],
33 },
34}
35
36#[derive(Clone, Debug)]
38pub struct ImageBitmap {
39 data: Arc<ImageBitmapData>,
40}
41
42#[derive(Debug)]
43struct ImageBitmapData {
44 width: u32,
45 height: u32,
46 id: u64,
47 opaque: bool,
48 format: ImagePixelFormat,
49 pixels: Box<[u8]>,
50}
51
52#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
54pub enum ImageSampling {
55 #[default]
57 Nearest,
58 Linear,
60}
61
62#[derive(Clone, Copy, Debug, PartialEq)]
64pub enum ColorFilter {
65 Tint(Color),
67 Modulate(Color),
69 Matrix([f32; 20]),
74}
75
76impl ColorFilter {
77 pub fn tint(color: Color) -> Self {
79 Self::Tint(color)
80 }
81
82 pub fn modulate(color: Color) -> Self {
84 Self::Modulate(color)
85 }
86
87 pub fn matrix(matrix: [f32; 20]) -> Self {
89 Self::Matrix(matrix)
90 }
91
92 pub fn compose(self, next: ColorFilter) -> ColorFilter {
93 ColorFilter::Matrix(compose_color_matrices(self.as_matrix(), next.as_matrix()))
94 }
95
96 pub fn as_matrix(self) -> [f32; 20] {
97 match self {
98 Self::Tint(tint) => [
99 0.0,
100 0.0,
101 0.0,
102 tint.r(),
103 0.0,
104 0.0,
105 0.0,
106 0.0,
107 tint.g(),
108 0.0,
109 0.0,
110 0.0,
111 0.0,
112 tint.b(),
113 0.0,
114 0.0,
115 0.0,
116 0.0,
117 tint.a(),
118 0.0,
119 ],
120 Self::Modulate(modulate) => [
121 modulate.r(),
122 0.0,
123 0.0,
124 0.0,
125 0.0,
126 0.0,
127 modulate.g(),
128 0.0,
129 0.0,
130 0.0,
131 0.0,
132 0.0,
133 modulate.b(),
134 0.0,
135 0.0,
136 0.0,
137 0.0,
138 0.0,
139 modulate.a(),
140 0.0,
141 ],
142 Self::Matrix(matrix) => matrix,
143 }
144 }
145
146 pub fn apply_rgba(self, rgba: [f32; 4]) -> [f32; 4] {
147 apply_color_matrix(self.as_matrix(), rgba)
148 }
149
150 pub fn supports_gpu_vertex_modulation(self) -> bool {
151 matches!(self, Self::Modulate(_))
152 }
153
154 pub fn gpu_vertex_tint(self) -> Option<[f32; 4]> {
155 match self {
156 Self::Modulate(tint) => Some([tint.r(), tint.g(), tint.b(), tint.a()]),
157 _ => None,
158 }
159 }
160
161 pub fn blend_mode(self) -> BlendMode {
162 match self {
163 Self::Tint(_) => BlendMode::SrcIn,
164 Self::Modulate(_) => BlendMode::Modulate,
165 Self::Matrix(_) => BlendMode::SrcOver,
166 }
167 }
168}
169
170fn apply_color_matrix(matrix: [f32; 20], rgba: [f32; 4]) -> [f32; 4] {
171 let r = rgba[0];
172 let g = rgba[1];
173 let b = rgba[2];
174 let a = rgba[3];
175 [
176 (matrix[0] * r + matrix[1] * g + matrix[2] * b + matrix[3] * a + matrix[4]).clamp(0.0, 1.0),
177 (matrix[5] * r + matrix[6] * g + matrix[7] * b + matrix[8] * a + matrix[9]).clamp(0.0, 1.0),
178 (matrix[10] * r + matrix[11] * g + matrix[12] * b + matrix[13] * a + matrix[14])
179 .clamp(0.0, 1.0),
180 (matrix[15] * r + matrix[16] * g + matrix[17] * b + matrix[18] * a + matrix[19])
181 .clamp(0.0, 1.0),
182 ]
183}
184
185fn compose_color_matrices(first: [f32; 20], second: [f32; 20]) -> [f32; 20] {
186 let mut composed = [0.0f32; 20];
187 for row in 0..4 {
188 let row_base = row * 5;
189 let s0 = second[row_base];
190 let s1 = second[row_base + 1];
191 let s2 = second[row_base + 2];
192 let s3 = second[row_base + 3];
193 let s4 = second[row_base + 4];
194
195 composed[row_base] = s0 * first[0] + s1 * first[5] + s2 * first[10] + s3 * first[15];
196 composed[row_base + 1] = s0 * first[1] + s1 * first[6] + s2 * first[11] + s3 * first[16];
197 composed[row_base + 2] = s0 * first[2] + s1 * first[7] + s2 * first[12] + s3 * first[17];
198 composed[row_base + 3] = s0 * first[3] + s1 * first[8] + s2 * first[13] + s3 * first[18];
199 composed[row_base + 4] =
200 s0 * first[4] + s1 * first[9] + s2 * first[14] + s3 * first[19] + s4;
201 }
202 composed
203}
204
205impl ImageBitmap {
206 pub fn from_rgba8(width: u32, height: u32, pixels: Vec<u8>) -> Result<Self, ImageBitmapError> {
209 Self::from_pixels(width, height, ImagePixelFormat::Rgba8, pixels)
210 }
211
212 pub fn from_rgba8_slice(
214 width: u32,
215 height: u32,
216 pixels: &[u8],
217 ) -> Result<Self, ImageBitmapError> {
218 Self::from_pixels(width, height, ImagePixelFormat::Rgba8, pixels)
219 }
220
221 pub fn from_alpha8(
224 width: u32,
225 height: u32,
226 color: [u8; 3],
227 alpha: Vec<u8>,
228 ) -> Result<Self, ImageBitmapError> {
229 Self::from_pixels(width, height, ImagePixelFormat::Alpha8 { color }, alpha)
230 }
231
232 fn from_pixels(
233 width: u32,
234 height: u32,
235 format: ImagePixelFormat,
236 pixels: impl AsRef<[u8]> + Into<Box<[u8]>>,
237 ) -> Result<Self, ImageBitmapError> {
238 if width == 0 || height == 0 {
239 return Err(ImageBitmapError::InvalidDimensions);
240 }
241 let expected = (width as usize)
242 .checked_mul(height as usize)
243 .and_then(|value| value.checked_mul(4))
244 .ok_or(ImageBitmapError::DimensionsTooLarge)?;
245 let expected = match format {
246 ImagePixelFormat::Rgba8 => expected,
247 ImagePixelFormat::Alpha8 { .. } => expected / 4,
248 };
249
250 let bytes = pixels.as_ref();
251 if bytes.len() != expected {
252 return Err(ImageBitmapError::PixelDataLengthMismatch {
253 expected,
254 actual: bytes.len(),
255 });
256 }
257
258 let id = bitmap_content_id(width, height, format, bytes);
259 let opaque = match format {
260 ImagePixelFormat::Rgba8 => bytes
261 .as_chunks::<4>()
262 .0
263 .iter()
264 .all(|pixel| pixel[3] == u8::MAX),
265 ImagePixelFormat::Alpha8 { .. } => bytes.iter().all(|alpha| *alpha == u8::MAX),
266 };
267 Ok(Self {
268 data: Arc::new(ImageBitmapData {
269 width,
270 height,
271 id,
272 opaque,
273 format,
274 pixels: pixels.into(),
275 }),
276 })
277 }
278
279 pub fn id(&self) -> u64 {
281 self.data.id
282 }
283
284 pub fn width(&self) -> u32 {
286 self.data.width
287 }
288
289 pub fn height(&self) -> u32 {
291 self.data.height
292 }
293
294 pub fn pixels(&self) -> &[u8] {
296 &self.data.pixels
297 }
298
299 pub fn format(&self) -> ImagePixelFormat {
301 self.data.format
302 }
303
304 pub fn rgba8_pixel(&self, index: usize) -> [u8; 4] {
309 match self.format() {
310 ImagePixelFormat::Rgba8 => self.pixels().as_chunks::<4>().0[index],
311 ImagePixelFormat::Alpha8 { color: [r, g, b] } => [r, g, b, self.pixels()[index]],
312 }
313 }
314
315 pub fn rgba8_pixels(&self) -> Cow<'_, [u8]> {
317 match self.format() {
318 ImagePixelFormat::Rgba8 => Cow::Borrowed(self.pixels()),
319 ImagePixelFormat::Alpha8 { color: [r, g, b] } => {
320 let mut pixels = Vec::with_capacity(self.pixels().len() * 4);
321 for &alpha in self.pixels() {
322 pixels.extend_from_slice(&[r, g, b, alpha]);
323 }
324 Cow::Owned(pixels)
325 }
326 }
327 }
328
329 pub fn is_opaque(&self) -> bool {
331 self.data.opaque
332 }
333
334 pub fn intrinsic_size(&self) -> Size {
336 Size {
337 width: self.data.width as f32,
338 height: self.data.height as f32,
339 }
340 }
341}
342
343impl PartialEq for ImageBitmap {
344 fn eq(&self, other: &Self) -> bool {
345 self.id() == other.id()
346 }
347}
348
349impl Eq for ImageBitmap {}
350
351impl Hash for ImageBitmap {
352 fn hash<H: Hasher>(&self, state: &mut H) {
353 self.id().hash(state);
354 }
355}
356
357fn bitmap_content_id(width: u32, height: u32, format: ImagePixelFormat, pixels: &[u8]) -> u64 {
358 let mut hasher = foldhash::quality::FixedState::default().build_hasher();
359 width.hash(&mut hasher);
360 height.hash(&mut hasher);
361 format.hash(&mut hasher);
362 pixels.hash(&mut hasher);
363 hasher.finish()
364}
365
366#[cfg(test)]
367#[path = "tests/image_tests.rs"]
368mod tests;