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, Copy, Debug, PartialEq)]
187pub enum ColorFilter {
188 Tint(Color),
190 Modulate(Color),
192 Matrix([f32; 20]),
197}
198
199impl ColorFilter {
200 pub fn tint(color: Color) -> Self {
202 Self::Tint(color)
203 }
204
205 pub fn modulate(color: Color) -> Self {
207 Self::Modulate(color)
208 }
209
210 pub fn matrix(matrix: [f32; 20]) -> Self {
212 Self::Matrix(matrix)
213 }
214
215 pub fn compose(self, next: ColorFilter) -> ColorFilter {
217 ColorFilter::Matrix(compose_color_matrices(self.as_matrix(), next.as_matrix()))
218 }
219
220 pub fn as_matrix(self) -> [f32; 20] {
223 match self {
224 Self::Tint(tint) => [
225 0.0,
226 0.0,
227 0.0,
228 tint.r(),
229 0.0,
230 0.0,
231 0.0,
232 0.0,
233 tint.g(),
234 0.0,
235 0.0,
236 0.0,
237 0.0,
238 tint.b(),
239 0.0,
240 0.0,
241 0.0,
242 0.0,
243 tint.a(),
244 0.0,
245 ],
246 Self::Modulate(modulate) => [
247 modulate.r(),
248 0.0,
249 0.0,
250 0.0,
251 0.0,
252 0.0,
253 modulate.g(),
254 0.0,
255 0.0,
256 0.0,
257 0.0,
258 0.0,
259 modulate.b(),
260 0.0,
261 0.0,
262 0.0,
263 0.0,
264 0.0,
265 modulate.a(),
266 0.0,
267 ],
268 Self::Matrix(matrix) => matrix,
269 }
270 }
271
272 pub fn apply_rgba(self, rgba: [f32; 4]) -> [f32; 4] {
275 apply_color_matrix(self.as_matrix(), rgba)
276 }
277
278 pub fn supports_gpu_vertex_modulation(self) -> bool {
281 matches!(self, Self::Modulate(_))
282 }
283
284 pub fn gpu_vertex_tint(self) -> Option<[f32; 4]> {
287 match self {
288 Self::Modulate(tint) => Some([tint.r(), tint.g(), tint.b(), tint.a()]),
289 _ => None,
290 }
291 }
292
293 pub fn blend_mode(self) -> BlendMode {
296 match self {
297 Self::Tint(_) => BlendMode::SrcIn,
298 Self::Modulate(_) => BlendMode::Modulate,
299 Self::Matrix(_) => BlendMode::SrcOver,
300 }
301 }
302}
303
304fn apply_color_matrix(matrix: [f32; 20], rgba: [f32; 4]) -> [f32; 4] {
305 let r = rgba[0];
306 let g = rgba[1];
307 let b = rgba[2];
308 let a = rgba[3];
309 [
310 (matrix[0] * r + matrix[1] * g + matrix[2] * b + matrix[3] * a + matrix[4]).clamp(0.0, 1.0),
311 (matrix[5] * r + matrix[6] * g + matrix[7] * b + matrix[8] * a + matrix[9]).clamp(0.0, 1.0),
312 (matrix[10] * r + matrix[11] * g + matrix[12] * b + matrix[13] * a + matrix[14])
313 .clamp(0.0, 1.0),
314 (matrix[15] * r + matrix[16] * g + matrix[17] * b + matrix[18] * a + matrix[19])
315 .clamp(0.0, 1.0),
316 ]
317}
318
319fn compose_color_matrices(first: [f32; 20], second: [f32; 20]) -> [f32; 20] {
320 let mut composed = [0.0f32; 20];
321 for row in 0..4 {
322 let row_base = row * 5;
323 let s0 = second[row_base];
324 let s1 = second[row_base + 1];
325 let s2 = second[row_base + 2];
326 let s3 = second[row_base + 3];
327 let s4 = second[row_base + 4];
328
329 composed[row_base] = s0 * first[0] + s1 * first[5] + s2 * first[10] + s3 * first[15];
330 composed[row_base + 1] = s0 * first[1] + s1 * first[6] + s2 * first[11] + s3 * first[16];
331 composed[row_base + 2] = s0 * first[2] + s1 * first[7] + s2 * first[12] + s3 * first[17];
332 composed[row_base + 3] = s0 * first[3] + s1 * first[8] + s2 * first[13] + s3 * first[18];
333 composed[row_base + 4] =
334 s0 * first[4] + s1 * first[9] + s2 * first[14] + s3 * first[19] + s4;
335 }
336 composed
337}
338
339impl ImageBitmap {
340 pub fn from_rgba8(width: u32, height: u32, pixels: Vec<u8>) -> Result<Self, ImageBitmapError> {
343 Self::from_pixels(width, height, ImagePixelFormat::Rgba8, pixels)
344 }
345
346 pub fn from_rgba8_slice(
348 width: u32,
349 height: u32,
350 pixels: &[u8],
351 ) -> Result<Self, ImageBitmapError> {
352 Self::from_pixels(width, height, ImagePixelFormat::Rgba8, pixels)
353 }
354
355 pub fn from_alpha8(
358 width: u32,
359 height: u32,
360 color: [u8; 3],
361 alpha: Vec<u8>,
362 ) -> Result<Self, ImageBitmapError> {
363 Self::from_pixels(width, height, ImagePixelFormat::Alpha8 { color }, alpha)
364 }
365
366 fn from_pixels(
367 width: u32,
368 height: u32,
369 format: ImagePixelFormat,
370 pixels: impl AsRef<[u8]> + Into<Box<[u8]>>,
371 ) -> Result<Self, ImageBitmapError> {
372 if width == 0 || height == 0 {
373 return Err(ImageBitmapError::InvalidDimensions);
374 }
375 let expected = (width as usize)
376 .checked_mul(height as usize)
377 .and_then(|value| value.checked_mul(4))
378 .ok_or(ImageBitmapError::DimensionsTooLarge)?;
379 let expected = match format {
380 ImagePixelFormat::Rgba8 => expected,
381 ImagePixelFormat::Alpha8 { .. } => expected / 4,
382 ImagePixelFormat::External => 0,
383 };
384
385 let bytes = pixels.as_ref();
386 if bytes.len() != expected {
387 return Err(ImageBitmapError::PixelDataLengthMismatch {
388 expected,
389 actual: bytes.len(),
390 });
391 }
392
393 let id = bitmap_content_id(width, height, format, bytes);
394 let opaque = match format {
395 ImagePixelFormat::Rgba8 => bytes
396 .as_chunks::<4>()
397 .0
398 .iter()
399 .all(|pixel| pixel[3] == u8::MAX),
400 ImagePixelFormat::Alpha8 { .. } => bytes.iter().all(|alpha| *alpha == u8::MAX),
401 ImagePixelFormat::External => false,
402 };
403 Ok(Self {
404 data: Arc::new(ImageBitmapData {
405 width,
406 height,
407 id,
408 opaque,
409 format,
410 storage: ImageStorage::Pixels(pixels.into()),
411 }),
412 })
413 }
414
415 pub fn id(&self) -> u64 {
418 self.data.id
419 }
420
421 pub fn storage_id(&self) -> u64 {
424 match &self.data.storage {
425 ImageStorage::External { key, .. } => *key,
426 ImageStorage::Pixels(_) => self.data.id,
427 }
428 }
429
430 pub fn external_handle<T: 'static>(&self) -> Option<&T> {
432 match &self.data.storage {
433 ImageStorage::External { handle, .. } => handle.downcast_ref(),
434 ImageStorage::Pixels(_) => None,
435 }
436 }
437
438 pub fn width(&self) -> u32 {
440 self.data.width
441 }
442
443 pub fn height(&self) -> u32 {
445 self.data.height
446 }
447
448 pub fn pixels(&self) -> &[u8] {
451 match &self.data.storage {
452 ImageStorage::Pixels(pixels) => pixels,
453 ImageStorage::External { .. } => &[],
454 }
455 }
456
457 pub fn format(&self) -> ImagePixelFormat {
459 self.data.format
460 }
461
462 pub fn rgba8_pixel(&self, index: usize) -> [u8; 4] {
468 match self.format() {
469 ImagePixelFormat::Rgba8 => self.pixels().as_chunks::<4>().0[index],
470 ImagePixelFormat::Alpha8 { color: [r, g, b] } => [r, g, b, self.pixels()[index]],
471 ImagePixelFormat::External => [0; 4],
472 }
473 }
474
475 pub fn rgba8_pixels(&self) -> Cow<'_, [u8]> {
478 match self.format() {
479 ImagePixelFormat::Rgba8 | ImagePixelFormat::External => Cow::Borrowed(self.pixels()),
480 ImagePixelFormat::Alpha8 { color: [r, g, b] } => {
481 let mut pixels = Vec::with_capacity(self.pixels().len() * 4);
482 for &alpha in self.pixels() {
483 pixels.extend_from_slice(&[r, g, b, alpha]);
484 }
485 Cow::Owned(pixels)
486 }
487 }
488 }
489
490 pub fn is_opaque(&self) -> bool {
492 self.data.opaque
493 }
494
495 pub fn intrinsic_size(&self) -> Size {
497 Size {
498 width: self.data.width as f32,
499 height: self.data.height as f32,
500 }
501 }
502}
503
504impl PartialEq for ImageBitmap {
505 fn eq(&self, other: &Self) -> bool {
506 self.id() == other.id()
507 }
508}
509
510impl Eq for ImageBitmap {}
511
512impl Hash for ImageBitmap {
513 fn hash<H: Hasher>(&self, state: &mut H) {
514 self.id().hash(state);
515 }
516}
517
518fn bitmap_content_id(width: u32, height: u32, format: ImagePixelFormat, pixels: &[u8]) -> u64 {
519 let mut hasher = foldhash::quality::FixedState::default().build_hasher();
520 width.hash(&mut hasher);
521 height.hash(&mut hasher);
522 format.hash(&mut hasher);
523 pixels.hash(&mut hasher);
524 hasher.finish()
525}
526
527#[cfg(test)]
528#[path = "tests/image_tests.rs"]
529mod tests;