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::Rgba;
14use zng_layout::{
15 context::LayoutMetrics,
16 unit::{ByteLength, ByteUnits, PxRect, PxSize},
17};
18use zng_task::{self as task, SignalOnce};
19use zng_txt::Txt;
20use zng_var::{AnyVar, ReadOnlyArcVar, impl_from_and_into_var};
21use zng_view_api::{ViewProcessOffline, image::ImageTextureId};
22
23use crate::render::ImageRenderWindowRoot;
24
25pub use zng_view_api::image::{ImageDataFormat, ImageDownscale, ImageMaskMode, ImagePpi};
26
27pub trait ImageCacheProxy: Send + Sync {
36 fn get(
38 &mut self,
39 key: &ImageHash,
40 source: &ImageSource,
41 mode: ImageCacheMode,
42 downscale: Option<ImageDownscale>,
43 mask: Option<ImageMaskMode>,
44 ) -> ProxyGetResult {
45 let r = match source {
46 ImageSource::Static(_, data, image_format) => self.data(key, data, image_format, mode, downscale, mask, false),
47 ImageSource::Data(_, data, image_format) => self.data(key, data, image_format, mode, downscale, mask, false),
48 _ => return ProxyGetResult::None,
49 };
50 match r {
51 Some(img) => ProxyGetResult::Image(img),
52 None => ProxyGetResult::None,
53 }
54 }
55
56 #[allow(clippy::too_many_arguments)]
70 fn data(
71 &mut self,
72 key: &ImageHash,
73 data: &[u8],
74 image_format: &ImageDataFormat,
75 mode: ImageCacheMode,
76 downscale: Option<ImageDownscale>,
77 mask: Option<ImageMaskMode>,
78 is_loaded: bool,
79 ) -> Option<ImageVar> {
80 let _ = (key, data, image_format, mode, downscale, mask, is_loaded);
81 None
82 }
83
84 fn remove(&mut self, key: &ImageHash, purge: bool) -> ProxyRemoveResult {
86 let _ = (key, purge);
87 ProxyRemoveResult::None
88 }
89
90 fn clear(&mut self, purge: bool) {
92 let _ = purge;
93 }
94
95 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 = ReadOnlyArcVar<Img>;
146
147#[derive(Debug, Clone)]
151pub struct Img {
152 pub(super) view: OnceCell<ViewImage>,
153 render_ids: Arc<Mutex<Vec<RenderImage>>>,
154 pub(super) done_signal: SignalOnce,
155 pub(super) cache_key: Option<ImageHash>,
156}
157impl PartialEq for Img {
158 fn eq(&self, other: &Self) -> bool {
159 self.view == other.view
160 }
161}
162impl Img {
163 pub(super) fn new_none(cache_key: Option<ImageHash>) -> Self {
164 Img {
165 view: OnceCell::new(),
166 render_ids: Arc::default(),
167 done_signal: SignalOnce::new(),
168 cache_key,
169 }
170 }
171
172 pub fn new(view: ViewImage) -> Self {
174 let sig = view.awaiter();
175 let v = OnceCell::new();
176 let _ = v.set(view);
177 Img {
178 view: v,
179 render_ids: Arc::default(),
180 done_signal: sig,
181 cache_key: None,
182 }
183 }
184
185 pub fn dummy(error: Option<Txt>) -> Self {
187 Self::new(ViewImage::dummy(error))
188 }
189
190 pub fn is_loading(&self) -> bool {
192 match self.view.get() {
193 Some(v) => !v.is_loaded() && !v.is_error(),
194 None => true,
195 }
196 }
197
198 pub fn is_loaded(&self) -> bool {
200 match self.view.get() {
201 Some(v) => v.is_loaded(),
202 None => false,
203 }
204 }
205
206 pub fn is_error(&self) -> bool {
208 match self.view.get() {
209 Some(v) => v.is_error(),
210 None => false,
211 }
212 }
213
214 pub fn error(&self) -> Option<Txt> {
216 match self.view.get() {
217 Some(v) => v.error(),
218 None => None,
219 }
220 }
221
222 pub fn wait_done(&self) -> impl Future<Output = ()> + Send + Sync + 'static {
224 self.done_signal.clone()
225 }
226
227 pub fn size(&self) -> PxSize {
229 self.view.get().map(|v| v.size()).unwrap_or_else(PxSize::zero)
230 }
231
232 pub fn ppi(&self) -> Option<ImagePpi> {
235 self.view.get().and_then(|v| v.ppi())
236 }
237
238 pub fn is_opaque(&self) -> bool {
240 self.view.get().map(|v| v.is_opaque()).unwrap_or(true)
241 }
242
243 pub fn is_mask(&self) -> bool {
245 self.view.get().map(|v| v.is_mask()).unwrap_or(false)
246 }
247
248 pub fn view(&self) -> Option<&ViewImage> {
250 self.view.get().filter(|&v| v.is_loaded())
251 }
252
253 pub fn layout_size(&self, ctx: &LayoutMetrics) -> PxSize {
261 self.calc_size(ctx, ImagePpi::splat(ctx.screen_ppi().0), false)
262 }
263
264 pub fn calc_size(&self, ctx: &LayoutMetrics, fallback_ppi: ImagePpi, ignore_image_ppi: bool) -> PxSize {
274 let dpi = if ignore_image_ppi {
275 fallback_ppi
276 } else {
277 self.ppi().unwrap_or(fallback_ppi)
278 };
279
280 let s_ppi = ctx.screen_ppi();
281 let mut size = self.size();
282
283 let fct = ctx.scale_factor().0;
284 size.width *= (s_ppi.0 / dpi.x) * fct;
285 size.height *= (s_ppi.0 / dpi.y) * fct;
286
287 size
288 }
289
290 pub fn pixels(&self) -> Option<zng_view_api::ipc::IpcBytes> {
294 self.view.get().and_then(|v| v.pixels())
295 }
296
297 pub fn copy_pixels(&self, rect: PxRect) -> Option<(PxRect, Vec<u8>)> {
305 self.pixels().map(|pixels| {
306 let area = PxRect::from_size(self.size()).intersection(&rect).unwrap_or_default();
307 if area.size.width.0 == 0 || area.size.height.0 == 0 {
308 (area, vec![])
309 } else {
310 let x = area.origin.x.0 as usize;
311 let y = area.origin.y.0 as usize;
312 let width = area.size.width.0 as usize;
313 let height = area.size.height.0 as usize;
314 let pixel = if self.is_mask() { 1 } else { 4 };
315 let mut bytes = Vec::with_capacity(width * height * pixel);
316 let row_stride = self.size().width.0 as usize * pixel;
317 for l in y..y + height {
318 let line_start = l * row_stride + x * pixel;
319 let line_end = line_start + width * pixel;
320 let line = &pixels[line_start..line_end];
321 bytes.extend_from_slice(line);
322 }
323 (area, bytes)
324 }
325 })
326 }
327
328 pub async fn encode(&self, format: Txt) -> std::result::Result<zng_view_api::ipc::IpcBytes, EncodeError> {
330 self.done_signal.clone().await;
331 if let Some(e) = self.error() {
332 Err(EncodeError::Encode(e))
333 } else {
334 self.view.get().unwrap().encode(format).await
335 }
336 }
337
338 pub async fn save(&self, path: impl Into<PathBuf>) -> io::Result<()> {
342 let path = path.into();
343 if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
344 self.save_impl(Txt::from_str(ext), path).await
345 } else {
346 Err(io::Error::new(
347 io::ErrorKind::InvalidInput,
348 "could not determinate image format from path extension",
349 ))
350 }
351 }
352
353 pub async fn save_with_format(&self, format: Txt, path: impl Into<PathBuf>) -> io::Result<()> {
357 self.save_impl(format, path.into()).await
358 }
359
360 async fn save_impl(&self, format: Txt, path: PathBuf) -> io::Result<()> {
361 let view = self.view.get().unwrap();
362 let data = view
363 .encode(format)
364 .await
365 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
366 task::wait(move || fs::write(path, &data[..])).await
367 }
368}
369impl zng_app::render::Img for Img {
370 fn renderer_id(&self, renderer: &ViewRenderer) -> ImageTextureId {
371 if self.is_loaded() {
372 let mut rms = self.render_ids.lock();
373 if let Some(rm) = rms.iter().find(|k| &k.renderer == renderer) {
374 return rm.image_id;
375 }
376
377 let key = match renderer.use_image(self.view.get().unwrap()) {
378 Ok(k) => {
379 if k == ImageTextureId::INVALID {
380 tracing::error!("received INVALID from `use_image`");
381 return k;
382 }
383 k
384 }
385 Err(ViewProcessOffline) => {
386 tracing::debug!("respawned `add_image`, will return INVALID");
387 return ImageTextureId::INVALID;
388 }
389 };
390
391 rms.push(RenderImage {
392 image_id: key,
393 renderer: renderer.clone(),
394 });
395 key
396 } else {
397 ImageTextureId::INVALID
398 }
399 }
400
401 fn size(&self) -> PxSize {
402 self.size()
403 }
404}
405
406struct RenderImage {
407 image_id: ImageTextureId,
408 renderer: ViewRenderer,
409}
410impl Drop for RenderImage {
411 fn drop(&mut self) {
412 let _ = self.renderer.delete_image_use(self.image_id);
414 }
415}
416impl fmt::Debug for RenderImage {
417 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
418 fmt::Debug::fmt(&self.image_id, f)
419 }
420}
421
422#[derive(Clone, Copy)]
430pub struct ImageHash([u8; 32]);
431impl ImageHash {
432 pub fn compute(data: &[u8]) -> Self {
434 let mut h = Self::hasher();
435 h.update(data);
436 h.finish()
437 }
438
439 pub fn hasher() -> ImageHasher {
441 ImageHasher::default()
442 }
443}
444impl fmt::Debug for ImageHash {
445 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
446 if f.alternate() {
447 f.debug_tuple("ImageHash").field(&self.0).finish()
448 } else {
449 use base64::*;
450 write!(f, "{}", base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(self.0))
451 }
452 }
453}
454impl fmt::Display for ImageHash {
455 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
456 write!(f, "{self:?}")
457 }
458}
459impl std::hash::Hash for ImageHash {
460 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
461 let h64 = [
462 self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], self.0[6], self.0[7],
463 ];
464 state.write_u64(u64::from_ne_bytes(h64))
465 }
466}
467impl PartialEq for ImageHash {
468 fn eq(&self, other: &Self) -> bool {
469 self.0 == other.0
470 }
471}
472impl Eq for ImageHash {}
473
474pub struct ImageHasher(sha2::Sha512_256);
476impl Default for ImageHasher {
477 fn default() -> Self {
478 use sha2::Digest;
479 Self(sha2::Sha512_256::new())
480 }
481}
482impl ImageHasher {
483 pub fn new() -> Self {
485 Self::default()
486 }
487
488 pub fn update(&mut self, data: impl AsRef<[u8]>) {
490 use sha2::Digest;
491 self.0.update(data);
492 }
493
494 pub fn finish(self) -> ImageHash {
496 use sha2::Digest;
497 ImageHash(self.0.finalize().as_slice().try_into().unwrap())
498 }
499}
500impl std::hash::Hasher for ImageHasher {
501 fn finish(&self) -> u64 {
502 tracing::warn!("Hasher::finish called for ImageHasher");
503
504 use sha2::Digest;
505 let hash = self.0.clone().finalize();
506 u64::from_le_bytes(hash[..8].try_into().unwrap())
507 }
508
509 fn write(&mut self, bytes: &[u8]) {
510 self.update(bytes);
511 }
512}
513
514type RenderFn = Arc<Box<dyn Fn(&ImageRenderArgs) -> Box<dyn ImageRenderWindowRoot> + Send + Sync>>;
516
517#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
521pub struct ImageRenderArgs {
522 pub parent: Option<WindowId>,
524}
525
526#[derive(Clone)]
528pub enum ImageSource {
529 Read(PathBuf),
533 #[cfg(feature = "http")]
539 Download(crate::task::http::Uri, Option<Txt>),
540 Static(ImageHash, &'static [u8], ImageDataFormat),
544 Data(ImageHash, Arc<Vec<u8>>, ImageDataFormat),
552
553 Render(RenderFn, Option<ImageRenderArgs>),
559
560 Image(ImageVar),
564}
565impl ImageSource {
566 pub fn flood(size: impl Into<PxSize>, color: impl Into<Rgba>, ppi: Option<ImagePpi>) -> Self {
568 let size = size.into();
569 let color = color.into();
570 let len = size.width.0 as usize * size.height.0 as usize * 4;
571 let mut data = vec![0; len];
572 for bgra in data.chunks_exact_mut(4) {
573 let rgba = color.to_bytes();
574 bgra[0] = rgba[2];
575 bgra[1] = rgba[1];
576 bgra[2] = rgba[0];
577 bgra[3] = rgba[3];
578 }
579 Self::from_data(Arc::new(data), ImageDataFormat::Bgra8 { size, ppi })
580 }
581
582 pub fn from_data(data: Arc<Vec<u8>>, format: ImageDataFormat) -> Self {
584 let mut hasher = ImageHasher::default();
585 hasher.update(&data[..]);
586 let hash = hasher.finish();
587 Self::Data(hash, data, format)
588 }
589
590 pub fn from_static(data: &'static [u8], format: ImageDataFormat) -> Self {
592 let mut hasher = ImageHasher::default();
593 hasher.update(data);
594 let hash = hasher.finish();
595 Self::Static(hash, data, format)
596 }
597
598 pub fn hash128(&self, downscale: Option<ImageDownscale>, mask: Option<ImageMaskMode>) -> Option<ImageHash> {
600 match self {
601 ImageSource::Read(p) => Some(Self::hash128_read(p, downscale, mask)),
602 #[cfg(feature = "http")]
603 ImageSource::Download(u, a) => Some(Self::hash128_download(u, a, downscale, mask)),
604 ImageSource::Static(h, _, _) => Some(Self::hash128_data(*h, downscale, mask)),
605 ImageSource::Data(h, _, _) => Some(Self::hash128_data(*h, downscale, mask)),
606 ImageSource::Render(rfn, args) => Some(Self::hash128_render(rfn, args, downscale, mask)),
607 ImageSource::Image(_) => None,
608 }
609 }
610
611 pub fn hash128_data(data_hash: ImageHash, downscale: Option<ImageDownscale>, mask: Option<ImageMaskMode>) -> ImageHash {
616 if downscale.is_some() || mask.is_some() {
617 use std::hash::Hash;
618 let mut h = ImageHash::hasher();
619 data_hash.0.hash(&mut h);
620 downscale.hash(&mut h);
621 mask.hash(&mut h);
622 h.finish()
623 } else {
624 data_hash
625 }
626 }
627
628 pub fn hash128_read(path: &Path, downscale: Option<ImageDownscale>, mask: Option<ImageMaskMode>) -> ImageHash {
632 use std::hash::Hash;
633 let mut h = ImageHash::hasher();
634 0u8.hash(&mut h);
635 path.hash(&mut h);
636 downscale.hash(&mut h);
637 mask.hash(&mut h);
638 h.finish()
639 }
640
641 #[cfg(feature = "http")]
645 pub fn hash128_download(
646 uri: &crate::task::http::Uri,
647 accept: &Option<Txt>,
648 downscale: Option<ImageDownscale>,
649 mask: Option<ImageMaskMode>,
650 ) -> ImageHash {
651 use std::hash::Hash;
652 let mut h = ImageHash::hasher();
653 1u8.hash(&mut h);
654 uri.hash(&mut h);
655 accept.hash(&mut h);
656 downscale.hash(&mut h);
657 mask.hash(&mut h);
658 h.finish()
659 }
660
661 pub fn hash128_render(
667 rfn: &RenderFn,
668 args: &Option<ImageRenderArgs>,
669 downscale: Option<ImageDownscale>,
670 mask: Option<ImageMaskMode>,
671 ) -> ImageHash {
672 use std::hash::Hash;
673 let mut h = ImageHash::hasher();
674 2u8.hash(&mut h);
675 (Arc::as_ptr(rfn) as usize).hash(&mut h);
676 args.hash(&mut h);
677 downscale.hash(&mut h);
678 mask.hash(&mut h);
679 h.finish()
680 }
681}
682impl PartialEq for ImageSource {
683 fn eq(&self, other: &Self) -> bool {
684 match (self, other) {
685 (Self::Read(l), Self::Read(r)) => l == r,
686 #[cfg(feature = "http")]
687 (Self::Download(lu, la), Self::Download(ru, ra)) => lu == ru && la == ra,
688 (Self::Render(lf, la), Self::Render(rf, ra)) => Arc::ptr_eq(lf, rf) && la == ra,
689 (Self::Image(l), Self::Image(r)) => l.var_ptr() == r.var_ptr(),
690 (l, r) => {
691 let l_hash = match l {
692 ImageSource::Static(h, _, _) => h,
693 ImageSource::Data(h, _, _) => h,
694 _ => return false,
695 };
696 let r_hash = match r {
697 ImageSource::Static(h, _, _) => h,
698 ImageSource::Data(h, _, _) => h,
699 _ => return false,
700 };
701
702 l_hash == r_hash
703 }
704 }
705 }
706}
707impl fmt::Debug for ImageSource {
708 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
709 if f.alternate() {
710 write!(f, "ImageSource::")?;
711 }
712 match self {
713 ImageSource::Read(p) => f.debug_tuple("Read").field(p).finish(),
714 #[cfg(feature = "http")]
715 ImageSource::Download(u, a) => f.debug_tuple("Download").field(u).field(a).finish(),
716 ImageSource::Static(key, _, fmt) => f.debug_tuple("Static").field(key).field(fmt).finish(),
717 ImageSource::Data(key, _, fmt) => f.debug_tuple("Data").field(key).field(fmt).finish(),
718 ImageSource::Render(_, _) => write!(f, "Render(_, _)"),
719 ImageSource::Image(_) => write!(f, "Image(_)"),
720 }
721 }
722}
723
724#[cfg(feature = "http")]
725impl_from_and_into_var! {
726 fn from(uri: crate::task::http::Uri) -> ImageSource {
727 ImageSource::Download(uri, None)
728 }
729 fn from((uri, accept): (crate::task::http::Uri, &'static str)) -> ImageSource {
731 ImageSource::Download(uri, Some(accept.into()))
732 }
733
734 fn from(s: &str) -> ImageSource {
740 use crate::task::http::uri::*;
741 if let Ok(uri) = Uri::try_from(s) {
742 if let Some(scheme) = uri.scheme() {
743 if scheme == &Scheme::HTTPS || scheme == &Scheme::HTTP {
744 return ImageSource::Download(uri, None);
745 } else if scheme.as_str() == "file" {
746 return PathBuf::from(uri.path()).into();
747 }
748 }
749 }
750 PathBuf::from(s).into()
751 }
752}
753
754#[cfg(not(feature = "http"))]
755impl_from_and_into_var! {
756 fn from(s: &str) -> ImageSource {
760 PathBuf::from(s).into()
761 }
762}
763
764impl_from_and_into_var! {
765 fn from(image: ImageVar) -> ImageSource {
766 ImageSource::Image(image)
767 }
768 fn from(path: PathBuf) -> ImageSource {
769 ImageSource::Read(path)
770 }
771 fn from(path: &Path) -> ImageSource {
772 path.to_owned().into()
773 }
774
775 fn from(s: String) -> ImageSource {
777 s.as_str().into()
778 }
779 fn from(s: Txt) -> ImageSource {
781 s.as_str().into()
782 }
783 fn from(data: &'static [u8]) -> ImageSource {
787 ImageSource::Static(ImageHash::compute(data), data, ImageDataFormat::Unknown)
788 }
789 fn from<const N: usize>(data: &'static [u8; N]) -> ImageSource {
793 (&data[..]).into()
794 }
795 fn from(data: Arc<Vec<u8>>) -> ImageSource {
799 ImageSource::Data(ImageHash::compute(&data[..]), data, ImageDataFormat::Unknown)
800 }
801 fn from(data: Vec<u8>) -> ImageSource {
805 Arc::new(data).into()
806 }
807 fn from<F: Into<ImageDataFormat>>((data, format): (&'static [u8], F)) -> ImageSource {
809 ImageSource::Static(ImageHash::compute(data), data, format.into())
810 }
811 fn from<F: Into<ImageDataFormat>, const N: usize>((data, format): (&'static [u8; N], F)) -> ImageSource {
813 (&data[..], format).into()
814 }
815 fn from<F: Into<ImageDataFormat>>((data, format): (Vec<u8>, F)) -> ImageSource {
817 (Arc::new(data), format).into()
818 }
819 fn from<F: Into<ImageDataFormat>>((data, format): (Arc<Vec<u8>>, F)) -> ImageSource {
821 ImageSource::Data(ImageHash::compute(&data[..]), data, format.into())
822 }
823}
824
825#[derive(Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
829pub enum ImageCacheMode {
830 Ignore,
832 Cache,
834 Retry,
836 Reload,
840}
841impl fmt::Debug for ImageCacheMode {
842 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
843 if f.alternate() {
844 write!(f, "CacheMode::")?;
845 }
846 match self {
847 Self::Ignore => write!(f, "Ignore"),
848 Self::Cache => write!(f, "Cache"),
849 Self::Retry => write!(f, "Retry"),
850 Self::Reload => write!(f, "Reload"),
851 }
852 }
853}
854
855#[derive(Clone)]
857pub enum ImageSourceFilter<U> {
858 BlockAll,
860 AllowAll,
862 Custom(Arc<dyn Fn(&U) -> bool + Send + Sync>),
864}
865impl<U> ImageSourceFilter<U> {
866 pub fn custom(allow: impl Fn(&U) -> bool + Send + Sync + 'static) -> Self {
870 Self::Custom(Arc::new(allow))
871 }
872
873 pub fn and(self, other: Self) -> Self
882 where
883 U: 'static,
884 {
885 use ImageSourceFilter::*;
886 match (self, other) {
887 (BlockAll, _) | (_, BlockAll) => BlockAll,
888 (AllowAll, _) | (_, AllowAll) => AllowAll,
889 (Custom(c0), Custom(c1)) => Custom(Arc::new(move |u| c0(u) && c1(u))),
890 }
891 }
892
893 pub fn or(self, other: Self) -> Self
902 where
903 U: 'static,
904 {
905 use ImageSourceFilter::*;
906 match (self, other) {
907 (AllowAll, _) | (_, AllowAll) => AllowAll,
908 (BlockAll, _) | (_, BlockAll) => BlockAll,
909 (Custom(c0), Custom(c1)) => Custom(Arc::new(move |u| c0(u) || c1(u))),
910 }
911 }
912
913 pub fn allows(&self, item: &U) -> bool {
915 match self {
916 ImageSourceFilter::BlockAll => false,
917 ImageSourceFilter::AllowAll => true,
918 ImageSourceFilter::Custom(f) => f(item),
919 }
920 }
921}
922impl<U> fmt::Debug for ImageSourceFilter<U> {
923 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
924 match self {
925 Self::BlockAll => write!(f, "BlockAll"),
926 Self::AllowAll => write!(f, "AllowAll"),
927 Self::Custom(_) => write!(f, "Custom(_)"),
928 }
929 }
930}
931impl<U: 'static> ops::BitAnd for ImageSourceFilter<U> {
932 type Output = Self;
933
934 fn bitand(self, rhs: Self) -> Self::Output {
935 self.and(rhs)
936 }
937}
938impl<U: 'static> ops::BitOr for ImageSourceFilter<U> {
939 type Output = Self;
940
941 fn bitor(self, rhs: Self) -> Self::Output {
942 self.or(rhs)
943 }
944}
945impl<U: 'static> ops::BitAndAssign for ImageSourceFilter<U> {
946 fn bitand_assign(&mut self, rhs: Self) {
947 *self = mem::replace(self, Self::BlockAll).and(rhs);
948 }
949}
950impl<U: 'static> ops::BitOrAssign for ImageSourceFilter<U> {
951 fn bitor_assign(&mut self, rhs: Self) {
952 *self = mem::replace(self, Self::BlockAll).or(rhs);
953 }
954}
955impl<U> PartialEq for ImageSourceFilter<U> {
956 fn eq(&self, other: &Self) -> bool {
957 match (self, other) {
958 (Self::Custom(l0), Self::Custom(r0)) => Arc::ptr_eq(l0, r0),
959 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
960 }
961 }
962}
963
964pub type PathFilter = ImageSourceFilter<PathBuf>;
974impl PathFilter {
975 pub fn allow_dir(dir: impl AsRef<Path>) -> Self {
977 let dir = crate::absolute_path(dir.as_ref(), || env::current_dir().expect("could not access current dir"), true);
978 PathFilter::custom(move |r| r.starts_with(&dir))
979 }
980
981 pub fn allow_ext(ext: impl Into<std::ffi::OsString>) -> Self {
983 let ext = ext.into();
984 PathFilter::custom(move |r| r.extension().map(|e| e == ext).unwrap_or(false))
985 }
986
987 pub fn allow_current_dir() -> Self {
995 PathFilter::custom(|r| env::current_dir().map(|d| r.starts_with(d)).unwrap_or(false))
996 }
997
998 pub fn allow_exe_dir() -> Self {
1000 if let Ok(mut p) = env::current_exe().and_then(dunce::canonicalize) {
1001 if p.pop() {
1002 return Self::allow_dir(p);
1003 }
1004 }
1005
1006 Self::custom(|_| false)
1008 }
1009
1010 pub fn allow_res() -> Self {
1014 Self::allow_dir(zng_env::res(""))
1015 }
1016}
1017
1018#[cfg(feature = "http")]
1022pub type UriFilter = ImageSourceFilter<crate::task::http::Uri>;
1023#[cfg(feature = "http")]
1024impl UriFilter {
1025 pub fn allow_host(host: impl Into<Txt>) -> Self {
1027 let host = host.into();
1028 UriFilter::custom(move |u| u.authority().map(|a| a.host() == host).unwrap_or(false))
1029 }
1030}
1031
1032impl<F: Fn(&PathBuf) -> bool + Send + Sync + 'static> From<F> for PathFilter {
1033 fn from(custom: F) -> Self {
1034 PathFilter::custom(custom)
1035 }
1036}
1037
1038#[cfg(feature = "http")]
1039impl<F: Fn(&task::http::Uri) -> bool + Send + Sync + 'static> From<F> for UriFilter {
1040 fn from(custom: F) -> Self {
1041 UriFilter::custom(custom)
1042 }
1043}
1044
1045#[derive(Clone, Debug, PartialEq)]
1047pub struct ImageLimits {
1048 pub max_encoded_len: ByteLength,
1056 pub max_decoded_len: ByteLength,
1060
1061 pub allow_path: PathFilter,
1065
1066 #[cfg(feature = "http")]
1068 pub allow_uri: UriFilter,
1069}
1070impl ImageLimits {
1071 pub fn none() -> Self {
1073 ImageLimits {
1074 max_encoded_len: ByteLength::MAX,
1075 max_decoded_len: ByteLength::MAX,
1076 allow_path: PathFilter::AllowAll,
1077 #[cfg(feature = "http")]
1078 allow_uri: UriFilter::AllowAll,
1079 }
1080 }
1081
1082 pub fn with_max_encoded_len(mut self, max_encoded_size: impl Into<ByteLength>) -> Self {
1086 self.max_encoded_len = max_encoded_size.into();
1087 self
1088 }
1089
1090 pub fn with_max_decoded_len(mut self, max_decoded_size: impl Into<ByteLength>) -> Self {
1094 self.max_decoded_len = max_decoded_size.into();
1095 self
1096 }
1097
1098 pub fn with_allow_path(mut self, allow_path: impl Into<PathFilter>) -> Self {
1102 self.allow_path = allow_path.into();
1103 self
1104 }
1105
1106 #[cfg(feature = "http")]
1110 pub fn with_allow_uri(mut self, allow_url: impl Into<UriFilter>) -> Self {
1111 self.allow_uri = allow_url.into();
1112 self
1113 }
1114}
1115impl Default for ImageLimits {
1116 fn default() -> Self {
1120 Self {
1121 max_encoded_len: 100.megabytes(),
1122 max_decoded_len: 4096.megabytes(),
1123 allow_path: PathFilter::allow_res(),
1124 #[cfg(feature = "http")]
1125 allow_uri: UriFilter::BlockAll,
1126 }
1127 }
1128}
1129impl_from_and_into_var! {
1130 fn from(some: ImageLimits) -> Option<ImageLimits>;
1131}