1use std::{
2 env, fmt, fs, io, mem, ops,
3 path::{Path, PathBuf},
4 sync::Arc,
5};
6
7use once_cell::sync::OnceCell;
8use parking_lot::Mutex;
9use zng_app::{
10 view_process::{EncodeError, ViewImage, ViewRenderer},
11 window::WindowId,
12};
13use zng_color::{
14 Hsla, Rgba,
15 gradient::{ExtendMode, GradientStops},
16};
17use zng_layout::{
18 context::{LAYOUT, LayoutMetrics, LayoutPassId},
19 unit::{ByteLength, ByteUnits, FactorUnits as _, LayoutAxis, Px, PxDensity2d, PxLine, PxPoint, PxRect, PxSize},
20};
21use zng_task::{self as task, SignalOnce, channel::IpcBytes};
22use zng_txt::Txt;
23use zng_var::{Var, animation::Transitionable, impl_from_and_into_var};
24use zng_view_api::image::ImageTextureId;
25
26use crate::render::ImageRenderWindowRoot;
27
28pub use zng_view_api::image::{ImageDataFormat, ImageDownscale, ImageMaskMode};
29
30pub trait ImageCacheProxy: Send + Sync {
39 fn get(
41 &mut self,
42 key: &ImageHash,
43 source: &ImageSource,
44 mode: ImageCacheMode,
45 downscale: Option<ImageDownscale>,
46 mask: Option<ImageMaskMode>,
47 ) -> ProxyGetResult {
48 let r = match source {
49 ImageSource::Data(_, data, image_format) => self.data(key, data, image_format, mode, downscale, mask, false),
50 _ => return ProxyGetResult::None,
51 };
52 match r {
53 Some(img) => ProxyGetResult::Image(img),
54 None => ProxyGetResult::None,
55 }
56 }
57
58 #[allow(clippy::too_many_arguments)]
71 fn data(
72 &mut self,
73 key: &ImageHash,
74 data: &[u8],
75 image_format: &ImageDataFormat,
76 mode: ImageCacheMode,
77 downscale: Option<ImageDownscale>,
78 mask: Option<ImageMaskMode>,
79 is_loaded: bool,
80 ) -> Option<ImageVar> {
81 let _ = (key, data, image_format, mode, downscale, mask, is_loaded);
82 None
83 }
84
85 fn remove(&mut self, key: &ImageHash, purge: bool) -> ProxyRemoveResult {
87 let _ = (key, purge);
88 ProxyRemoveResult::None
89 }
90
91 fn clear(&mut self, purge: bool) {
93 let _ = purge;
94 }
95
96 fn is_data_proxy(&self) -> bool {
110 false
111 }
112}
113
114pub enum ProxyGetResult {
116 None,
120 Cache(ImageSource, ImageCacheMode, Option<ImageDownscale>, Option<ImageMaskMode>),
122 Image(ImageVar),
124}
125
126pub enum ProxyRemoveResult {
128 None,
132 Remove(ImageHash, bool),
136 Removed,
138}
139
140pub type ImageVar = Var<Img>;
146
147#[derive(Debug, Clone)]
151pub struct Img {
152 pub(super) view: OnceCell<ViewImage>,
154 render_ids: Arc<Mutex<Vec<RenderImage>>>,
155 pub(super) done_signal: SignalOnce,
156 pub(super) cache_key: Option<ImageHash>,
157}
158impl PartialEq for Img {
159 fn eq(&self, other: &Self) -> bool {
160 self.view == other.view
161 }
162}
163impl Img {
164 pub(super) fn new_none(cache_key: Option<ImageHash>) -> Self {
165 Img {
166 view: OnceCell::new(),
167 render_ids: Arc::default(),
168 done_signal: SignalOnce::new(),
169 cache_key,
170 }
171 }
172
173 pub fn new(view: ViewImage) -> Self {
175 let sig = view.awaiter();
176 let v = OnceCell::new();
177 let _ = v.set(view);
178 Img {
179 view: v,
180 render_ids: Arc::default(),
181 done_signal: sig,
182 cache_key: None,
183 }
184 }
185
186 pub fn dummy(error: Option<Txt>) -> Self {
188 Self::new(ViewImage::dummy(error))
189 }
190
191 pub fn is_loading(&self) -> bool {
193 match self.view.get() {
194 Some(v) => !v.is_loaded() && !v.is_error(),
195 None => true,
196 }
197 }
198
199 pub fn is_loaded(&self) -> bool {
201 match self.view.get() {
202 Some(v) => v.is_loaded(),
203 None => false,
204 }
205 }
206
207 pub fn is_error(&self) -> bool {
209 match self.view.get() {
210 Some(v) => v.is_error(),
211 None => false,
212 }
213 }
214
215 pub fn error(&self) -> Option<Txt> {
217 match self.view.get() {
218 Some(v) => v.error(),
219 None => None,
220 }
221 }
222
223 pub fn wait_done(&self) -> impl Future<Output = ()> + Send + Sync + 'static {
225 self.done_signal.clone()
226 }
227
228 pub fn size(&self) -> PxSize {
230 self.view.get().map(|v| v.size()).unwrap_or_else(PxSize::zero)
231 }
232
233 pub fn density(&self) -> Option<PxDensity2d> {
236 self.view.get().and_then(|v| v.density())
237 }
238
239 pub fn is_opaque(&self) -> bool {
241 self.view.get().map(|v| v.is_opaque()).unwrap_or(true)
242 }
243
244 pub fn is_mask(&self) -> bool {
246 self.view.get().map(|v| v.is_mask()).unwrap_or(false)
247 }
248
249 pub fn view(&self) -> Option<&ViewImage> {
251 self.view.get().filter(|&v| v.is_loaded())
252 }
253
254 pub fn layout_size(&self, ctx: &LayoutMetrics) -> PxSize {
262 self.calc_size(ctx, PxDensity2d::splat(ctx.screen_density()), false)
263 }
264
265 pub fn calc_size(&self, ctx: &LayoutMetrics, fallback_density: PxDensity2d, ignore_image_density: bool) -> PxSize {
275 let dpi = if ignore_image_density {
276 fallback_density
277 } else {
278 self.density().unwrap_or(fallback_density)
279 };
280
281 let s_density = ctx.screen_density();
282 let mut size = self.size();
283
284 let fct = ctx.scale_factor().0;
285 size.width *= (s_density.ppcm() / dpi.width.ppcm()) * fct;
286 size.height *= (s_density.ppcm() / dpi.height.ppcm()) * fct;
287
288 size
289 }
290
291 pub fn pixels(&self) -> Option<zng_task::channel::IpcBytes> {
295 self.view.get().and_then(|v| v.pixels())
296 }
297
298 pub fn copy_pixels(&self, rect: PxRect) -> Option<(PxRect, Vec<u8>)> {
306 self.pixels().map(|pixels| {
307 let area = PxRect::from_size(self.size()).intersection(&rect).unwrap_or_default();
308 if area.size.width.0 == 0 || area.size.height.0 == 0 {
309 (area, vec![])
310 } else {
311 let x = area.origin.x.0 as usize;
312 let y = area.origin.y.0 as usize;
313 let width = area.size.width.0 as usize;
314 let height = area.size.height.0 as usize;
315 let pixel = if self.is_mask() { 1 } else { 4 };
316 let mut bytes = Vec::with_capacity(width * height * pixel);
317 let row_stride = self.size().width.0 as usize * pixel;
318 for l in y..y + height {
319 let line_start = l * row_stride + x * pixel;
320 let line_end = line_start + width * pixel;
321 let line = &pixels[line_start..line_end];
322 bytes.extend_from_slice(line);
323 }
324 (area, bytes)
325 }
326 })
327 }
328
329 pub async fn encode(&self, format: Txt) -> std::result::Result<zng_task::channel::IpcBytes, EncodeError> {
331 self.done_signal.clone().await;
332 if let Some(e) = self.error() {
333 Err(EncodeError::Encode(e))
334 } else {
335 self.view.get().unwrap().encode(format).await
336 }
337 }
338
339 pub async fn save(&self, path: impl Into<PathBuf>) -> io::Result<()> {
343 let path = path.into();
344 if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
345 self.save_impl(Txt::from_str(ext), path).await
346 } else {
347 Err(io::Error::new(
348 io::ErrorKind::InvalidInput,
349 "could not determinate image format from path extension",
350 ))
351 }
352 }
353
354 pub async fn save_with_format(&self, format: Txt, path: impl Into<PathBuf>) -> io::Result<()> {
358 self.save_impl(format, path.into()).await
359 }
360
361 async fn save_impl(&self, format: Txt, path: PathBuf) -> io::Result<()> {
362 let view = self.view.get().unwrap();
363 let data = view
364 .encode(format)
365 .await
366 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
367 task::wait(move || fs::write(path, &data[..])).await
368 }
369
370 pub(crate) fn inner_set_or_replace(&mut self, img: ViewImage, done: bool) {
371 match self.view.set(img) {
372 Ok(()) => {
373 if done {
374 self.done_signal.set();
375 }
376 }
377 Err(img) => {
378 let cache_key = self.cache_key;
380 *self = Self {
381 view: OnceCell::with_value(img),
382 render_ids: Arc::default(),
383 done_signal: if done { SignalOnce::new_set() } else { SignalOnce::new() },
384 cache_key,
385 };
386 }
387 }
388 }
389}
390impl zng_app::render::Img for Img {
391 fn renderer_id(&self, renderer: &ViewRenderer) -> ImageTextureId {
392 if self.is_loaded() {
393 let mut rms = self.render_ids.lock();
394 if let Some(rm) = rms.iter().find(|k| &k.renderer == renderer) {
395 return rm.image_id;
396 }
397
398 let key = match renderer.use_image(self.view.get().unwrap()) {
399 Ok(k) => {
400 if k == ImageTextureId::INVALID {
401 tracing::error!("received INVALID from `use_image`");
402 return k;
403 }
404 k
405 }
406 Err(_) => {
407 tracing::debug!("respawned `add_image`, will return INVALID");
408 return ImageTextureId::INVALID;
409 }
410 };
411
412 rms.push(RenderImage {
413 image_id: key,
414 renderer: renderer.clone(),
415 });
416 key
417 } else {
418 ImageTextureId::INVALID
419 }
420 }
421
422 fn size(&self) -> PxSize {
423 self.size()
424 }
425}
426
427struct RenderImage {
428 image_id: ImageTextureId,
429 renderer: ViewRenderer,
430}
431impl Drop for RenderImage {
432 fn drop(&mut self) {
433 let _ = self.renderer.delete_image_use(self.image_id);
435 }
436}
437impl fmt::Debug for RenderImage {
438 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
439 fmt::Debug::fmt(&self.image_id, f)
440 }
441}
442
443#[derive(Clone, Copy)]
451pub struct ImageHash([u8; 32]);
452impl ImageHash {
453 pub fn compute(data: &[u8]) -> Self {
455 let mut h = Self::hasher();
456 h.update(data);
457 h.finish()
458 }
459
460 pub fn hasher() -> ImageHasher {
462 ImageHasher::default()
463 }
464}
465impl fmt::Debug for ImageHash {
466 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
467 if f.alternate() {
468 f.debug_tuple("ImageHash").field(&self.0).finish()
469 } else {
470 use base64::*;
471 write!(f, "{}", base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(self.0))
472 }
473 }
474}
475impl fmt::Display for ImageHash {
476 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
477 write!(f, "{self:?}")
478 }
479}
480impl std::hash::Hash for ImageHash {
481 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
482 let h64 = [
483 self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], self.0[6], self.0[7],
484 ];
485 state.write_u64(u64::from_ne_bytes(h64))
486 }
487}
488impl PartialEq for ImageHash {
489 fn eq(&self, other: &Self) -> bool {
490 self.0 == other.0
491 }
492}
493impl Eq for ImageHash {}
494
495pub struct ImageHasher(sha2::Sha512_256);
497impl Default for ImageHasher {
498 fn default() -> Self {
499 use sha2::Digest;
500 Self(sha2::Sha512_256::new())
501 }
502}
503impl ImageHasher {
504 pub fn new() -> Self {
506 Self::default()
507 }
508
509 pub fn update(&mut self, data: &[u8]) {
511 use sha2::Digest;
512
513 const NUM_SAMPLES: usize = 1000;
516 const SAMPLE_CHUNK_SIZE: usize = 1024;
517
518 let total_size = data.len();
519 if total_size == 0 {
520 return;
521 }
522 if total_size < 1000 * 1000 * 4 {
523 return self.0.update(data);
524 }
525
526 let step_size = total_size.checked_div(NUM_SAMPLES).unwrap_or(total_size);
527 for n in 0..NUM_SAMPLES {
528 let start_index = n * step_size;
529 if start_index >= total_size {
530 break;
531 }
532 let end_index = (start_index + SAMPLE_CHUNK_SIZE).min(total_size);
533 let s = &data[start_index..end_index];
534 self.0.update(s);
535 }
536 }
537
538 pub fn finish(self) -> ImageHash {
540 use sha2::Digest;
541 #[allow(deprecated)]
545 ImageHash(self.0.finalize().as_slice().try_into().unwrap())
546 }
547}
548impl std::hash::Hasher for ImageHasher {
549 fn finish(&self) -> u64 {
550 tracing::warn!("Hasher::finish called for ImageHasher");
551
552 use sha2::Digest;
553 let hash = self.0.clone().finalize();
554 u64::from_le_bytes(hash[..8].try_into().unwrap())
555 }
556
557 fn write(&mut self, bytes: &[u8]) {
558 self.update(bytes);
559 }
560}
561
562type RenderFn = Arc<Box<dyn Fn(&ImageRenderArgs) -> Box<dyn ImageRenderWindowRoot> + Send + Sync>>;
564
565#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
569#[non_exhaustive]
570pub struct ImageRenderArgs {
571 pub parent: Option<WindowId>,
573}
574impl ImageRenderArgs {
575 pub fn new(parent: WindowId) -> Self {
577 Self { parent: Some(parent) }
578 }
579}
580
581#[derive(Clone)]
583#[non_exhaustive]
584pub enum ImageSource {
585 Read(PathBuf),
589 #[cfg(feature = "http")]
595 Download(crate::task::http::Uri, Option<Txt>),
596 Data(ImageHash, IpcBytes, ImageDataFormat),
604
605 Render(RenderFn, Option<ImageRenderArgs>),
611
612 Image(ImageVar),
616}
617impl ImageSource {
618 pub fn from_data(data: IpcBytes, format: ImageDataFormat) -> Self {
620 let mut hasher = ImageHasher::default();
621 hasher.update(&data[..]);
622 let hash = hasher.finish();
623 Self::Data(hash, data, format)
624 }
625
626 pub fn hash128(&self, downscale: Option<ImageDownscale>, mask: Option<ImageMaskMode>) -> Option<ImageHash> {
628 match self {
629 ImageSource::Read(p) => Some(Self::hash128_read(p, downscale, mask)),
630 #[cfg(feature = "http")]
631 ImageSource::Download(u, a) => Some(Self::hash128_download(u, a, downscale, mask)),
632 ImageSource::Data(h, _, _) => Some(Self::hash128_data(*h, downscale, mask)),
633 ImageSource::Render(rfn, args) => Some(Self::hash128_render(rfn, args, downscale, mask)),
634 ImageSource::Image(_) => None,
635 }
636 }
637
638 pub fn hash128_data(data_hash: ImageHash, downscale: Option<ImageDownscale>, mask: Option<ImageMaskMode>) -> ImageHash {
642 if downscale.is_some() || mask.is_some() {
643 use std::hash::Hash;
644 let mut h = ImageHash::hasher();
645 data_hash.0.hash(&mut h);
646 downscale.hash(&mut h);
647 mask.hash(&mut h);
648 h.finish()
649 } else {
650 data_hash
651 }
652 }
653
654 pub fn hash128_read(path: &Path, downscale: Option<ImageDownscale>, mask: Option<ImageMaskMode>) -> ImageHash {
658 use std::hash::Hash;
659 let mut h = ImageHash::hasher();
660 0u8.hash(&mut h);
661 path.hash(&mut h);
662 downscale.hash(&mut h);
663 mask.hash(&mut h);
664 h.finish()
665 }
666
667 #[cfg(feature = "http")]
671 pub fn hash128_download(
672 uri: &crate::task::http::Uri,
673 accept: &Option<Txt>,
674 downscale: Option<ImageDownscale>,
675 mask: Option<ImageMaskMode>,
676 ) -> ImageHash {
677 use std::hash::Hash;
678 let mut h = ImageHash::hasher();
679 1u8.hash(&mut h);
680 uri.hash(&mut h);
681 accept.hash(&mut h);
682 downscale.hash(&mut h);
683 mask.hash(&mut h);
684 h.finish()
685 }
686
687 pub fn hash128_render(
693 rfn: &RenderFn,
694 args: &Option<ImageRenderArgs>,
695 downscale: Option<ImageDownscale>,
696 mask: Option<ImageMaskMode>,
697 ) -> ImageHash {
698 use std::hash::Hash;
699 let mut h = ImageHash::hasher();
700 2u8.hash(&mut h);
701 (Arc::as_ptr(rfn) as usize).hash(&mut h);
702 args.hash(&mut h);
703 downscale.hash(&mut h);
704 mask.hash(&mut h);
705 h.finish()
706 }
707}
708
709impl ImageSource {
710 pub fn flood(size: impl Into<PxSize>, color: impl Into<Rgba>, density: Option<PxDensity2d>) -> Self {
712 Self::flood_impl(size.into(), color.into(), density)
713 }
714 fn flood_impl(size: PxSize, color: Rgba, density: Option<PxDensity2d>) -> Self {
715 let pixels = size.width.0 as usize * size.height.0 as usize;
716 let bgra = color.to_bgra_bytes();
717 let mut b = IpcBytes::new_mut_blocking(pixels * 4).expect("cannot allocate IpcBytes");
718 for b in b.chunks_exact_mut(4) {
719 b.copy_from_slice(&bgra);
720 }
721 Self::from_data(
722 b.finish_blocking().expect("cannot allocate IpcBytes"),
723 ImageDataFormat::Bgra8 { size, density },
724 )
725 }
726
727 pub fn linear_vertical(
729 size: impl Into<PxSize>,
730 stops: impl Into<GradientStops>,
731 density: Option<PxDensity2d>,
732 mask: Option<ImageMaskMode>,
733 ) -> Self {
734 Self::linear_vertical_impl(size.into(), stops.into(), density, mask)
735 }
736 fn linear_vertical_impl(size: PxSize, stops: GradientStops, density: Option<PxDensity2d>, mask: Option<ImageMaskMode>) -> Self {
737 assert!(size.width > Px(0));
738 assert!(size.height > Px(0));
739
740 let mut line = PxLine::new(PxPoint::splat(Px(0)), PxPoint::new(Px(0), size.height));
741 let mut render_stops = vec![];
742
743 LAYOUT.with_root_context(LayoutPassId::new(), LayoutMetrics::new(1.fct(), size, Px(14)), || {
744 stops.layout_linear(LayoutAxis::Y, ExtendMode::Clamp, &mut line, &mut render_stops);
745 });
746 let line_a = line.start.y.0 as f32;
747 let line_b = line.end.y.0 as f32;
748
749 let mut bgra = Vec::with_capacity(size.height.0 as usize);
750 let mut render_stops = render_stops.into_iter();
751 let mut stop_a = render_stops.next().unwrap();
752 let mut stop_b = render_stops.next().unwrap();
753 'outer: for y in 0..size.height.0 {
754 let yf = y as f32;
755 let yf = (yf - line_a) / (line_b - line_a);
756 if yf < stop_a.offset {
757 bgra.push(stop_a.color.to_bgra_bytes());
759 continue;
760 }
761 while yf > stop_b.offset {
762 if let Some(next_b) = render_stops.next() {
763 stop_a = stop_b;
765 stop_b = next_b;
766 } else {
767 for _ in y..size.height.0 {
769 bgra.push(stop_b.color.to_bgra_bytes());
770 }
771 break 'outer;
772 }
773 }
774
775 let yf = (yf - stop_a.offset) / (stop_b.offset - stop_a.offset);
777 let sample = stop_a.color.lerp(&stop_b.color, yf.fct());
778 bgra.push(sample.to_bgra_bytes());
779 }
780
781 match mask {
782 Some(m) => {
783 let len = size.width.0 as usize * size.height.0 as usize;
784 let mut data = Vec::with_capacity(len);
785
786 for y in 0..size.height.0 {
787 let c = bgra[y as usize];
788 let c = match m {
789 ImageMaskMode::A => c[3],
790 ImageMaskMode::B => c[0],
791 ImageMaskMode::G => c[1],
792 ImageMaskMode::R => c[2],
793 ImageMaskMode::Luminance => {
794 let hsla = Hsla::from(Rgba::new(c[2], c[1], c[0], c[3]));
795 (hsla.lightness * 255.0).round().clamp(0.0, 255.0) as u8
796 }
797 _ => unreachable!(),
798 };
799 for _x in 0..size.width.0 {
800 data.push(c);
801 }
802 }
803
804 Self::from_data(
805 IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"),
806 ImageDataFormat::A8 { size },
807 )
808 }
809 None => {
810 let len = size.width.0 as usize * size.height.0 as usize * 4;
811 let mut data = Vec::with_capacity(len);
812
813 for y in 0..size.height.0 {
814 let c = bgra[y as usize];
815 for _x in 0..size.width.0 {
816 data.extend_from_slice(&c);
817 }
818 }
819
820 Self::from_data(
821 IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"),
822 ImageDataFormat::Bgra8 { size, density },
823 )
824 }
825 }
826 }
827
828 pub fn linear_horizontal(
830 size: impl Into<PxSize>,
831 stops: impl Into<GradientStops>,
832 density: Option<PxDensity2d>,
833 mask: Option<ImageMaskMode>,
834 ) -> Self {
835 Self::linear_horizontal_impl(size.into(), stops.into(), density, mask)
836 }
837 fn linear_horizontal_impl(size: PxSize, stops: GradientStops, density: Option<PxDensity2d>, mask: Option<ImageMaskMode>) -> Self {
838 assert!(size.width > Px(0));
839 assert!(size.height > Px(0));
840
841 let mut line = PxLine::new(PxPoint::splat(Px(0)), PxPoint::new(size.width, Px(0)));
842 let mut render_stops = vec![];
843 LAYOUT.with_root_context(LayoutPassId::new(), LayoutMetrics::new(1.fct(), size, Px(14)), || {
844 stops.layout_linear(LayoutAxis::Y, ExtendMode::Clamp, &mut line, &mut render_stops);
845 });
846 let line_a = line.start.x.0 as f32;
847 let line_b = line.end.x.0 as f32;
848
849 let mut bgra = Vec::with_capacity(size.width.0 as usize);
850 let mut render_stops = render_stops.into_iter();
851 let mut stop_a = render_stops.next().unwrap();
852 let mut stop_b = render_stops.next().unwrap();
853 'outer: for x in 0..size.width.0 {
854 let xf = x as f32;
855 let xf = (xf - line_a) / (line_b - line_a);
856 if xf < stop_a.offset {
857 bgra.push(stop_a.color.to_bgra_bytes());
859 continue;
860 }
861 while xf > stop_b.offset {
862 if let Some(next_b) = render_stops.next() {
863 stop_a = stop_b;
865 stop_b = next_b;
866 } else {
867 for _ in x..size.width.0 {
869 bgra.push(stop_b.color.to_bgra_bytes());
870 }
871 break 'outer;
872 }
873 }
874
875 let xf = (xf - stop_a.offset) / (stop_b.offset - stop_a.offset);
877 let sample = stop_a.color.lerp(&stop_b.color, xf.fct());
878 bgra.push(sample.to_bgra_bytes());
879 }
880
881 match mask {
882 Some(m) => {
883 let len = size.width.0 as usize * size.height.0 as usize;
884 let mut data = Vec::with_capacity(len);
885
886 for _y in 0..size.height.0 {
887 for c in &bgra {
888 let c = match m {
889 ImageMaskMode::A => c[3],
890 ImageMaskMode::B => c[0],
891 ImageMaskMode::G => c[1],
892 ImageMaskMode::R => c[2],
893 ImageMaskMode::Luminance => {
894 let hsla = Hsla::from(Rgba::new(c[2], c[1], c[0], c[3]));
895 (hsla.lightness * 255.0).round().clamp(0.0, 255.0) as u8
896 }
897 _ => unreachable!(),
898 };
899 data.push(c);
900 }
901 }
902
903 Self::from_data(
904 IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"),
905 ImageDataFormat::A8 { size },
906 )
907 }
908 None => {
909 let len = size.width.0 as usize * size.height.0 as usize * 4;
910 let mut data = Vec::with_capacity(len);
911
912 for _y in 0..size.height.0 {
913 for c in &bgra {
914 data.extend_from_slice(c);
915 }
916 }
917
918 Self::from_data(
919 IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"),
920 ImageDataFormat::Bgra8 { size, density },
921 )
922 }
923 }
924 }
925}
926
927impl PartialEq for ImageSource {
928 fn eq(&self, other: &Self) -> bool {
929 match (self, other) {
930 (Self::Read(l), Self::Read(r)) => l == r,
931 #[cfg(feature = "http")]
932 (Self::Download(lu, la), Self::Download(ru, ra)) => lu == ru && la == ra,
933 (Self::Render(lf, la), Self::Render(rf, ra)) => Arc::ptr_eq(lf, rf) && la == ra,
934 (Self::Image(l), Self::Image(r)) => l.var_eq(r),
935 (l, r) => {
936 let l_hash = match l {
937 ImageSource::Data(h, _, _) => h,
938 _ => return false,
939 };
940 let r_hash = match r {
941 ImageSource::Data(h, _, _) => h,
942 _ => return false,
943 };
944
945 l_hash == r_hash
946 }
947 }
948 }
949}
950impl fmt::Debug for ImageSource {
951 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
952 if f.alternate() {
953 write!(f, "ImageSource::")?;
954 }
955 match self {
956 ImageSource::Read(p) => f.debug_tuple("Read").field(p).finish(),
957 #[cfg(feature = "http")]
958 ImageSource::Download(u, a) => f.debug_tuple("Download").field(u).field(a).finish(),
959 ImageSource::Data(key, bytes, fmt) => f.debug_tuple("Data").field(key).field(bytes).field(fmt).finish(),
960
961 ImageSource::Render(_, _) => write!(f, "Render(_, _)"),
962 ImageSource::Image(_) => write!(f, "Image(_)"),
963 }
964 }
965}
966
967#[cfg(feature = "http")]
968impl_from_and_into_var! {
969 fn from(uri: crate::task::http::Uri) -> ImageSource {
970 ImageSource::Download(uri, None)
971 }
972 fn from((uri, accept): (crate::task::http::Uri, &'static str)) -> ImageSource {
974 ImageSource::Download(uri, Some(accept.into()))
975 }
976
977 fn from(s: &str) -> ImageSource {
983 use crate::task::http::*;
984 if let Ok(uri) = Uri::try_from(s)
985 && let Some(scheme) = uri.scheme()
986 {
987 if scheme == &uri::Scheme::HTTPS || scheme == &uri::Scheme::HTTP {
988 return ImageSource::Download(uri, None);
989 } else if scheme.as_str() == "file" {
990 return PathBuf::from(uri.path()).into();
991 }
992 }
993 PathBuf::from(s).into()
994 }
995}
996
997#[cfg(not(feature = "http"))]
998impl_from_and_into_var! {
999 fn from(s: &str) -> ImageSource {
1003 PathBuf::from(s).into()
1004 }
1005}
1006
1007impl_from_and_into_var! {
1008 fn from(image: ImageVar) -> ImageSource {
1009 ImageSource::Image(image)
1010 }
1011 fn from(path: PathBuf) -> ImageSource {
1012 ImageSource::Read(path)
1013 }
1014 fn from(path: &Path) -> ImageSource {
1015 path.to_owned().into()
1016 }
1017
1018 fn from(s: String) -> ImageSource {
1020 s.as_str().into()
1021 }
1022 fn from(s: Txt) -> ImageSource {
1024 s.as_str().into()
1025 }
1026 fn from(data: &[u8]) -> ImageSource {
1030 ImageSource::Data(
1031 ImageHash::compute(data),
1032 IpcBytes::from_slice_blocking(data).expect("cannot allocate IpcBytes"),
1033 ImageDataFormat::Unknown,
1034 )
1035 }
1036 fn from<const N: usize>(data: &[u8; N]) -> ImageSource {
1040 (&data[..]).into()
1041 }
1042 fn from(data: IpcBytes) -> ImageSource {
1046 ImageSource::Data(ImageHash::compute(&data[..]), data, ImageDataFormat::Unknown)
1047 }
1048 fn from(data: Vec<u8>) -> ImageSource {
1052 IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes").into()
1053 }
1054 fn from<F: Into<ImageDataFormat>>((data, format): (&[u8], F)) -> ImageSource {
1056 ImageSource::Data(
1057 ImageHash::compute(data),
1058 IpcBytes::from_slice_blocking(data).expect("cannot allocate IpcBytes"),
1059 format.into(),
1060 )
1061 }
1062 fn from<F: Into<ImageDataFormat>, const N: usize>((data, format): (&[u8; N], F)) -> ImageSource {
1064 (&data[..], format).into()
1065 }
1066 fn from<F: Into<ImageDataFormat>>((data, format): (Vec<u8>, F)) -> ImageSource {
1068 (IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"), format).into()
1069 }
1070 fn from<F: Into<ImageDataFormat>>((data, format): (IpcBytes, F)) -> ImageSource {
1072 ImageSource::Data(ImageHash::compute(&data[..]), data, format.into())
1073 }
1074}
1075
1076#[derive(Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1080pub enum ImageCacheMode {
1081 Ignore,
1083 Cache,
1085 Retry,
1087 Reload,
1091}
1092impl fmt::Debug for ImageCacheMode {
1093 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1094 if f.alternate() {
1095 write!(f, "CacheMode::")?;
1096 }
1097 match self {
1098 Self::Ignore => write!(f, "Ignore"),
1099 Self::Cache => write!(f, "Cache"),
1100 Self::Retry => write!(f, "Retry"),
1101 Self::Reload => write!(f, "Reload"),
1102 }
1103 }
1104}
1105
1106#[derive(Clone)]
1108pub enum ImageSourceFilter<U> {
1109 BlockAll,
1111 AllowAll,
1113 Custom(Arc<dyn Fn(&U) -> bool + Send + Sync>),
1115}
1116impl<U> ImageSourceFilter<U> {
1117 pub fn custom(allow: impl Fn(&U) -> bool + Send + Sync + 'static) -> Self {
1121 Self::Custom(Arc::new(allow))
1122 }
1123
1124 pub fn and(self, other: Self) -> Self
1133 where
1134 U: 'static,
1135 {
1136 use ImageSourceFilter::*;
1137 match (self, other) {
1138 (BlockAll, _) | (_, BlockAll) => BlockAll,
1139 (AllowAll, _) | (_, AllowAll) => AllowAll,
1140 (Custom(c0), Custom(c1)) => Custom(Arc::new(move |u| c0(u) && c1(u))),
1141 }
1142 }
1143
1144 pub fn or(self, other: Self) -> Self
1153 where
1154 U: 'static,
1155 {
1156 use ImageSourceFilter::*;
1157 match (self, other) {
1158 (AllowAll, _) | (_, AllowAll) => AllowAll,
1159 (BlockAll, _) | (_, BlockAll) => BlockAll,
1160 (Custom(c0), Custom(c1)) => Custom(Arc::new(move |u| c0(u) || c1(u))),
1161 }
1162 }
1163
1164 pub fn allows(&self, item: &U) -> bool {
1166 match self {
1167 ImageSourceFilter::BlockAll => false,
1168 ImageSourceFilter::AllowAll => true,
1169 ImageSourceFilter::Custom(f) => f(item),
1170 }
1171 }
1172}
1173impl<U> fmt::Debug for ImageSourceFilter<U> {
1174 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1175 match self {
1176 Self::BlockAll => write!(f, "BlockAll"),
1177 Self::AllowAll => write!(f, "AllowAll"),
1178 Self::Custom(_) => write!(f, "Custom(_)"),
1179 }
1180 }
1181}
1182impl<U: 'static> ops::BitAnd for ImageSourceFilter<U> {
1183 type Output = Self;
1184
1185 fn bitand(self, rhs: Self) -> Self::Output {
1186 self.and(rhs)
1187 }
1188}
1189impl<U: 'static> ops::BitOr for ImageSourceFilter<U> {
1190 type Output = Self;
1191
1192 fn bitor(self, rhs: Self) -> Self::Output {
1193 self.or(rhs)
1194 }
1195}
1196impl<U: 'static> ops::BitAndAssign for ImageSourceFilter<U> {
1197 fn bitand_assign(&mut self, rhs: Self) {
1198 *self = mem::replace(self, Self::BlockAll).and(rhs);
1199 }
1200}
1201impl<U: 'static> ops::BitOrAssign for ImageSourceFilter<U> {
1202 fn bitor_assign(&mut self, rhs: Self) {
1203 *self = mem::replace(self, Self::BlockAll).or(rhs);
1204 }
1205}
1206impl<U> PartialEq for ImageSourceFilter<U> {
1207 fn eq(&self, other: &Self) -> bool {
1208 match (self, other) {
1209 (Self::Custom(l0), Self::Custom(r0)) => Arc::ptr_eq(l0, r0),
1210 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
1211 }
1212 }
1213}
1214
1215pub type PathFilter = ImageSourceFilter<PathBuf>;
1225impl PathFilter {
1226 pub fn allow_dir(dir: impl AsRef<Path>) -> Self {
1228 let dir = crate::absolute_path(dir.as_ref(), || env::current_dir().expect("could not access current dir"), true);
1229 PathFilter::custom(move |r| r.starts_with(&dir))
1230 }
1231
1232 pub fn allow_ext(ext: impl Into<std::ffi::OsString>) -> Self {
1234 let ext = ext.into();
1235 PathFilter::custom(move |r| r.extension().map(|e| e == ext).unwrap_or(false))
1236 }
1237
1238 pub fn allow_current_dir() -> Self {
1246 PathFilter::custom(|r| env::current_dir().map(|d| r.starts_with(d)).unwrap_or(false))
1247 }
1248
1249 pub fn allow_exe_dir() -> Self {
1251 if let Ok(mut p) = env::current_exe().and_then(dunce::canonicalize)
1252 && p.pop()
1253 {
1254 return Self::allow_dir(p);
1255 }
1256
1257 Self::custom(|_| false)
1259 }
1260
1261 pub fn allow_res() -> Self {
1265 Self::allow_dir(zng_env::res(""))
1266 }
1267}
1268
1269#[cfg(feature = "http")]
1273pub type UriFilter = ImageSourceFilter<crate::task::http::Uri>;
1274#[cfg(feature = "http")]
1275impl UriFilter {
1276 pub fn allow_host(host: impl Into<Txt>) -> Self {
1278 let host = host.into();
1279 UriFilter::custom(move |u| u.authority().map(|a| a.host() == host).unwrap_or(false))
1280 }
1281}
1282
1283impl<F: Fn(&PathBuf) -> bool + Send + Sync + 'static> From<F> for PathFilter {
1284 fn from(custom: F) -> Self {
1285 PathFilter::custom(custom)
1286 }
1287}
1288
1289#[cfg(feature = "http")]
1290impl<F: Fn(&task::http::Uri) -> bool + Send + Sync + 'static> From<F> for UriFilter {
1291 fn from(custom: F) -> Self {
1292 UriFilter::custom(custom)
1293 }
1294}
1295
1296#[derive(Clone, Debug, PartialEq)]
1298#[non_exhaustive]
1299pub struct ImageLimits {
1300 pub max_encoded_len: ByteLength,
1308 pub max_decoded_len: ByteLength,
1312
1313 pub allow_path: PathFilter,
1317
1318 #[cfg(feature = "http")]
1320 pub allow_uri: UriFilter,
1321}
1322impl ImageLimits {
1323 pub fn none() -> Self {
1325 ImageLimits {
1326 max_encoded_len: ByteLength::MAX,
1327 max_decoded_len: ByteLength::MAX,
1328 allow_path: PathFilter::AllowAll,
1329 #[cfg(feature = "http")]
1330 allow_uri: UriFilter::AllowAll,
1331 }
1332 }
1333
1334 pub fn with_max_encoded_len(mut self, max_encoded_size: impl Into<ByteLength>) -> Self {
1338 self.max_encoded_len = max_encoded_size.into();
1339 self
1340 }
1341
1342 pub fn with_max_decoded_len(mut self, max_decoded_size: impl Into<ByteLength>) -> Self {
1346 self.max_decoded_len = max_decoded_size.into();
1347 self
1348 }
1349
1350 pub fn with_allow_path(mut self, allow_path: impl Into<PathFilter>) -> Self {
1354 self.allow_path = allow_path.into();
1355 self
1356 }
1357
1358 #[cfg(feature = "http")]
1362 pub fn with_allow_uri(mut self, allow_url: impl Into<UriFilter>) -> Self {
1363 self.allow_uri = allow_url.into();
1364 self
1365 }
1366}
1367impl Default for ImageLimits {
1368 fn default() -> Self {
1372 Self {
1373 max_encoded_len: 100.megabytes(),
1374 max_decoded_len: 4096.megabytes(),
1375 allow_path: PathFilter::allow_res(),
1376 #[cfg(feature = "http")]
1377 allow_uri: UriFilter::BlockAll,
1378 }
1379 }
1380}
1381impl_from_and_into_var! {
1382 fn from(some: ImageLimits) -> Option<ImageLimits>;
1383}