1use std::{
4 any::Any,
5 borrow::Cow,
6 hash::{BuildHasher, Hash, Hasher},
7 panic::AssertUnwindSafe,
8 sync::{Arc, atomic::Ordering},
9};
10
11use portable_atomic::AtomicU64;
12use thiserror::Error;
13
14use crate::{BlendMode, Color, Size};
15
16#[derive(Debug, Clone, PartialEq, Eq, Error)]
18pub enum ImageBitmapError {
19 #[error("image dimensions must be greater than zero")]
20 InvalidDimensions,
21 #[error("image dimensions are too large")]
22 DimensionsTooLarge,
23 #[error("pixel data length mismatch: expected {expected} bytes, got {actual}")]
24 PixelDataLengthMismatch { expected: usize, actual: usize },
25}
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
29pub enum ImagePixelFormat {
30 Rgba8,
32 Alpha8 {
34 color: [u8; 3],
36 },
37 External,
40}
41
42#[derive(Clone, Debug)]
44pub struct ImageBitmap {
45 data: Arc<ImageBitmapData>,
46}
47
48#[derive(Debug)]
49struct ImageBitmapData {
50 width: u32,
51 height: u32,
52 id: u64,
53 opaque: bool,
54 format: ImagePixelFormat,
55 storage: ImageStorage,
56}
57
58enum ImageStorage {
59 Pixels(Box<[u8]>),
60 External { key: u64, handle: ExternalHandle },
61}
62
63type ExternalHandle = Arc<AssertUnwindSafe<Box<dyn Any + Send + Sync>>>;
64
65impl std::fmt::Debug for ImageStorage {
66 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 match self {
68 Self::Pixels(pixels) => write!(formatter, "Pixels({} bytes)", pixels.len()),
69 Self::External { key, .. } => write!(formatter, "External({key:#x})"),
70 }
71 }
72}
73
74#[derive(Clone)]
85pub struct ExternalTexture {
86 key: u64,
87 width: u32,
88 height: u32,
89 opaque: bool,
90 handle: ExternalHandle,
91}
92
93impl std::fmt::Debug for ExternalTexture {
94 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 formatter
96 .debug_struct("ExternalTexture")
97 .field("key", &self.key)
98 .field("width", &self.width)
99 .field("height", &self.height)
100 .finish_non_exhaustive()
101 }
102}
103
104impl ExternalTexture {
105 pub fn new(
108 width: u32,
109 height: u32,
110 opaque: bool,
111 handle: impl Any + Send + Sync,
112 ) -> Result<Self, ImageBitmapError> {
113 if width == 0 || height == 0 {
114 return Err(ImageBitmapError::InvalidDimensions);
115 }
116 let serial = NEXT_EXTERNAL_TEXTURE.fetch_add(1, Ordering::Relaxed);
117 let mut hasher = foldhash::quality::FixedState::default().build_hasher();
118 ImagePixelFormat::External.hash(&mut hasher);
119 serial.hash(&mut hasher);
120 Ok(Self {
121 key: hasher.finish(),
122 width,
123 height,
124 opaque,
125 handle: Arc::new(AssertUnwindSafe(Box::new(handle))),
126 })
127 }
128
129 pub fn frame(&self, frame: u64) -> ImageBitmap {
131 ImageBitmap {
132 data: Arc::new(ImageBitmapData {
133 width: self.width,
134 height: self.height,
135 id: self.key ^ frame.wrapping_mul(0x9E37_79B9_7F4A_7C15),
136 opaque: self.opaque,
137 format: ImagePixelFormat::External,
138 storage: ImageStorage::External {
139 key: self.key,
140 handle: Arc::clone(&self.handle),
141 },
142 }),
143 }
144 }
145
146 pub fn width(&self) -> u32 {
148 self.width
149 }
150
151 pub fn height(&self) -> u32 {
153 self.height
154 }
155
156 pub fn bitmaps_alive(&self) -> bool {
161 Arc::strong_count(&self.handle) > 1
162 }
163}
164
165static NEXT_EXTERNAL_TEXTURE: AtomicU64 = AtomicU64::new(0);
166
167#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
169pub enum ImageSampling {
170 #[default]
172 Nearest,
173 Linear,
175}
176
177#[derive(Clone, Copy, Debug, PartialEq)]
179pub enum ColorFilter {
180 Tint(Color),
182 Modulate(Color),
184 Matrix([f32; 20]),
189}
190
191impl ColorFilter {
192 pub fn tint(color: Color) -> Self {
194 Self::Tint(color)
195 }
196
197 pub fn modulate(color: Color) -> Self {
199 Self::Modulate(color)
200 }
201
202 pub fn matrix(matrix: [f32; 20]) -> Self {
204 Self::Matrix(matrix)
205 }
206
207 pub fn compose(self, next: ColorFilter) -> ColorFilter {
208 ColorFilter::Matrix(compose_color_matrices(self.as_matrix(), next.as_matrix()))
209 }
210
211 pub fn as_matrix(self) -> [f32; 20] {
212 match self {
213 Self::Tint(tint) => [
214 0.0,
215 0.0,
216 0.0,
217 tint.r(),
218 0.0,
219 0.0,
220 0.0,
221 0.0,
222 tint.g(),
223 0.0,
224 0.0,
225 0.0,
226 0.0,
227 tint.b(),
228 0.0,
229 0.0,
230 0.0,
231 0.0,
232 tint.a(),
233 0.0,
234 ],
235 Self::Modulate(modulate) => [
236 modulate.r(),
237 0.0,
238 0.0,
239 0.0,
240 0.0,
241 0.0,
242 modulate.g(),
243 0.0,
244 0.0,
245 0.0,
246 0.0,
247 0.0,
248 modulate.b(),
249 0.0,
250 0.0,
251 0.0,
252 0.0,
253 0.0,
254 modulate.a(),
255 0.0,
256 ],
257 Self::Matrix(matrix) => matrix,
258 }
259 }
260
261 pub fn apply_rgba(self, rgba: [f32; 4]) -> [f32; 4] {
262 apply_color_matrix(self.as_matrix(), rgba)
263 }
264
265 pub fn supports_gpu_vertex_modulation(self) -> bool {
266 matches!(self, Self::Modulate(_))
267 }
268
269 pub fn gpu_vertex_tint(self) -> Option<[f32; 4]> {
270 match self {
271 Self::Modulate(tint) => Some([tint.r(), tint.g(), tint.b(), tint.a()]),
272 _ => None,
273 }
274 }
275
276 pub fn blend_mode(self) -> BlendMode {
277 match self {
278 Self::Tint(_) => BlendMode::SrcIn,
279 Self::Modulate(_) => BlendMode::Modulate,
280 Self::Matrix(_) => BlendMode::SrcOver,
281 }
282 }
283}
284
285fn apply_color_matrix(matrix: [f32; 20], rgba: [f32; 4]) -> [f32; 4] {
286 let r = rgba[0];
287 let g = rgba[1];
288 let b = rgba[2];
289 let a = rgba[3];
290 [
291 (matrix[0] * r + matrix[1] * g + matrix[2] * b + matrix[3] * a + matrix[4]).clamp(0.0, 1.0),
292 (matrix[5] * r + matrix[6] * g + matrix[7] * b + matrix[8] * a + matrix[9]).clamp(0.0, 1.0),
293 (matrix[10] * r + matrix[11] * g + matrix[12] * b + matrix[13] * a + matrix[14])
294 .clamp(0.0, 1.0),
295 (matrix[15] * r + matrix[16] * g + matrix[17] * b + matrix[18] * a + matrix[19])
296 .clamp(0.0, 1.0),
297 ]
298}
299
300fn compose_color_matrices(first: [f32; 20], second: [f32; 20]) -> [f32; 20] {
301 let mut composed = [0.0f32; 20];
302 for row in 0..4 {
303 let row_base = row * 5;
304 let s0 = second[row_base];
305 let s1 = second[row_base + 1];
306 let s2 = second[row_base + 2];
307 let s3 = second[row_base + 3];
308 let s4 = second[row_base + 4];
309
310 composed[row_base] = s0 * first[0] + s1 * first[5] + s2 * first[10] + s3 * first[15];
311 composed[row_base + 1] = s0 * first[1] + s1 * first[6] + s2 * first[11] + s3 * first[16];
312 composed[row_base + 2] = s0 * first[2] + s1 * first[7] + s2 * first[12] + s3 * first[17];
313 composed[row_base + 3] = s0 * first[3] + s1 * first[8] + s2 * first[13] + s3 * first[18];
314 composed[row_base + 4] =
315 s0 * first[4] + s1 * first[9] + s2 * first[14] + s3 * first[19] + s4;
316 }
317 composed
318}
319
320impl ImageBitmap {
321 pub fn from_rgba8(width: u32, height: u32, pixels: Vec<u8>) -> Result<Self, ImageBitmapError> {
324 Self::from_pixels(width, height, ImagePixelFormat::Rgba8, pixels)
325 }
326
327 pub fn from_rgba8_slice(
329 width: u32,
330 height: u32,
331 pixels: &[u8],
332 ) -> Result<Self, ImageBitmapError> {
333 Self::from_pixels(width, height, ImagePixelFormat::Rgba8, pixels)
334 }
335
336 pub fn from_alpha8(
339 width: u32,
340 height: u32,
341 color: [u8; 3],
342 alpha: Vec<u8>,
343 ) -> Result<Self, ImageBitmapError> {
344 Self::from_pixels(width, height, ImagePixelFormat::Alpha8 { color }, alpha)
345 }
346
347 fn from_pixels(
348 width: u32,
349 height: u32,
350 format: ImagePixelFormat,
351 pixels: impl AsRef<[u8]> + Into<Box<[u8]>>,
352 ) -> Result<Self, ImageBitmapError> {
353 if width == 0 || height == 0 {
354 return Err(ImageBitmapError::InvalidDimensions);
355 }
356 let expected = (width as usize)
357 .checked_mul(height as usize)
358 .and_then(|value| value.checked_mul(4))
359 .ok_or(ImageBitmapError::DimensionsTooLarge)?;
360 let expected = match format {
361 ImagePixelFormat::Rgba8 => expected,
362 ImagePixelFormat::Alpha8 { .. } => expected / 4,
363 ImagePixelFormat::External => 0,
364 };
365
366 let bytes = pixels.as_ref();
367 if bytes.len() != expected {
368 return Err(ImageBitmapError::PixelDataLengthMismatch {
369 expected,
370 actual: bytes.len(),
371 });
372 }
373
374 let id = bitmap_content_id(width, height, format, bytes);
375 let opaque = match format {
376 ImagePixelFormat::Rgba8 => bytes
377 .as_chunks::<4>()
378 .0
379 .iter()
380 .all(|pixel| pixel[3] == u8::MAX),
381 ImagePixelFormat::Alpha8 { .. } => bytes.iter().all(|alpha| *alpha == u8::MAX),
382 ImagePixelFormat::External => false,
383 };
384 Ok(Self {
385 data: Arc::new(ImageBitmapData {
386 width,
387 height,
388 id,
389 opaque,
390 format,
391 storage: ImageStorage::Pixels(pixels.into()),
392 }),
393 })
394 }
395
396 pub fn id(&self) -> u64 {
399 self.data.id
400 }
401
402 pub fn storage_id(&self) -> u64 {
405 match &self.data.storage {
406 ImageStorage::External { key, .. } => *key,
407 ImageStorage::Pixels(_) => self.data.id,
408 }
409 }
410
411 pub fn external_handle<T: 'static>(&self) -> Option<&T> {
413 match &self.data.storage {
414 ImageStorage::External { handle, .. } => handle.downcast_ref(),
415 ImageStorage::Pixels(_) => None,
416 }
417 }
418
419 pub fn width(&self) -> u32 {
421 self.data.width
422 }
423
424 pub fn height(&self) -> u32 {
426 self.data.height
427 }
428
429 pub fn pixels(&self) -> &[u8] {
432 match &self.data.storage {
433 ImageStorage::Pixels(pixels) => pixels,
434 ImageStorage::External { .. } => &[],
435 }
436 }
437
438 pub fn format(&self) -> ImagePixelFormat {
440 self.data.format
441 }
442
443 pub fn rgba8_pixel(&self, index: usize) -> [u8; 4] {
449 match self.format() {
450 ImagePixelFormat::Rgba8 => self.pixels().as_chunks::<4>().0[index],
451 ImagePixelFormat::Alpha8 { color: [r, g, b] } => [r, g, b, self.pixels()[index]],
452 ImagePixelFormat::External => [0; 4],
453 }
454 }
455
456 pub fn rgba8_pixels(&self) -> Cow<'_, [u8]> {
459 match self.format() {
460 ImagePixelFormat::Rgba8 | ImagePixelFormat::External => Cow::Borrowed(self.pixels()),
461 ImagePixelFormat::Alpha8 { color: [r, g, b] } => {
462 let mut pixels = Vec::with_capacity(self.pixels().len() * 4);
463 for &alpha in self.pixels() {
464 pixels.extend_from_slice(&[r, g, b, alpha]);
465 }
466 Cow::Owned(pixels)
467 }
468 }
469 }
470
471 pub fn is_opaque(&self) -> bool {
473 self.data.opaque
474 }
475
476 pub fn intrinsic_size(&self) -> Size {
478 Size {
479 width: self.data.width as f32,
480 height: self.data.height as f32,
481 }
482 }
483}
484
485impl PartialEq for ImageBitmap {
486 fn eq(&self, other: &Self) -> bool {
487 self.id() == other.id()
488 }
489}
490
491impl Eq for ImageBitmap {}
492
493impl Hash for ImageBitmap {
494 fn hash<H: Hasher>(&self, state: &mut H) {
495 self.id().hash(state);
496 }
497}
498
499fn bitmap_content_id(width: u32, height: u32, format: ImagePixelFormat, pixels: &[u8]) -> u64 {
500 let mut hasher = foldhash::quality::FixedState::default().build_hasher();
501 width.hash(&mut hasher);
502 height.hash(&mut hasher);
503 format.hash(&mut hasher);
504 pixels.hash(&mut hasher);
505 hasher.finish()
506}
507
508#[cfg(test)]
509#[path = "tests/image_tests.rs"]
510mod tests;