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