1use std::{fmt, mem, sync::Arc};
2
3use image::ImageDecoder;
4use webrender::api::{ImageDescriptor, ImageDescriptorFlags, ImageFormat};
5use winit::{
6 event_loop::ActiveEventLoop,
7 window::{CustomCursor, Icon},
8};
9use zng_txt::{ToTxt, Txt, formatx};
10use zng_unit::{Px, PxPoint, PxSize};
11use zng_view_api::{
12 Event,
13 image::{ImageDataFormat, ImageDownscale, ImageId, ImageLoadedData, ImageMaskMode, ImagePpi, ImageRequest},
14 ipc::{IpcBytes, IpcBytesReceiver},
15};
16
17use crate::{AppEvent, AppEventSender};
18use rustc_hash::FxHashMap;
19
20pub(crate) const ENCODERS: &[&str] = &[
21 #[cfg(feature = "image_png")]
22 "png",
23 #[cfg(feature = "image_jpeg")]
24 "jpg",
25 #[cfg(feature = "image_jpeg")]
26 "jpeg",
27 #[cfg(feature = "image_webp")]
28 "webp",
29 #[cfg(any(feature = "image_avif", zng_view_image_has_avif))]
30 "avif",
31 #[cfg(feature = "image_gif")]
32 "gif",
33 #[cfg(feature = "image_ico")]
34 "ico",
35 #[cfg(feature = "image_bmp")]
36 "bmp",
37 #[cfg(feature = "image_jpeg")]
38 "jfif",
39 #[cfg(feature = "image_exr")]
40 "exr",
41 #[cfg(feature = "image_hdr")]
42 "hdr",
43 #[cfg(feature = "image_pnm")]
44 "pnm",
45 #[cfg(feature = "image_qoi")]
46 "qoi",
47 #[cfg(feature = "image_ff")]
48 "ff",
49 #[cfg(feature = "image_ff")]
50 "farbfeld",
51];
52pub(crate) const DECODERS: &[&str] = &[
53 #[cfg(feature = "image_png")]
54 "png",
55 #[cfg(feature = "image_jpeg")]
56 "jpg",
57 #[cfg(feature = "image_jpeg")]
58 "jpeg",
59 #[cfg(feature = "image_webp")]
60 "webp",
61 #[cfg(any(feature = "image_avif", zng_view_image_has_avif))]
62 "avif",
63 #[cfg(feature = "image_gif")]
64 "gif",
65 #[cfg(feature = "image_ico")]
66 "ico",
67 #[cfg(feature = "image_bmp")]
68 "bmp",
69 #[cfg(feature = "image_jpeg")]
70 "jfif",
71 #[cfg(feature = "image_exr")]
72 "exr",
73 #[cfg(feature = "image_pnm")]
74 "pnm",
75 #[cfg(feature = "image_qoi")]
76 "qoi",
77 #[cfg(feature = "image_ff")]
78 "ff",
79 #[cfg(feature = "image_ff")]
80 "farbfeld",
81 #[cfg(feature = "image_dds")]
82 "dds",
83];
84
85pub(crate) struct ImageCache {
87 app_sender: AppEventSender,
88 images: FxHashMap<ImageId, Image>,
89 image_id_gen: ImageId,
90}
91impl ImageCache {
92 pub fn new(app_sender: AppEventSender) -> Self {
93 Self {
94 app_sender,
95 images: FxHashMap::default(),
96 image_id_gen: ImageId::first(),
97 }
98 }
99
100 pub fn add(
101 &mut self,
102 ImageRequest {
103 format,
104 data,
105 max_decoded_len,
106 downscale,
107 mask,
108 }: ImageRequest<IpcBytes>,
109 ) -> ImageId {
110 let id = self.image_id_gen.incr();
111
112 let app_sender = self.app_sender.clone();
113 rayon::spawn(move || {
114 let r = match format {
115 ImageDataFormat::Bgra8 { size, ppi } => {
116 let expected_len = size.width.0 as usize * size.height.0 as usize * 4;
117 if data.len() != expected_len {
118 Err(formatx!(
119 "pixels.len() is not width * height * 4, expected {expected_len}, found {}",
120 data.len()
121 ))
122 } else if mask.is_some() {
123 let (pixels, size, _, is_opaque, _) = Self::convert_decoded(
124 image::DynamicImage::ImageLuma8(
125 image::ImageBuffer::from_raw(size.width.0 as _, size.height.0 as _, data.to_vec()).unwrap(),
126 ),
127 mask,
128 );
129 Ok((pixels, size, ppi, is_opaque, true))
130 } else {
131 let is_opaque = data.chunks_exact(4).all(|c| c[3] == 255);
132 Ok((data, size, ppi, is_opaque, false))
133 }
134 }
135 ImageDataFormat::A8 { size } => {
136 let expected_len = size.width.0 as usize * size.height.0 as usize;
137 if data.len() != expected_len {
138 Err(formatx!(
139 "pixels.len() is not width * height, expected {expected_len}, found {}",
140 data.len()
141 ))
142 } else if mask.is_none() {
143 let (pixels, size, _, is_opaque, _) = Self::convert_decoded(
144 image::DynamicImage::ImageLuma8(
145 image::ImageBuffer::from_raw(size.width.0 as _, size.height.0 as _, data.to_vec()).unwrap(),
146 ),
147 None,
148 );
149 Ok((pixels, size, None, is_opaque, false))
150 } else {
151 let is_opaque = data.iter().all(|&c| c == 255);
152 Ok((data, size, None, is_opaque, true))
153 }
154 }
155 fmt => match Self::get_format_and_size(&fmt, &data[..]) {
156 Ok((fmt, mut size, orientation)) => {
157 let decoded_len = size.width.0 as u64 * size.height.0 as u64 * 4;
158 if decoded_len > max_decoded_len {
159 Err(formatx!(
160 "image {size:?} needs to allocate {decoded_len} bytes, but max allowed size is {max_decoded_len} bytes",
161 ))
162 } else {
163 if let Some(d) = downscale {
164 size = d.resize_dimensions(size);
165 }
166 let _ = app_sender.send(AppEvent::Notify(Event::ImageMetadataLoaded {
167 image: id,
168 size,
169 ppi: None,
170 is_mask: false,
171 }));
172 match Self::image_decode(&data[..], fmt, downscale, orientation) {
173 Ok(img) => Ok(Self::convert_decoded(img, mask)),
174 Err(e) => Err(e.to_txt()),
175 }
176 }
177 }
178 Err(e) => Err(e),
179 },
180 };
181
182 match r {
183 Ok((pixels, size, ppi, is_opaque, is_mask)) => {
184 let _ = app_sender.send(AppEvent::ImageLoaded(ImageLoadedData {
185 id,
186 pixels,
187 size,
188 ppi,
189 is_opaque,
190 is_mask,
191 }));
192 }
193 Err(e) => {
194 let _ = app_sender.send(AppEvent::Notify(Event::ImageLoadError { image: id, error: e }));
195 }
196 }
197 });
198
199 id
200 }
201
202 pub fn add_pro(
203 &mut self,
204 ImageRequest {
205 format,
206 data,
207 max_decoded_len,
208 downscale,
209 mask,
210 }: ImageRequest<IpcBytesReceiver>,
211 ) -> ImageId {
212 let id = self.image_id_gen.incr();
213 let app_sender = self.app_sender.clone();
214 rayon::spawn(move || {
215 let mut full = vec![];
217 let mut size = None;
218 let mut ppi = None;
219 let mut is_encoded = true;
220 let mut orientation = image::metadata::Orientation::NoTransforms;
221
222 let mut format = match format {
223 ImageDataFormat::Bgra8 { size: s, ppi: p } => {
224 is_encoded = false;
225 size = Some(s);
226 ppi = p;
227 None
228 }
229 ImageDataFormat::A8 { size: s } => {
230 is_encoded = false;
231 size = Some(s);
232 None
233 }
234 ImageDataFormat::FileExtension(ext) => image::ImageFormat::from_extension(ext.as_str()),
235 ImageDataFormat::MimeType(t) => t.strip_prefix("image/").and_then(image::ImageFormat::from_extension),
236 ImageDataFormat::Unknown => None,
237 };
238
239 let mut pending = true;
240 while pending {
241 match data.recv() {
242 Ok(d) => {
243 pending = !d.is_empty();
244
245 full.extend(d);
246
247 if let Some(fmt) = format {
248 if size.is_none() {
249 if let Ok(mut d) = image::ImageReader::with_format(std::io::Cursor::new(&full), fmt).into_decoder() {
250 use image::metadata::Orientation::*;
251
252 let (mut w, mut h) = d.dimensions();
253 orientation = d.orientation().unwrap_or(NoTransforms);
254
255 if matches!(orientation, Rotate90 | Rotate270 | Rotate90FlipH | Rotate270FlipH) {
256 mem::swap(&mut w, &mut h)
257 }
258
259 size = Some(PxSize::new(Px(w as i32), Px(h as i32)));
260 }
261
262 if let Some(s) = size {
263 let decoded_len = s.width.0 as u64 * s.height.0 as u64 * 4;
264 if decoded_len > max_decoded_len {
265 let error = formatx!(
266 "image {size:?} needs to allocate {decoded_len} bytes, but max allowed size is {max_decoded_len} bytes",
267 );
268 let _ = app_sender.send(AppEvent::Notify(Event::ImageLoadError { image: id, error }));
269 return;
270 }
271 }
272 }
273 } else if is_encoded {
274 format = image::guess_format(&full).ok();
275 }
276 }
277 Err(_) => {
278 return;
280 }
281 }
282 }
283
284 if let Some(fmt) = format {
285 match Self::image_decode(&full[..], fmt, downscale, orientation) {
286 Ok(img) => {
287 let (pixels, size, ppi, is_opaque, is_mask) = Self::convert_decoded(img, mask);
288 let _ = app_sender.send(AppEvent::ImageLoaded(ImageLoadedData {
289 id,
290 pixels,
291 size,
292 ppi,
293 is_opaque,
294 is_mask,
295 }));
296 }
297 Err(e) => {
298 let _ = app_sender.send(AppEvent::Notify(Event::ImageLoadError {
299 image: id,
300 error: e.to_txt(),
301 }));
302 }
303 }
304 } else if !is_encoded {
305 let pixels = IpcBytes::from_vec(full);
306 let is_opaque = pixels.chunks_exact(4).all(|c| c[3] == 255);
307 let _ = app_sender.send(AppEvent::ImageLoaded(ImageLoadedData {
308 id,
309 pixels,
310 size: size.unwrap(),
311 ppi,
312 is_opaque,
313 is_mask: false,
314 }));
315 } else {
316 let _ = app_sender.send(AppEvent::Notify(Event::ImageLoadError {
317 image: id,
318 error: Txt::from_static("unknown format"),
319 }));
320 }
321 });
322 id
323 }
324
325 pub fn forget(&mut self, id: ImageId) {
326 self.images.remove(&id);
327 }
328
329 pub fn get(&self, id: ImageId) -> Option<&Image> {
330 self.images.get(&id)
331 }
332
333 pub(crate) fn loaded(&mut self, data: ImageLoadedData) {
335 let mut flags = ImageDescriptorFlags::empty(); if data.is_opaque {
337 flags |= ImageDescriptorFlags::IS_OPAQUE
338 }
339
340 self.images.insert(
341 data.id,
342 Image(Arc::new(ImageData::RawData {
343 size: data.size,
344 pixels: data.pixels.clone(),
345 descriptor: ImageDescriptor::new(
346 data.size.width.0,
347 data.size.height.0,
348 if data.is_mask { ImageFormat::R8 } else { ImageFormat::BGRA8 },
349 flags,
350 ),
351 ppi: data.ppi,
352 })),
353 );
354
355 let _ = self.app_sender.send(AppEvent::Notify(Event::ImageLoaded(data)));
356 }
357
358 fn get_format_and_size(fmt: &ImageDataFormat, data: &[u8]) -> Result<(image::ImageFormat, PxSize, image::metadata::Orientation), Txt> {
359 let fmt = match fmt {
360 ImageDataFormat::FileExtension(ext) => image::ImageFormat::from_extension(ext.as_str()),
361 ImageDataFormat::MimeType(t) => t.strip_prefix("image/").and_then(image::ImageFormat::from_extension),
362 ImageDataFormat::Unknown => None,
363 ImageDataFormat::Bgra8 { .. } => unreachable!(),
364 ImageDataFormat::A8 { .. } => unreachable!(),
365 };
366
367 let reader = match fmt {
368 Some(fmt) => image::ImageReader::with_format(std::io::Cursor::new(data), fmt),
369 None => image::ImageReader::new(std::io::Cursor::new(data))
370 .with_guessed_format()
371 .map_err(|e| e.to_txt())?,
372 };
373
374 match reader.format() {
375 Some(fmt) => {
376 use image::metadata::Orientation::*;
377
378 let mut decoder = reader.into_decoder().map_err(|e| e.to_string())?;
379 let (mut w, mut h) = decoder.dimensions();
380 let orientation = decoder.orientation().unwrap_or(NoTransforms);
381
382 if matches!(orientation, Rotate90 | Rotate270 | Rotate90FlipH | Rotate270FlipH) {
383 mem::swap(&mut w, &mut h)
384 }
385
386 Ok((fmt, PxSize::new(Px(w as i32), Px(h as i32)), orientation))
387 }
388 None => Err(Txt::from_static("unknown format")),
389 }
390 }
391
392 fn image_decode(
393 buf: &[u8],
394 format: image::ImageFormat,
395 downscale: Option<ImageDownscale>,
396 orientation: image::metadata::Orientation,
397 ) -> image::ImageResult<image::DynamicImage> {
398 let buf = std::io::Cursor::new(buf);
399
400 let mut reader = image::ImageReader::new(buf);
401 reader.set_format(format);
402 reader.no_limits();
403 let mut image = reader.decode()?;
404
405 image.apply_orientation(orientation);
406
407 if let Some(s) = downscale {
408 let (img_w, img_h) = (image.width(), image.height());
409 match s {
410 ImageDownscale::Fit(s) => {
411 let w = img_w.min(s.width.0 as u32);
412 let h = img_h.min(s.height.0 as u32);
413 if w != img_w || h != img_h {
414 image = image.resize(w, h, image::imageops::FilterType::Triangle);
415 }
416 }
417 ImageDownscale::Fill(s) => {
418 let w = img_w.min(s.width.0 as u32);
419 let h = img_h.min(s.height.0 as u32);
420 if w != img_w && h != img_h {
421 image = image.resize_to_fill(w, h, image::imageops::FilterType::Triangle);
422 }
423 }
424 }
425 }
426
427 Ok(image)
428 }
429
430 fn convert_decoded(image: image::DynamicImage, mask: Option<ImageMaskMode>) -> RawLoadedImg {
431 use image::DynamicImage::*;
432
433 let mut is_opaque = true;
434
435 let (size, pixels) = match image {
436 ImageLuma8(img) => (
437 img.dimensions(),
438 if mask.is_some() {
439 let r = img.into_raw();
440 is_opaque = !r.iter().any(|&a| a < 255);
441 r
442 } else {
443 img.into_raw().into_iter().flat_map(|l| [l, l, l, 255]).collect()
444 },
445 ),
446 ImageLumaA8(img) => (
447 img.dimensions(),
448 if let Some(mask) = mask {
449 match mask {
450 ImageMaskMode::A => img
451 .into_raw()
452 .chunks(2)
453 .map(|la| {
454 if la[1] < 255 {
455 is_opaque = false;
456 }
457 la[1]
458 })
459 .collect(),
460 ImageMaskMode::B | ImageMaskMode::G | ImageMaskMode::R | ImageMaskMode::Luminance => img
461 .into_raw()
462 .chunks(2)
463 .map(|la| {
464 if la[0] < 255 {
465 is_opaque = false;
466 }
467 la[0]
468 })
469 .collect(),
470 }
471 } else {
472 img.into_raw()
473 .chunks(2)
474 .flat_map(|la| {
475 if la[1] < 255 {
476 is_opaque = false;
477 let l = la[0] as f32 * la[1] as f32 / 255.0;
478 let l = l as u8;
479 [l, l, l, la[1]]
480 } else {
481 let l = la[0];
482 [l, l, l, la[1]]
483 }
484 })
485 .collect()
486 },
487 ),
488 ImageRgb8(img) => (
489 img.dimensions(),
490 if let Some(mask) = mask {
491 match mask {
492 ImageMaskMode::Luminance | ImageMaskMode::A => img
493 .into_raw()
494 .chunks(3)
495 .map(|c| {
496 let c = luminance(c);
497 if c < 255 {
498 is_opaque = false;
499 }
500 c
501 })
502 .collect(),
503 mask => {
504 let channel = match mask {
505 ImageMaskMode::B => 2,
506 ImageMaskMode::G => 1,
507 ImageMaskMode::R => 0,
508 _ => unreachable!(),
509 };
510 img.into_raw()
511 .chunks(3)
512 .map(|c| {
513 let c = c[channel];
514 if c < 255 {
515 is_opaque = false;
516 }
517 c
518 })
519 .collect()
520 }
521 }
522 } else {
523 img.into_raw().chunks(3).flat_map(|c| [c[2], c[1], c[0], 255]).collect()
524 },
525 ),
526 ImageRgba8(img) => (
527 img.dimensions(),
528 if let Some(mask) = mask {
529 match mask {
530 ImageMaskMode::Luminance => img
531 .into_raw()
532 .chunks(4)
533 .map(|c| {
534 let c = luminance(&c[..3]);
535 if c < 255 {
536 is_opaque = false;
537 }
538 c
539 })
540 .collect(),
541 mask => {
542 let channel = match mask {
543 ImageMaskMode::A => 3,
544 ImageMaskMode::B => 2,
545 ImageMaskMode::G => 1,
546 ImageMaskMode::R => 0,
547 _ => unreachable!(),
548 };
549 img.into_raw()
550 .chunks(4)
551 .map(|c| {
552 let c = c[channel];
553 if c < 255 {
554 is_opaque = false;
555 }
556 c
557 })
558 .collect()
559 }
560 }
561 } else {
562 let mut buf = img.into_raw();
563 buf.chunks_mut(4).for_each(|c| {
564 if c[3] < 255 {
565 is_opaque = false;
566 let a = c[3] as f32 / 255.0;
567 c[0..3].iter_mut().for_each(|c| *c = (*c as f32 * a) as u8);
568 }
569 c.swap(0, 2);
570 });
571 buf
572 },
573 ),
574 ImageLuma16(img) => (
575 img.dimensions(),
576 if mask.is_some() {
577 img.into_raw()
578 .into_iter()
579 .map(|l| {
580 let l = (l as f32 / u16::MAX as f32 * 255.0) as u8;
581 if l < 255 {
582 is_opaque = false;
583 }
584 l
585 })
586 .collect()
587 } else {
588 img.into_raw()
589 .into_iter()
590 .flat_map(|l| {
591 let l = (l as f32 / u16::MAX as f32 * 255.0) as u8;
592 [l, l, l, 255]
593 })
594 .collect()
595 },
596 ),
597 ImageLumaA16(img) => (
598 img.dimensions(),
599 if let Some(mask) = mask {
600 match mask {
601 ImageMaskMode::A => img
602 .into_raw()
603 .chunks(2)
604 .map(|la| {
605 if la[1] < u16::MAX {
606 is_opaque = false;
607 }
608 let max = u16::MAX as f32;
609 let l = la[1] as f32 / max * 255.0;
610 l as u8
611 })
612 .collect(),
613 ImageMaskMode::B | ImageMaskMode::G | ImageMaskMode::R | ImageMaskMode::Luminance => img
614 .into_raw()
615 .chunks(2)
616 .map(|la| {
617 if la[0] < u16::MAX {
618 is_opaque = false;
619 }
620 let max = u16::MAX as f32;
621 let l = la[0] as f32 / max * 255.0;
622 l as u8
623 })
624 .collect(),
625 }
626 } else {
627 img.into_raw()
628 .chunks(2)
629 .flat_map(|la| {
630 let max = u16::MAX as f32;
631 let l = la[0] as f32 / max;
632 let a = la[1] as f32 / max * 255.0;
633
634 if la[1] < u16::MAX {
635 is_opaque = false;
636 let l = (l * a) as u8;
637 [l, l, l, a as u8]
638 } else {
639 let l = (l * 255.0) as u8;
640 [l, l, l, a as u8]
641 }
642 })
643 .collect()
644 },
645 ),
646 ImageRgb16(img) => (
647 img.dimensions(),
648 if let Some(mask) = mask {
649 match mask {
650 ImageMaskMode::Luminance | ImageMaskMode::A => img
651 .into_raw()
652 .chunks(3)
653 .map(|c| {
654 let c = luminance_16(c);
655 if c < 255 {
656 is_opaque = false;
657 }
658 c
659 })
660 .collect(),
661 mask => {
662 let channel = match mask {
663 ImageMaskMode::B => 2,
664 ImageMaskMode::G => 1,
665 ImageMaskMode::R => 0,
666 _ => unreachable!(),
667 };
668 img.into_raw()
669 .chunks(3)
670 .map(|c| {
671 let c = c[channel];
672 if c < u16::MAX {
673 is_opaque = false;
674 }
675
676 (c as f32 / u16::MAX as f32 * 255.0) as u8
677 })
678 .collect()
679 }
680 }
681 } else {
682 img.into_raw()
683 .chunks(3)
684 .flat_map(|c| {
685 let to_u8 = 255.0 / u16::MAX as f32;
686 [
687 (c[2] as f32 * to_u8) as u8,
688 (c[1] as f32 * to_u8) as u8,
689 (c[0] as f32 * to_u8) as u8,
690 255,
691 ]
692 })
693 .collect()
694 },
695 ),
696 ImageRgba16(img) => (
697 img.dimensions(),
698 if let Some(mask) = mask {
699 match mask {
700 ImageMaskMode::Luminance => img
701 .into_raw()
702 .chunks(4)
703 .map(|c| {
704 let c = luminance_16(&c[..3]);
705 if c < 255 {
706 is_opaque = false;
707 }
708 c
709 })
710 .collect(),
711 mask => {
712 let channel = match mask {
713 ImageMaskMode::A => 3,
714 ImageMaskMode::B => 2,
715 ImageMaskMode::G => 1,
716 ImageMaskMode::R => 0,
717 _ => unreachable!(),
718 };
719 img.into_raw()
720 .chunks(4)
721 .map(|c| {
722 let c = c[channel];
723 if c < 255 {
724 is_opaque = false;
725 }
726 (c as f32 / u16::MAX as f32 * 255.0) as u8
727 })
728 .collect()
729 }
730 }
731 } else {
732 img.into_raw()
733 .chunks(4)
734 .flat_map(|c| {
735 if c[3] < u16::MAX {
736 is_opaque = false;
737 let max = u16::MAX as f32;
738 let a = c[3] as f32 / max * 255.0;
739 [
740 (c[2] as f32 / max * a) as u8,
741 (c[1] as f32 / max * a) as u8,
742 (c[0] as f32 / max * a) as u8,
743 a as u8,
744 ]
745 } else {
746 let to_u8 = 255.0 / u16::MAX as f32;
747 [
748 (c[2] as f32 * to_u8) as u8,
749 (c[1] as f32 * to_u8) as u8,
750 (c[0] as f32 * to_u8) as u8,
751 255,
752 ]
753 }
754 })
755 .collect()
756 },
757 ),
758 ImageRgb32F(img) => (
759 img.dimensions(),
760 if let Some(mask) = mask {
761 match mask {
762 ImageMaskMode::Luminance | ImageMaskMode::A => img
763 .into_raw()
764 .chunks(3)
765 .map(|c| {
766 let c = luminance_f32(c);
767 if c < 255 {
768 is_opaque = false;
769 }
770 c
771 })
772 .collect(),
773 mask => {
774 let channel = match mask {
775 ImageMaskMode::B => 2,
776 ImageMaskMode::G => 1,
777 ImageMaskMode::R => 0,
778 _ => unreachable!(),
779 };
780 img.into_raw()
781 .chunks(3)
782 .map(|c| {
783 let c = (c[channel] * 255.0) as u8;
784 if c < 255 {
785 is_opaque = false;
786 }
787 c
788 })
789 .collect()
790 }
791 }
792 } else {
793 img.into_raw()
794 .chunks(3)
795 .flat_map(|c| [(c[2] * 255.0) as u8, (c[1] * 255.0) as u8, (c[0] * 255.0) as u8, 255])
796 .collect()
797 },
798 ),
799 ImageRgba32F(img) => (
800 img.dimensions(),
801 if let Some(mask) = mask {
802 match mask {
803 ImageMaskMode::Luminance => img
804 .into_raw()
805 .chunks(4)
806 .map(|c| {
807 let c = luminance_f32(&c[..3]);
808 if c < 255 {
809 is_opaque = false;
810 }
811 c
812 })
813 .collect(),
814 mask => {
815 let channel = match mask {
816 ImageMaskMode::A => 3,
817 ImageMaskMode::B => 2,
818 ImageMaskMode::G => 1,
819 ImageMaskMode::R => 0,
820 _ => unreachable!(),
821 };
822 img.into_raw()
823 .chunks(4)
824 .map(|c| {
825 let c = (c[channel] * 255.0) as u8;
826 if c < 255 {
827 is_opaque = false;
828 }
829 c
830 })
831 .collect()
832 }
833 }
834 } else {
835 img.into_raw()
836 .chunks(4)
837 .flat_map(|c| {
838 if c[3] < 1.0 {
839 is_opaque = false;
840 let a = c[3] * 255.0;
841 [(c[2] * a) as u8, (c[1] * a) as u8, (c[0] * a) as u8, a as u8]
842 } else {
843 [(c[2] * 255.0) as u8, (c[1] * 255.0) as u8, (c[0] * 255.0) as u8, 255]
844 }
845 })
846 .collect()
847 },
848 ),
849 _ => unreachable!(),
850 };
851
852 (
853 IpcBytes::from_vec(pixels),
854 PxSize::new(Px(size.0 as i32), Px(size.1 as i32)),
855 None,
856 is_opaque,
857 mask.is_some(), )
859 }
860
861 pub fn encode(&self, id: ImageId, format: Txt) {
862 if !ENCODERS.contains(&format.as_str()) {
863 let error = formatx!("cannot encode `{id:?}` to `{format}`, unknown format");
864 let _ = self
865 .app_sender
866 .send(AppEvent::Notify(Event::ImageEncodeError { image: id, format, error }));
867 return;
868 }
869
870 if let Some(img) = self.get(id) {
871 let fmt = image::ImageFormat::from_extension(format.as_str()).unwrap();
872 debug_assert!(fmt.can_write());
873
874 let img = img.clone();
875 let sender = self.app_sender.clone();
876 rayon::spawn(move || {
877 let mut data = vec![];
878 match img.encode(fmt, &mut data) {
879 Ok(_) => {
880 let _ = sender.send(AppEvent::Notify(Event::ImageEncoded {
881 image: id,
882 format,
883 data: IpcBytes::from_vec(data),
884 }));
885 }
886 Err(e) => {
887 let error = formatx!("failed to encode `{id:?}` to `{format}`, {e}");
888 let _ = sender.send(AppEvent::Notify(Event::ImageEncodeError { image: id, format, error }));
889 }
890 }
891 })
892 } else {
893 let error = formatx!("cannot encode `{id:?}` to `{format}`, image not found");
894 let _ = self
895 .app_sender
896 .send(AppEvent::Notify(Event::ImageEncodeError { image: id, format, error }));
897 }
898 }
899
900 pub(crate) fn on_low_memory(&mut self) {
901 }
903
904 pub(crate) fn clear(&mut self) {
905 self.images.clear();
906 }
907}
908
909type RawLoadedImg = (IpcBytes, PxSize, Option<ImagePpi>, bool, bool);
911pub(crate) enum ImageData {
912 RawData {
913 size: PxSize,
914 pixels: IpcBytes,
915 descriptor: ImageDescriptor,
916 ppi: Option<ImagePpi>,
917 },
918 NativeTexture {
919 uv: webrender::api::units::TexelRect,
920 texture: gleam::gl::GLuint,
921 },
922}
923impl ImageData {
924 pub fn is_opaque(&self) -> bool {
925 match self {
926 ImageData::RawData { descriptor, .. } => descriptor.flags.contains(ImageDescriptorFlags::IS_OPAQUE),
927 ImageData::NativeTexture { .. } => false,
928 }
929 }
930
931 pub fn is_mask(&self) -> bool {
932 match self {
933 ImageData::RawData { descriptor, .. } => descriptor.format == ImageFormat::R8,
934 ImageData::NativeTexture { .. } => false,
935 }
936 }
937}
938
939#[derive(Clone)]
940pub(crate) struct Image(Arc<ImageData>);
941impl fmt::Debug for Image {
942 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
943 match &*self.0 {
944 ImageData::RawData {
945 size,
946 pixels,
947 descriptor,
948 ppi,
949 } => f
950 .debug_struct("Image")
951 .field("size", size)
952 .field("descriptor", descriptor)
953 .field("ppi", ppi)
954 .field("pixels", &format_args!("<{} shared bytes>", pixels.len()))
955 .finish(),
956 ImageData::NativeTexture { .. } => unreachable!(),
957 }
958 }
959}
960impl Image {
961 pub fn descriptor(&self) -> ImageDescriptor {
962 match &*self.0 {
963 ImageData::RawData { descriptor, .. } => *descriptor,
964 ImageData::NativeTexture { .. } => unreachable!(),
965 }
966 }
967
968 pub fn icon(&self) -> Option<Icon> {
970 let (size, pixels) = match &*self.0 {
971 ImageData::RawData { size, pixels, .. } => (size, pixels),
972 ImageData::NativeTexture { .. } => unreachable!(),
973 };
974
975 let width = size.width.0 as u32;
976 let height = size.height.0 as u32;
977 if width == 0 || height == 0 || self.0.is_mask() {
978 None
979 } else {
980 let r = if width > 255 || height > 255 {
981 let mut buf = pixels[..].to_vec();
983 buf.chunks_exact_mut(4).for_each(|c| c.swap(0, 2));
985 let img = image::ImageBuffer::from_raw(width, height, buf).unwrap();
986 let img = image::DynamicImage::ImageRgba8(img);
987 let img = img.resize(255, 255, image::imageops::FilterType::Lanczos3);
988
989 use image::GenericImageView;
990 let (width, height) = img.dimensions();
991 let buf = img.into_rgba8().into_raw();
992 winit::window::Icon::from_rgba(buf, width, height)
993 } else {
994 let mut buf = pixels[..].to_vec();
995 buf.chunks_exact_mut(4).for_each(|c| c.swap(0, 2));
997 winit::window::Icon::from_rgba(buf, width, height)
998 };
999 match r {
1000 Ok(i) => Some(i),
1001 Err(e) => {
1002 tracing::error!("failed to convert image to custom icon, {e}");
1003 None
1004 }
1005 }
1006 }
1007 }
1008
1009 pub fn cursor(&self, hotspot: PxPoint, event_loop: &ActiveEventLoop) -> Option<CustomCursor> {
1011 let (size, pixels) = match &*self.0 {
1012 ImageData::RawData { size, pixels, .. } => (size, pixels),
1013 ImageData::NativeTexture { .. } => unreachable!(),
1014 };
1015
1016 let width: u16 = size.width.0.try_into().ok()?;
1017 let height: u16 = size.height.0.try_into().ok()?;
1018 let hotspot_x: u16 = hotspot.x.0.try_into().ok()?;
1019 let hotspot_y: u16 = hotspot.y.0.try_into().ok()?;
1020
1021 if width == 0 || height == 0 || hotspot_x > width || hotspot_y > height || self.0.is_mask() {
1022 None
1023 } else {
1024 let mut buf = pixels[..].to_vec();
1025 buf.chunks_exact_mut(4).for_each(|c| c.swap(0, 2));
1027 match CustomCursor::from_rgba(buf, width, height, hotspot_x, hotspot_y) {
1028 Ok(c) => Some(event_loop.create_custom_cursor(c)),
1029 Err(e) => {
1030 tracing::error!("failed to convert image to custom cursor, {e}");
1031 None
1032 }
1033 }
1034 }
1035 }
1036
1037 pub fn encode(&self, format: image::ImageFormat, buffer: &mut Vec<u8>) -> image::ImageResult<()> {
1038 let (size, pixels, ppi) = match &*self.0 {
1039 ImageData::RawData { size, pixels, ppi, .. } => (size, pixels, ppi),
1040 ImageData::NativeTexture { .. } => unreachable!(),
1041 };
1042
1043 if size.width <= 0 || size.height <= 0 {
1044 return Err(image::ImageError::IoError(std::io::Error::new(
1045 std::io::ErrorKind::InvalidInput,
1046 "cannot encode zero sized image",
1047 )));
1048 }
1049
1050 if self.0.is_mask() {
1051 let width = size.width.0 as u32;
1052 let height = size.height.0 as u32;
1053 let is_opaque = self.0.is_opaque();
1054 let r8 = pixels[..].to_vec();
1055
1056 let mut img = image::DynamicImage::ImageLuma8(image::ImageBuffer::from_raw(width, height, r8).unwrap());
1057 if is_opaque {
1058 img = image::DynamicImage::ImageRgb8(img.to_rgb8());
1059 }
1060 img.write_to(&mut std::io::Cursor::new(buffer), format)?;
1061
1062 return Ok(());
1063 }
1064
1065 let mut buf = pixels[..].to_vec();
1067 buf.chunks_exact_mut(4).for_each(|c| c.swap(0, 2));
1069 let rgba = buf;
1070
1071 let width = size.width.0 as u32;
1072 let height = size.height.0 as u32;
1073 let is_opaque = self.0.is_opaque();
1074
1075 match format {
1076 #[cfg(feature = "image_jpeg")]
1077 image::ImageFormat::Jpeg => {
1078 let mut jpg = image::codecs::jpeg::JpegEncoder::new(buffer);
1079 if let Some(ppi) = ppi {
1080 jpg.set_pixel_density(image::codecs::jpeg::PixelDensity {
1081 density: (ppi.x as u16, ppi.y as u16),
1082 unit: image::codecs::jpeg::PixelDensityUnit::Inches,
1083 });
1084 }
1085 jpg.encode(&rgba, width, height, image::ColorType::Rgba8.into())?;
1086 }
1087 #[cfg(feature = "image_png")]
1088 image::ImageFormat::Png => {
1089 let mut img = image::DynamicImage::ImageRgba8(image::ImageBuffer::from_raw(width, height, rgba).unwrap());
1090 if is_opaque {
1091 img = image::DynamicImage::ImageRgb8(img.to_rgb8());
1092 }
1093 if let Some(ppi) = ppi {
1094 let mut png_bytes = vec![];
1095
1096 img.write_to(&mut std::io::Cursor::new(&mut png_bytes), image::ImageFormat::Png)?;
1097
1098 let mut png = img_parts::png::Png::from_bytes(png_bytes.into()).unwrap();
1099
1100 let chunk_kind = *b"pHYs";
1101 debug_assert!(png.chunk_by_type(chunk_kind).is_none());
1102
1103 use byteorder::*;
1104 let mut chunk = Vec::with_capacity(4 * 2 + 1);
1105
1106 let ppm_x = (ppi.x / 0.0254) as u32;
1108 let ppm_y = (ppi.y / 0.0254) as u32;
1109
1110 chunk.write_u32::<BigEndian>(ppm_x).unwrap();
1111 chunk.write_u32::<BigEndian>(ppm_y).unwrap();
1112 chunk.write_u8(1).unwrap(); let chunk = img_parts::png::PngChunk::new(chunk_kind, chunk.into());
1115 png.chunks_mut().insert(1, chunk);
1116
1117 png.encoder().write_to(buffer)?;
1118 } else {
1119 img.write_to(&mut std::io::Cursor::new(buffer), image::ImageFormat::Png)?;
1120 }
1121 }
1122 _ => {
1123 let _ = ppi; let mut img = image::DynamicImage::ImageRgba8(image::ImageBuffer::from_raw(width, height, rgba).unwrap());
1128 if is_opaque {
1129 img = image::DynamicImage::ImageRgb8(img.to_rgb8());
1130 }
1131 img.write_to(&mut std::io::Cursor::new(buffer), format)?;
1132 }
1133 }
1134
1135 Ok(())
1136 }
1137
1138 #[allow(unused)]
1139 pub fn size(&self) -> PxSize {
1140 match &*self.0 {
1141 ImageData::RawData { size, .. } => *size,
1142 ImageData::NativeTexture { .. } => unreachable!(),
1143 }
1144 }
1145
1146 #[allow(unused)]
1147 pub fn pixels(&self) -> &IpcBytes {
1148 match &*self.0 {
1149 ImageData::RawData { pixels, .. } => pixels,
1150 ImageData::NativeTexture { .. } => unreachable!(),
1151 }
1152 }
1153}
1154
1155mod external {
1158 use std::{collections::hash_map::Entry, sync::Arc};
1159
1160 use rustc_hash::FxHashMap;
1161 use webrender::{
1162 RenderApi,
1163 api::{
1164 DocumentId, ExternalImage, ExternalImageData, ExternalImageHandler, ExternalImageId, ExternalImageSource, ExternalImageType,
1165 ImageKey,
1166 units::{ImageDirtyRect, TexelRect},
1167 },
1168 };
1169 use zng_view_api::image::ImageTextureId;
1170
1171 use super::{Image, ImageData};
1172
1173 pub(crate) struct WrImageCache {
1179 locked: Vec<Arc<ImageData>>,
1180 }
1181 impl WrImageCache {
1182 pub fn new_boxed() -> Box<dyn ExternalImageHandler> {
1183 Box::new(WrImageCache { locked: vec![] })
1184 }
1185 }
1186 impl ExternalImageHandler for WrImageCache {
1187 fn lock(&mut self, key: ExternalImageId, _channel_index: u8) -> ExternalImage {
1188 let img = unsafe {
1190 let ptr = key.0 as *const ImageData;
1191 Arc::increment_strong_count(ptr);
1192 Arc::<ImageData>::from_raw(ptr)
1193 };
1194
1195 self.locked.push(img); match &**self.locked.last().unwrap() {
1198 ImageData::RawData { pixels, .. } => {
1199 ExternalImage {
1200 uv: TexelRect::invalid(), source: ExternalImageSource::RawData(&pixels[..]),
1202 }
1203 }
1204 ImageData::NativeTexture { uv, texture: id } => ExternalImage {
1205 uv: *uv,
1206 source: ExternalImageSource::NativeTexture(*id),
1207 },
1208 }
1209 }
1210
1211 fn unlock(&mut self, key: ExternalImageId, _channel_index: u8) {
1212 if let Some(i) = self.locked.iter().position(|d| ExternalImageId(Arc::as_ptr(d) as _) == key) {
1213 self.locked.swap_remove(i);
1214 } else {
1215 debug_assert!(false);
1216 }
1217 }
1218 }
1219
1220 impl Image {
1221 fn external_id(&self) -> ExternalImageId {
1222 ExternalImageId(Arc::as_ptr(&self.0) as u64)
1223 }
1224
1225 fn data(&self) -> webrender::api::ImageData {
1226 webrender::api::ImageData::External(ExternalImageData {
1227 id: self.external_id(),
1228 channel_index: 0,
1229 image_type: ExternalImageType::Buffer,
1230 normalized_uvs: false,
1231 })
1232 }
1233 }
1234
1235 #[derive(Default)]
1239 pub(crate) struct ImageUseMap {
1240 id_tex: FxHashMap<ExternalImageId, (ImageTextureId, Image)>,
1241 tex_id: FxHashMap<ImageTextureId, ExternalImageId>,
1242 }
1243 impl ImageUseMap {
1244 pub fn new_use(&mut self, image: &Image, document_id: DocumentId, api: &mut RenderApi) -> ImageTextureId {
1245 let id = image.external_id();
1246 match self.id_tex.entry(id) {
1247 Entry::Occupied(e) => e.get().0,
1248 Entry::Vacant(e) => {
1249 let key = api.generate_image_key();
1250 let tex_id = ImageTextureId::from_raw(key.1);
1251 e.insert((tex_id, image.clone())); self.tex_id.insert(tex_id, id);
1253
1254 let mut txn = webrender::Transaction::new();
1255 txn.add_image(key, image.descriptor(), image.data(), None);
1256 api.send_transaction(document_id, txn);
1257
1258 tex_id
1259 }
1260 }
1261 }
1262
1263 pub fn update_use(&mut self, texture_id: ImageTextureId, image: &Image, document_id: DocumentId, api: &mut RenderApi) {
1265 if let Entry::Occupied(mut e) = self.tex_id.entry(texture_id) {
1266 let id = image.external_id();
1267 if *e.get() != id {
1268 let prev_id = e.insert(id);
1269 self.id_tex.remove(&prev_id).unwrap();
1270 self.id_tex.insert(id, (texture_id, image.clone()));
1271
1272 let mut txn = webrender::Transaction::new();
1273 txn.update_image(
1274 ImageKey(api.get_namespace_id(), texture_id.get()),
1275 image.descriptor(),
1276 image.data(),
1277 &ImageDirtyRect::All,
1278 );
1279 api.send_transaction(document_id, txn);
1280 }
1281 }
1282 }
1283
1284 pub fn delete(&mut self, texture_id: ImageTextureId, document_id: DocumentId, api: &mut RenderApi) {
1285 if let Some(id) = self.tex_id.remove(&texture_id) {
1286 let _img = self.id_tex.remove(&id); let mut txn = webrender::Transaction::new();
1288 txn.delete_image(ImageKey(api.get_namespace_id(), texture_id.get()));
1289 api.send_transaction(document_id, txn);
1290 }
1291 }
1292 }
1293}
1294pub(crate) use external::{ImageUseMap, WrImageCache};
1295
1296mod capture {
1297 use std::sync::Arc;
1298
1299 use webrender::api::{ImageDescriptor, ImageDescriptorFlags, ImageFormat};
1300 use zng_txt::formatx;
1301 use zng_unit::{Factor, PxRect};
1302 use zng_view_api::{
1303 Event,
1304 image::{ImageDataFormat, ImageId, ImageLoadedData, ImageMaskMode, ImagePpi, ImageRequest},
1305 ipc::IpcBytes,
1306 window::{FrameId, WindowId},
1307 };
1308
1309 use crate::{
1310 AppEvent,
1311 image_cache::{Image, ImageData},
1312 };
1313
1314 use super::ImageCache;
1315
1316 impl ImageCache {
1317 pub fn frame_image(
1319 &mut self,
1320 gl: &dyn gleam::gl::Gl,
1321 rect: PxRect,
1322 window_id: WindowId,
1323 frame_id: FrameId,
1324 scale_factor: Factor,
1325 mask: Option<ImageMaskMode>,
1326 ) -> ImageId {
1327 if frame_id == FrameId::INVALID {
1328 let id = self.image_id_gen.incr();
1329 let _ = self.app_sender.send(AppEvent::Notify(Event::ImageLoadError {
1330 image: id,
1331 error: formatx!("no frame rendered in window `{window_id:?}`"),
1332 }));
1333 let _ = self.app_sender.send(AppEvent::Notify(Event::FrameImageReady {
1334 window: window_id,
1335 frame: frame_id,
1336 image: id,
1337 selection: rect,
1338 }));
1339 return id;
1340 }
1341
1342 let data = self.frame_image_data(gl, rect, scale_factor, mask);
1343
1344 let id = data.id;
1345
1346 let _ = self.app_sender.send(AppEvent::ImageLoaded(data));
1347 let _ = self.app_sender.send(AppEvent::Notify(Event::FrameImageReady {
1348 window: window_id,
1349 frame: frame_id,
1350 image: id,
1351 selection: rect,
1352 }));
1353
1354 id
1355 }
1356
1357 pub fn frame_image_data(
1359 &mut self,
1360 gl: &dyn gleam::gl::Gl,
1361 rect: PxRect,
1362 scale_factor: Factor,
1363 mask: Option<ImageMaskMode>,
1364 ) -> ImageLoadedData {
1365 let data = self.frame_image_data_impl(gl, rect, scale_factor, mask);
1366
1367 let flags = if data.is_opaque {
1368 ImageDescriptorFlags::IS_OPAQUE
1369 } else {
1370 ImageDescriptorFlags::empty()
1371 };
1372
1373 self.images.insert(
1374 data.id,
1375 Image(Arc::new(ImageData::RawData {
1376 size: data.size,
1377 pixels: data.pixels.clone(),
1378 descriptor: ImageDescriptor::new(
1379 data.size.width.0,
1380 data.size.height.0,
1381 if data.is_mask { ImageFormat::R8 } else { ImageFormat::BGRA8 },
1382 flags,
1383 ),
1384 ppi: data.ppi,
1385 })),
1386 );
1387
1388 data
1389 }
1390
1391 fn frame_image_data_impl(
1392 &mut self,
1393 gl: &dyn gleam::gl::Gl,
1394 rect: PxRect,
1395 scale_factor: Factor,
1396 mask: Option<ImageMaskMode>,
1397 ) -> ImageLoadedData {
1398 let format = match gl.get_type() {
1399 gleam::gl::GlType::Gl => gleam::gl::BGRA,
1400 gleam::gl::GlType::Gles => gleam::gl::RGBA,
1401 };
1402 let pixels_flipped = gl.read_pixels(
1403 rect.origin.x.0,
1404 rect.origin.y.0,
1405 rect.size.width.0,
1406 rect.size.height.0,
1407 format,
1408 gleam::gl::UNSIGNED_BYTE,
1409 );
1410 let mut buf = vec![0u8; pixels_flipped.len()];
1411 assert_eq!(rect.size.width.0 as usize * rect.size.height.0 as usize * 4, buf.len());
1412 let stride = 4 * rect.size.width.0 as usize;
1413 for (px, buf) in pixels_flipped.chunks_exact(stride).rev().zip(buf.chunks_exact_mut(stride)) {
1414 buf.copy_from_slice(px);
1415 }
1416
1417 if let Some(mask) = mask {
1418 if format == gleam::gl::BGRA {
1419 for bgra in buf.chunks_exact_mut(4) {
1420 bgra.swap(0, 3);
1421 }
1422 }
1423 let (pixels, size, ppi, is_opaque, is_mask) = Self::convert_decoded(
1424 image::DynamicImage::ImageRgba8(
1425 image::ImageBuffer::from_raw(rect.size.width.0 as u32, rect.size.height.0 as u32, buf).unwrap(),
1426 ),
1427 Some(mask),
1428 );
1429
1430 let id = self.add(ImageRequest {
1431 format: ImageDataFormat::A8 { size },
1432 data: pixels.clone(),
1433 max_decoded_len: u64::MAX,
1434 downscale: None,
1435 mask: Some(mask),
1436 });
1437
1438 ImageLoadedData {
1439 id,
1440 size,
1441 ppi,
1442 is_opaque,
1443 is_mask,
1444 pixels,
1445 }
1446 } else {
1447 if format == gleam::gl::RGBA {
1448 for rgba in buf.chunks_exact_mut(4) {
1449 rgba.swap(0, 3);
1450 }
1451 }
1452
1453 let is_opaque = buf.chunks_exact(4).all(|bgra| bgra[3] == 255);
1454
1455 let data = IpcBytes::from_vec(buf);
1456 let ppi = 96.0 * scale_factor.0;
1457 let ppi = Some(ImagePpi::splat(ppi));
1458 let size = rect.size;
1459
1460 let id = self.add(ImageRequest {
1461 format: ImageDataFormat::Bgra8 { size, ppi },
1462 data: data.clone(),
1463 max_decoded_len: u64::MAX,
1464 downscale: None,
1465 mask,
1466 });
1467
1468 ImageLoadedData {
1469 id,
1470 size,
1471 ppi,
1472 is_opaque,
1473 pixels: data,
1474 is_mask: false,
1475 }
1476 }
1477 }
1478 }
1479}
1480
1481fn luminance(rgb: &[u8]) -> u8 {
1482 let r = rgb[0] as f32 / 255.0;
1483 let g = rgb[1] as f32 / 255.0;
1484 let b = rgb[2] as f32 / 255.0;
1485
1486 let l = r * 0.2126 + g * 0.7152 + b * 0.0722;
1487 (l * 255.0) as u8
1488}
1489
1490fn luminance_16(rgb: &[u16]) -> u8 {
1491 let max = u16::MAX as f32;
1492 let r = rgb[0] as f32 / max;
1493 let g = rgb[1] as f32 / max;
1494 let b = rgb[2] as f32 / max;
1495
1496 let l = r * 0.2126 + g * 0.7152 + b * 0.0722;
1497 (l * 255.0) as u8
1498}
1499
1500fn luminance_f32(rgb: &[f32]) -> u8 {
1501 let r = rgb[0];
1502 let g = rgb[1];
1503 let b = rgb[2];
1504
1505 let l = r * 0.2126 + g * 0.7152 + b * 0.0722;
1506 (l * 255.0) as u8
1507}