1use std::{
4 hash::{BuildHasher, Hash, Hasher},
5 sync::Arc,
6};
7
8use thiserror::Error;
9
10use crate::{BlendMode, Color, Size};
11
12#[derive(Debug, Clone, PartialEq, Eq, Error)]
14pub enum ImageBitmapError {
15 #[error("image dimensions must be greater than zero")]
16 InvalidDimensions,
17 #[error("image dimensions are too large")]
18 DimensionsTooLarge,
19 #[error("pixel data length mismatch: expected {expected} bytes, got {actual}")]
20 PixelDataLengthMismatch { expected: usize, actual: usize },
21}
22
23#[derive(Clone, Debug)]
25pub struct ImageBitmap {
26 width: u32,
27 height: u32,
28 id: u64,
29 opaque: bool,
30 pixels: Arc<[u8]>,
31}
32
33#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
35pub enum ImageSampling {
36 #[default]
38 Nearest,
39 Linear,
41}
42
43#[derive(Clone, Copy, Debug, PartialEq)]
45pub enum ColorFilter {
46 Tint(Color),
48 Modulate(Color),
50 Matrix([f32; 20]),
55}
56
57impl ColorFilter {
58 pub fn tint(color: Color) -> Self {
60 Self::Tint(color)
61 }
62
63 pub fn modulate(color: Color) -> Self {
65 Self::Modulate(color)
66 }
67
68 pub fn matrix(matrix: [f32; 20]) -> Self {
70 Self::Matrix(matrix)
71 }
72
73 pub fn compose(self, next: ColorFilter) -> ColorFilter {
74 ColorFilter::Matrix(compose_color_matrices(self.as_matrix(), next.as_matrix()))
75 }
76
77 pub fn as_matrix(self) -> [f32; 20] {
78 match self {
79 Self::Tint(tint) => [
80 0.0,
81 0.0,
82 0.0,
83 tint.r(),
84 0.0,
85 0.0,
86 0.0,
87 0.0,
88 tint.g(),
89 0.0,
90 0.0,
91 0.0,
92 0.0,
93 tint.b(),
94 0.0,
95 0.0,
96 0.0,
97 0.0,
98 tint.a(),
99 0.0,
100 ],
101 Self::Modulate(modulate) => [
102 modulate.r(),
103 0.0,
104 0.0,
105 0.0,
106 0.0,
107 0.0,
108 modulate.g(),
109 0.0,
110 0.0,
111 0.0,
112 0.0,
113 0.0,
114 modulate.b(),
115 0.0,
116 0.0,
117 0.0,
118 0.0,
119 0.0,
120 modulate.a(),
121 0.0,
122 ],
123 Self::Matrix(matrix) => matrix,
124 }
125 }
126
127 pub fn apply_rgba(self, rgba: [f32; 4]) -> [f32; 4] {
128 apply_color_matrix(self.as_matrix(), rgba)
129 }
130
131 pub fn supports_gpu_vertex_modulation(self) -> bool {
132 matches!(self, Self::Modulate(_))
133 }
134
135 pub fn gpu_vertex_tint(self) -> Option<[f32; 4]> {
136 match self {
137 Self::Modulate(tint) => Some([tint.r(), tint.g(), tint.b(), tint.a()]),
138 _ => None,
139 }
140 }
141
142 pub fn blend_mode(self) -> BlendMode {
143 match self {
144 Self::Tint(_) => BlendMode::SrcIn,
145 Self::Modulate(_) => BlendMode::Modulate,
146 Self::Matrix(_) => BlendMode::SrcOver,
147 }
148 }
149}
150
151fn apply_color_matrix(matrix: [f32; 20], rgba: [f32; 4]) -> [f32; 4] {
152 let r = rgba[0];
153 let g = rgba[1];
154 let b = rgba[2];
155 let a = rgba[3];
156 [
157 (matrix[0] * r + matrix[1] * g + matrix[2] * b + matrix[3] * a + matrix[4]).clamp(0.0, 1.0),
158 (matrix[5] * r + matrix[6] * g + matrix[7] * b + matrix[8] * a + matrix[9]).clamp(0.0, 1.0),
159 (matrix[10] * r + matrix[11] * g + matrix[12] * b + matrix[13] * a + matrix[14])
160 .clamp(0.0, 1.0),
161 (matrix[15] * r + matrix[16] * g + matrix[17] * b + matrix[18] * a + matrix[19])
162 .clamp(0.0, 1.0),
163 ]
164}
165
166fn compose_color_matrices(first: [f32; 20], second: [f32; 20]) -> [f32; 20] {
167 let mut composed = [0.0f32; 20];
168 for row in 0..4 {
169 let row_base = row * 5;
170 let s0 = second[row_base];
171 let s1 = second[row_base + 1];
172 let s2 = second[row_base + 2];
173 let s3 = second[row_base + 3];
174 let s4 = second[row_base + 4];
175
176 composed[row_base] = s0 * first[0] + s1 * first[5] + s2 * first[10] + s3 * first[15];
177 composed[row_base + 1] = s0 * first[1] + s1 * first[6] + s2 * first[11] + s3 * first[16];
178 composed[row_base + 2] = s0 * first[2] + s1 * first[7] + s2 * first[12] + s3 * first[17];
179 composed[row_base + 3] = s0 * first[3] + s1 * first[8] + s2 * first[13] + s3 * first[18];
180 composed[row_base + 4] =
181 s0 * first[4] + s1 * first[9] + s2 * first[14] + s3 * first[19] + s4;
182 }
183 composed
184}
185
186impl ImageBitmap {
187 pub fn from_rgba8(width: u32, height: u32, pixels: Vec<u8>) -> Result<Self, ImageBitmapError> {
189 Self::from_rgba8_slice(width, height, &pixels)
190 }
191
192 pub fn from_rgba8_slice(
194 width: u32,
195 height: u32,
196 pixels: &[u8],
197 ) -> Result<Self, ImageBitmapError> {
198 if width == 0 || height == 0 {
199 return Err(ImageBitmapError::InvalidDimensions);
200 }
201 let expected = (width as usize)
202 .checked_mul(height as usize)
203 .and_then(|value| value.checked_mul(4))
204 .ok_or(ImageBitmapError::DimensionsTooLarge)?;
205
206 if pixels.len() != expected {
207 return Err(ImageBitmapError::PixelDataLengthMismatch {
208 expected,
209 actual: pixels.len(),
210 });
211 }
212
213 let id = bitmap_content_id(width, height, pixels);
214 let opaque = pixels
215 .as_chunks::<4>()
216 .0
217 .iter()
218 .all(|pixel| pixel[3] == u8::MAX);
219 Ok(Self {
220 width,
221 height,
222 id,
223 opaque,
224 pixels: Arc::from(pixels),
225 })
226 }
227
228 pub fn id(&self) -> u64 {
230 self.id
231 }
232
233 pub fn width(&self) -> u32 {
235 self.width
236 }
237
238 pub fn height(&self) -> u32 {
240 self.height
241 }
242
243 pub fn pixels(&self) -> &[u8] {
245 &self.pixels
246 }
247
248 pub fn is_opaque(&self) -> bool {
250 self.opaque
251 }
252
253 pub fn intrinsic_size(&self) -> Size {
255 Size {
256 width: self.width as f32,
257 height: self.height as f32,
258 }
259 }
260}
261
262impl PartialEq for ImageBitmap {
263 fn eq(&self, other: &Self) -> bool {
264 self.id() == other.id()
265 }
266}
267
268impl Eq for ImageBitmap {}
269
270impl Hash for ImageBitmap {
271 fn hash<H: Hasher>(&self, state: &mut H) {
272 self.id().hash(state);
273 }
274}
275
276fn bitmap_content_id(width: u32, height: u32, pixels: &[u8]) -> u64 {
277 let mut hasher = foldhash::quality::FixedState::default().build_hasher();
278 width.hash(&mut hasher);
279 height.hash(&mut hasher);
280 pixels.hash(&mut hasher);
281 hasher.finish()
282}
283
284#[cfg(test)]
285#[path = "tests/image_tests.rs"]
286mod tests;