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")]
21 InvalidDimensions,
22 #[error("image dimensions are too large")]
24 DimensionsTooLarge,
25 #[error("pixel data length mismatch: expected {expected} bytes, got {actual}")]
27 PixelDataLengthMismatch {
28 expected: usize,
30 actual: usize,
32 },
33}
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
37pub enum ImagePixelFormat {
38 Rgba8,
40 Alpha8 {
42 color: [u8; 3],
44 },
45 External,
48}
49
50#[derive(Clone, Debug)]
52pub struct ImageBitmap {
53 data: Arc<ImageBitmapData>,
54}
55
56#[derive(Debug)]
57struct ImageBitmapData {
58 width: u32,
59 height: u32,
60 id: u64,
61 opaque: bool,
62 format: ImagePixelFormat,
63 storage: ImageStorage,
64}
65
66enum ImageStorage {
67 Pixels(Box<[u8]>),
68 External { key: u64, handle: ExternalHandle },
69}
70
71type ExternalHandle = Arc<AssertUnwindSafe<Box<dyn Any + Send + Sync>>>;
72
73impl std::fmt::Debug for ImageStorage {
74 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 match self {
76 Self::Pixels(pixels) => write!(formatter, "Pixels({} bytes)", pixels.len()),
77 Self::External { key, .. } => write!(formatter, "External({key:#x})"),
78 }
79 }
80}
81
82#[derive(Clone)]
93pub struct ExternalTexture {
94 key: u64,
95 width: u32,
96 height: u32,
97 opaque: bool,
98 handle: ExternalHandle,
99}
100
101impl std::fmt::Debug for ExternalTexture {
102 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103 formatter
104 .debug_struct("ExternalTexture")
105 .field("key", &self.key)
106 .field("width", &self.width)
107 .field("height", &self.height)
108 .finish_non_exhaustive()
109 }
110}
111
112impl ExternalTexture {
113 pub fn new(
116 width: u32,
117 height: u32,
118 opaque: bool,
119 handle: impl Any + Send + Sync,
120 ) -> Result<Self, ImageBitmapError> {
121 if width == 0 || height == 0 {
122 return Err(ImageBitmapError::InvalidDimensions);
123 }
124 let serial = NEXT_EXTERNAL_TEXTURE.fetch_add(1, Ordering::Relaxed);
125 let mut hasher = foldhash::quality::FixedState::default().build_hasher();
126 ImagePixelFormat::External.hash(&mut hasher);
127 serial.hash(&mut hasher);
128 Ok(Self {
129 key: hasher.finish(),
130 width,
131 height,
132 opaque,
133 handle: Arc::new(AssertUnwindSafe(Box::new(handle))),
134 })
135 }
136
137 pub fn frame(&self, frame: u64) -> ImageBitmap {
139 ImageBitmap {
140 data: Arc::new(ImageBitmapData {
141 width: self.width,
142 height: self.height,
143 id: self.key ^ frame.wrapping_mul(0x9E37_79B9_7F4A_7C15),
144 opaque: self.opaque,
145 format: ImagePixelFormat::External,
146 storage: ImageStorage::External {
147 key: self.key,
148 handle: Arc::clone(&self.handle),
149 },
150 }),
151 }
152 }
153
154 pub fn width(&self) -> u32 {
156 self.width
157 }
158
159 pub fn height(&self) -> u32 {
161 self.height
162 }
163
164 pub fn bitmaps_alive(&self) -> bool {
169 Arc::strong_count(&self.handle) > 1
170 }
171}
172
173static NEXT_EXTERNAL_TEXTURE: AtomicU64 = AtomicU64::new(0);
174
175#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
177pub enum ImageSampling {
178 #[default]
180 Nearest,
181 Linear,
183}
184
185#[derive(Clone, Debug, PartialEq)]
187pub enum ColorFilter {
188 Tint(Color),
190 Modulate(Color),
192 Matrix(Arc<[f32; 20]>),
199}
200
201impl ColorFilter {
202 pub fn tint(color: Color) -> Self {
204 Self::Tint(color)
205 }
206
207 pub fn modulate(color: Color) -> Self {
209 Self::Modulate(color)
210 }
211
212 pub fn matrix(matrix: [f32; 20]) -> Self {
214 Self::Matrix(Arc::new(matrix))
215 }
216
217 pub fn compose(&self, next: &ColorFilter) -> ColorFilter {
219 ColorFilter::matrix(compose_color_matrices(self.as_matrix(), next.as_matrix()))
220 }
221
222 pub fn as_matrix(&self) -> [f32; 20] {
225 match self {
226 Self::Tint(tint) => [
227 0.0,
228 0.0,
229 0.0,
230 tint.r(),
231 0.0,
232 0.0,
233 0.0,
234 0.0,
235 tint.g(),
236 0.0,
237 0.0,
238 0.0,
239 0.0,
240 tint.b(),
241 0.0,
242 0.0,
243 0.0,
244 0.0,
245 tint.a(),
246 0.0,
247 ],
248 Self::Modulate(modulate) => [
249 modulate.r(),
250 0.0,
251 0.0,
252 0.0,
253 0.0,
254 0.0,
255 modulate.g(),
256 0.0,
257 0.0,
258 0.0,
259 0.0,
260 0.0,
261 modulate.b(),
262 0.0,
263 0.0,
264 0.0,
265 0.0,
266 0.0,
267 modulate.a(),
268 0.0,
269 ],
270 Self::Matrix(matrix) => **matrix,
271 }
272 }
273
274 pub fn apply_rgba(&self, rgba: [f32; 4]) -> [f32; 4] {
277 apply_color_matrix(self.as_matrix(), rgba)
278 }
279
280 pub fn supports_gpu_vertex_modulation(&self) -> bool {
283 matches!(self, Self::Modulate(_))
284 }
285
286 pub fn gpu_vertex_tint(&self) -> Option<[f32; 4]> {
289 match self {
290 Self::Modulate(tint) => Some([tint.r(), tint.g(), tint.b(), tint.a()]),
291 _ => None,
292 }
293 }
294
295 pub fn blend_mode(&self) -> BlendMode {
298 match self {
299 Self::Tint(_) => BlendMode::SrcIn,
300 Self::Modulate(_) => BlendMode::Modulate,
301 Self::Matrix(_) => BlendMode::SrcOver,
302 }
303 }
304}
305
306fn apply_color_matrix(matrix: [f32; 20], rgba: [f32; 4]) -> [f32; 4] {
307 let r = rgba[0];
308 let g = rgba[1];
309 let b = rgba[2];
310 let a = rgba[3];
311 [
312 (matrix[0] * r + matrix[1] * g + matrix[2] * b + matrix[3] * a + matrix[4]).clamp(0.0, 1.0),
313 (matrix[5] * r + matrix[6] * g + matrix[7] * b + matrix[8] * a + matrix[9]).clamp(0.0, 1.0),
314 (matrix[10] * r + matrix[11] * g + matrix[12] * b + matrix[13] * a + matrix[14])
315 .clamp(0.0, 1.0),
316 (matrix[15] * r + matrix[16] * g + matrix[17] * b + matrix[18] * a + matrix[19])
317 .clamp(0.0, 1.0),
318 ]
319}
320
321fn compose_color_matrices(first: [f32; 20], second: [f32; 20]) -> [f32; 20] {
322 let mut composed = [0.0f32; 20];
323 for row in 0..4 {
324 let row_base = row * 5;
325 let s0 = second[row_base];
326 let s1 = second[row_base + 1];
327 let s2 = second[row_base + 2];
328 let s3 = second[row_base + 3];
329 let s4 = second[row_base + 4];
330
331 composed[row_base] = s0 * first[0] + s1 * first[5] + s2 * first[10] + s3 * first[15];
332 composed[row_base + 1] = s0 * first[1] + s1 * first[6] + s2 * first[11] + s3 * first[16];
333 composed[row_base + 2] = s0 * first[2] + s1 * first[7] + s2 * first[12] + s3 * first[17];
334 composed[row_base + 3] = s0 * first[3] + s1 * first[8] + s2 * first[13] + s3 * first[18];
335 composed[row_base + 4] =
336 s0 * first[4] + s1 * first[9] + s2 * first[14] + s3 * first[19] + s4;
337 }
338 composed
339}
340
341impl ImageBitmap {
342 pub fn from_rgba8(width: u32, height: u32, pixels: Vec<u8>) -> Result<Self, ImageBitmapError> {
345 Self::from_pixels(width, height, ImagePixelFormat::Rgba8, pixels)
346 }
347
348 pub fn from_rgba8_slice(
350 width: u32,
351 height: u32,
352 pixels: &[u8],
353 ) -> Result<Self, ImageBitmapError> {
354 Self::from_pixels(width, height, ImagePixelFormat::Rgba8, pixels)
355 }
356
357 pub fn from_alpha8(
360 width: u32,
361 height: u32,
362 color: [u8; 3],
363 alpha: Vec<u8>,
364 ) -> Result<Self, ImageBitmapError> {
365 Self::from_pixels(width, height, ImagePixelFormat::Alpha8 { color }, alpha)
366 }
367
368 fn from_pixels(
369 width: u32,
370 height: u32,
371 format: ImagePixelFormat,
372 pixels: impl AsRef<[u8]> + Into<Box<[u8]>>,
373 ) -> Result<Self, ImageBitmapError> {
374 if width == 0 || height == 0 {
375 return Err(ImageBitmapError::InvalidDimensions);
376 }
377 let expected = (width as usize)
378 .checked_mul(height as usize)
379 .and_then(|value| value.checked_mul(4))
380 .ok_or(ImageBitmapError::DimensionsTooLarge)?;
381 let expected = match format {
382 ImagePixelFormat::Rgba8 => expected,
383 ImagePixelFormat::Alpha8 { .. } => expected / 4,
384 ImagePixelFormat::External => 0,
385 };
386
387 let bytes = pixels.as_ref();
388 if bytes.len() != expected {
389 return Err(ImageBitmapError::PixelDataLengthMismatch {
390 expected,
391 actual: bytes.len(),
392 });
393 }
394
395 let id = bitmap_content_id(width, height, format, bytes);
396 let opaque = match format {
397 ImagePixelFormat::Rgba8 => bytes
398 .as_chunks::<4>()
399 .0
400 .iter()
401 .all(|pixel| pixel[3] == u8::MAX),
402 ImagePixelFormat::Alpha8 { .. } => bytes.iter().all(|alpha| *alpha == u8::MAX),
403 ImagePixelFormat::External => false,
404 };
405 Ok(Self {
406 data: Arc::new(ImageBitmapData {
407 width,
408 height,
409 id,
410 opaque,
411 format,
412 storage: ImageStorage::Pixels(pixels.into()),
413 }),
414 })
415 }
416
417 pub fn id(&self) -> u64 {
420 self.data.id
421 }
422
423 pub fn storage_id(&self) -> u64 {
426 match &self.data.storage {
427 ImageStorage::External { key, .. } => *key,
428 ImageStorage::Pixels(_) => self.data.id,
429 }
430 }
431
432 pub fn external_handle<T: 'static>(&self) -> Option<&T> {
434 match &self.data.storage {
435 ImageStorage::External { handle, .. } => handle.downcast_ref(),
436 ImageStorage::Pixels(_) => None,
437 }
438 }
439
440 pub fn width(&self) -> u32 {
442 self.data.width
443 }
444
445 pub fn height(&self) -> u32 {
447 self.data.height
448 }
449
450 pub fn pixels(&self) -> &[u8] {
453 match &self.data.storage {
454 ImageStorage::Pixels(pixels) => pixels,
455 ImageStorage::External { .. } => &[],
456 }
457 }
458
459 pub fn format(&self) -> ImagePixelFormat {
461 self.data.format
462 }
463
464 pub fn rgba8_pixel(&self, index: usize) -> [u8; 4] {
470 match self.format() {
471 ImagePixelFormat::Rgba8 => self.pixels().as_chunks::<4>().0[index],
472 ImagePixelFormat::Alpha8 { color: [r, g, b] } => [r, g, b, self.pixels()[index]],
473 ImagePixelFormat::External => [0; 4],
474 }
475 }
476
477 pub fn rgba8_pixels(&self) -> Cow<'_, [u8]> {
480 match self.format() {
481 ImagePixelFormat::Rgba8 | ImagePixelFormat::External => Cow::Borrowed(self.pixels()),
482 ImagePixelFormat::Alpha8 { color: [r, g, b] } => {
483 let mut pixels = Vec::with_capacity(self.pixels().len() * 4);
484 for &alpha in self.pixels() {
485 pixels.extend_from_slice(&[r, g, b, alpha]);
486 }
487 Cow::Owned(pixels)
488 }
489 }
490 }
491
492 pub fn is_opaque(&self) -> bool {
494 self.data.opaque
495 }
496
497 pub fn intrinsic_size(&self) -> Size {
499 Size {
500 width: self.data.width as f32,
501 height: self.data.height as f32,
502 }
503 }
504}
505
506impl PartialEq for ImageBitmap {
507 fn eq(&self, other: &Self) -> bool {
508 self.id() == other.id()
509 }
510}
511
512impl Eq for ImageBitmap {}
513
514impl Hash for ImageBitmap {
515 fn hash<H: Hasher>(&self, state: &mut H) {
516 self.id().hash(state);
517 }
518}
519
520fn bitmap_content_id(width: u32, height: u32, format: ImagePixelFormat, pixels: &[u8]) -> u64 {
521 let mut hasher = foldhash::quality::FixedState::default().build_hasher();
522 width.hash(&mut hasher);
523 height.hash(&mut hasher);
524 format.hash(&mut hasher);
525 pixels.hash(&mut hasher);
526 hasher.finish()
527}
528
529#[cfg(test)]
530#[path = "tests/image_tests.rs"]
531mod tests;