1#![doc(html_favicon_url = "https://zng-ui.github.io/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://zng-ui.github.io/res/zng-logo.png")]
3#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9#![warn(unused_extern_crates)]
10#![warn(missing_docs)]
11
12use std::{
13 env, mem,
14 path::{Path, PathBuf},
15 sync::atomic::{AtomicBool, Ordering},
16};
17
18use parking_lot::Mutex;
19use task::io::AsyncReadExt;
20use zng_app::{
21 APP, AppExtension,
22 update::EventUpdate,
23 view_process::{
24 VIEW_PROCESS, VIEW_PROCESS_INITED_EVENT, ViewImage,
25 raw_events::{LOW_MEMORY_EVENT, RAW_IMAGE_LOAD_ERROR_EVENT, RAW_IMAGE_LOADED_EVENT, RAW_IMAGE_METADATA_LOADED_EVENT},
26 },
27 widget::UiTaskWidget,
28};
29use zng_app_context::app_local;
30use zng_clone_move::{async_clmv, clmv};
31use zng_task as task;
32
33mod types;
34pub use types::*;
35
36mod render;
37#[doc(inline)]
38pub use render::{IMAGE_RENDER, IMAGES_WINDOW, ImageRenderWindowRoot, ImageRenderWindowsService, render_retain};
39use zng_layout::unit::{ByteLength, ByteUnits};
40use zng_task::{UiTask, channel::IpcBytes};
41use zng_txt::{ToTxt, Txt, formatx};
42use zng_unique_id::{IdEntry, IdMap};
43use zng_var::{Var, WeakVar, var};
44use zng_view_api::image::ImageRequest;
45
46#[derive(Default)]
54#[non_exhaustive]
55pub struct ImageManager {}
56impl AppExtension for ImageManager {
57 fn event_preview(&mut self, update: &mut EventUpdate) {
58 if let Some(args) = RAW_IMAGE_METADATA_LOADED_EVENT.on(update) {
59 let images = IMAGES_SV.read();
60
61 if let Some(var) = images
62 .decoding
63 .iter()
64 .map(|t| &t.image)
65 .find(|v| v.with(|img| img.view.get().unwrap() == &args.image))
66 {
67 var.update();
68 }
69 } else if let Some(args) = RAW_IMAGE_LOADED_EVENT.on(update) {
70 let image = &args.image;
71
72 let mut images = IMAGES_SV.write();
75
76 if let Some(i) = images
77 .decoding
78 .iter()
79 .position(|t| t.image.with(|img| img.view.get().unwrap() == image))
80 {
81 let ImageDecodingTask { image, .. } = images.decoding.swap_remove(i);
82 image.update();
83 image.with(|img| img.done_signal.set());
84 }
85 } else if let Some(args) = RAW_IMAGE_LOAD_ERROR_EVENT.on(update) {
86 let image = &args.image;
87
88 let mut images = IMAGES_SV.write();
91
92 if let Some(i) = images
93 .decoding
94 .iter()
95 .position(|t| t.image.with(|img| img.view.get().unwrap() == image))
96 {
97 let ImageDecodingTask { image, .. } = images.decoding.swap_remove(i);
98 image.update();
99 image.with(|img| {
100 img.done_signal.set();
101
102 if let Some(k) = &img.cache_key
103 && let Some(e) = images.cache.get(k)
104 {
105 e.error.store(true, Ordering::Relaxed);
106 }
107
108 tracing::error!("decode error: {:?}", img.error().unwrap());
109 });
110 }
111 } else if let Some(args) = VIEW_PROCESS_INITED_EVENT.on(update) {
112 let mut images = IMAGES_SV.write();
113 let images = &mut *images;
114 images.cleanup_not_cached(true);
115 images.download_accept.clear();
116
117 let mut decoding_interrupted = mem::take(&mut images.decoding);
118 for (img_var, max_decoded_len, downscale, mask) in images
119 .cache
120 .values()
121 .map(|e| (e.image.clone(), e.max_decoded_len, e.downscale, e.mask))
122 .chain(
123 images
124 .not_cached
125 .iter()
126 .filter_map(|e| e.image.upgrade().map(|v| (v, e.max_decoded_len, e.downscale, e.mask))),
127 )
128 {
129 let img = img_var.get();
130
131 if let Some(view) = img.view.get() {
132 if view.generation() == args.generation {
133 continue; }
135 if let Some(e) = view.error() {
136 img_var.set(Img::dummy(Some(e.to_owned())));
138 } else if let Some(task_i) = decoding_interrupted
139 .iter()
140 .position(|e| e.image.with(|img| img.view() == Some(view)))
141 {
142 let task = decoding_interrupted.swap_remove(task_i);
143 match VIEW_PROCESS.add_image(ImageRequest::new(
145 task.format.clone(),
146 task.data.clone(),
147 max_decoded_len.0 as u64,
148 downscale,
149 mask,
150 )) {
151 Ok(img) => {
152 img_var.set(Img::new(img));
153 }
154 Err(_) => { }
155 }
156 images.decoding.push(ImageDecodingTask {
157 format: task.format.clone(),
158 data: task.data.clone(),
159 image: img_var,
160 });
161 } else {
162 let img_format = if view.is_mask() {
165 ImageDataFormat::A8 { size: view.size() }
166 } else {
167 ImageDataFormat::Bgra8 {
168 size: view.size(),
169 density: view.density(),
170 }
171 };
172
173 let data = view.pixels().unwrap();
174 let img = match VIEW_PROCESS.add_image(ImageRequest::new(
175 img_format.clone(),
176 data.clone(),
177 max_decoded_len.0 as u64,
178 downscale,
179 mask,
180 )) {
181 Ok(img) => img,
182 Err(_) => return, };
184
185 img_var.set(Img::new(img));
186
187 images.decoding.push(ImageDecodingTask {
188 format: img_format,
189 data,
190 image: img_var,
191 });
192 }
193 } else if let Some(task_i) = decoding_interrupted.iter().position(|e| e.image.var_eq(&img_var)) {
194 let task = decoding_interrupted.swap_remove(task_i);
196 match VIEW_PROCESS.add_image(ImageRequest::new(
197 task.format.clone(),
198 task.data.clone(),
199 max_decoded_len.0 as u64,
200 downscale,
201 mask,
202 )) {
203 Ok(img) => {
204 img_var.set(Img::new(img));
205 }
206 Err(_) => { }
207 }
208 images.decoding.push(ImageDecodingTask {
209 format: task.format.clone(),
210 data: task.data.clone(),
211 image: img_var,
212 });
213 }
214 }
216 } else if LOW_MEMORY_EVENT.on(update).is_some() {
217 IMAGES.clean_all();
218 } else {
219 self.event_preview_render(update);
220 }
221 }
222
223 fn update_preview(&mut self) {
224 let mut images = IMAGES_SV.write();
227 let mut loading = Vec::with_capacity(images.loading.len());
228 let loading_tasks = mem::take(&mut images.loading);
229 let mut proxies = mem::take(&mut images.proxies);
230 drop(images); 'loading_tasks: for t in loading_tasks {
233 t.task.lock().update();
234 match t.task.into_inner().into_result() {
235 Ok(d) => {
236 match d.r {
237 Ok(data) => {
238 if let Some((key, mode)) = &t.is_data_proxy_source {
239 for proxy in &mut proxies {
240 if proxy.is_data_proxy()
241 && let Some(replaced) = proxy.data(key, &data, &d.format, *mode, t.downscale, t.mask, true)
242 {
243 replaced.set_bind(&t.image).perm();
244 t.image.hold(replaced).perm();
245 continue 'loading_tasks;
246 }
247 }
248 }
249
250 if VIEW_PROCESS.is_available() {
251 match VIEW_PROCESS.add_image(ImageRequest::new(
253 d.format.clone(),
254 data.clone(),
255 t.max_decoded_len.0 as u64,
256 t.downscale,
257 t.mask,
258 )) {
259 Ok(img) => {
260 t.image.modify(move |v| {
264 v.inner_set_or_replace(img, false);
265 });
266 }
267 Err(_) => {
268 }
270 }
271 IMAGES_SV.write().decoding.push(ImageDecodingTask {
272 format: d.format,
273 data,
274 image: t.image,
275 });
276 } else {
277 let img = ViewImage::dummy(None);
279 t.image.modify(move |v| {
280 v.inner_set_or_replace(img, true);
281 });
282 }
283 }
284 Err(e) => {
285 tracing::error!("load error: {e:?}");
286 let img = ViewImage::dummy(Some(e));
288 t.image.modify(move |v| {
289 v.inner_set_or_replace(img, true);
290 });
291
292 if let Some(k) = &t.image.with(|img| img.cache_key)
294 && let Some(e) = IMAGES_SV.read().cache.get(k)
295 {
296 e.error.store(true, Ordering::Relaxed);
297 }
298 }
299 }
300 }
301 Err(task) => {
302 loading.push(ImageLoadingTask {
303 task: Mutex::new(task),
304 image: t.image,
305 max_decoded_len: t.max_decoded_len,
306 downscale: t.downscale,
307 mask: t.mask,
308 is_data_proxy_source: t.is_data_proxy_source,
309 });
310 }
311 }
312 }
313 let mut images = IMAGES_SV.write();
314 images.loading = loading;
315 images.proxies = proxies;
316 }
317
318 fn update(&mut self) {
319 self.update_render();
320 }
321}
322
323app_local! {
324 static IMAGES_SV: ImagesService = {
325 APP.extensions().require::<ImageManager>();
326 ImagesService::new()
327 };
328}
329
330struct ImageLoadingTask {
331 task: Mutex<UiTask<ImageData>>,
332 image: Var<Img>,
333 max_decoded_len: ByteLength,
334 downscale: Option<ImageDownscale>,
335 mask: Option<ImageMaskMode>,
336 is_data_proxy_source: Option<(ImageHash, ImageCacheMode)>,
337}
338
339struct ImageDecodingTask {
340 format: ImageDataFormat,
341 data: IpcBytes,
342 image: Var<Img>,
343}
344
345struct CacheEntry {
346 image: Var<Img>,
347 error: AtomicBool,
348 max_decoded_len: ByteLength,
349 downscale: Option<ImageDownscale>,
350 mask: Option<ImageMaskMode>,
351}
352
353struct NotCachedEntry {
354 image: WeakVar<Img>,
355 max_decoded_len: ByteLength,
356 downscale: Option<ImageDownscale>,
357 mask: Option<ImageMaskMode>,
358}
359
360struct ImagesService {
361 load_in_headless: Var<bool>,
362 limits: Var<ImageLimits>,
363
364 download_accept: Txt,
365 proxies: Vec<Box<dyn ImageCacheProxy>>,
366
367 loading: Vec<ImageLoadingTask>,
368 decoding: Vec<ImageDecodingTask>,
369 cache: IdMap<ImageHash, CacheEntry>,
370 not_cached: Vec<NotCachedEntry>,
371
372 render: render::ImagesRender,
373}
374impl ImagesService {
375 fn new() -> Self {
376 Self {
377 load_in_headless: var(false),
378 limits: var(ImageLimits::default()),
379 proxies: vec![],
380 loading: vec![],
381 decoding: vec![],
382 download_accept: Txt::from_static(""),
383 cache: IdMap::new(),
384 not_cached: vec![],
385 render: render::ImagesRender::default(),
386 }
387 }
388
389 fn register(
390 &mut self,
391 key: ImageHash,
392 image: ViewImage,
393 downscale: Option<ImageDownscale>,
394 ) -> std::result::Result<ImageVar, (ViewImage, ImageVar)> {
395 let limits = self.limits.get();
396 let limits = ImageLimits {
397 max_encoded_len: limits.max_encoded_len,
398 max_decoded_len: limits.max_decoded_len.max(image.pixels().map(|b| b.len()).unwrap_or(0).bytes()),
399 allow_path: PathFilter::BlockAll,
400 #[cfg(feature = "http")]
401 allow_uri: UriFilter::BlockAll,
402 };
403
404 match self.cache.entry(key) {
405 IdEntry::Occupied(e) => Err((image, e.get().image.read_only())),
406 IdEntry::Vacant(e) => {
407 let is_error = image.is_error();
408 let is_loading = !is_error && !image.is_loaded();
409 let is_mask = image.is_mask();
410 let format = if is_mask {
411 ImageDataFormat::A8 { size: image.size() }
412 } else {
413 ImageDataFormat::Bgra8 {
414 size: image.size(),
415 density: image.density(),
416 }
417 };
418 let img_var = var(Img::new(image));
419 if is_loading {
420 self.decoding.push(ImageDecodingTask {
421 format,
422 data: IpcBytes::default(),
423 image: img_var.clone(),
424 });
425 }
426
427 Ok(e.insert(CacheEntry {
428 error: AtomicBool::new(is_error),
429 image: img_var,
430 max_decoded_len: limits.max_decoded_len,
431 downscale,
432 mask: if is_mask { Some(ImageMaskMode::A) } else { None },
433 })
434 .image
435 .read_only())
436 }
437 }
438 }
439
440 fn detach(&mut self, image: ImageVar) -> ImageVar {
441 if let Some(key) = &image.with(|i| i.cache_key) {
442 let decoded_size = image.with(|img| img.pixels().map(|b| b.len()).unwrap_or(0).bytes());
443 let mut max_decoded_len = self.limits.with(|l| l.max_decoded_len.max(decoded_size));
444 let mut downscale = None;
445 let mut mask = None;
446
447 if let Some(e) = self.cache.get(key) {
448 max_decoded_len = e.max_decoded_len;
449 downscale = e.downscale;
450 mask = e.mask;
451
452 if image.strong_count() == 2 {
454 self.cache.remove(key);
455 }
456 }
457
458 let mut img = image.get();
460 img.cache_key = None;
461 let img = var(img);
462 self.not_cached.push(NotCachedEntry {
463 image: img.downgrade(),
464 max_decoded_len,
465 downscale,
466 mask,
467 });
468 img.read_only()
469 } else {
470 image
472 }
473 }
474
475 fn proxy_then_remove(mut proxies: Vec<Box<dyn ImageCacheProxy>>, key: &ImageHash, purge: bool) -> bool {
476 for proxy in &mut proxies {
477 let r = proxy.remove(key, purge);
478 match r {
479 ProxyRemoveResult::None => continue,
480 ProxyRemoveResult::Remove(r, p) => return IMAGES_SV.write().proxied_remove(proxies, &r, p),
481 ProxyRemoveResult::Removed => {
482 IMAGES_SV.write().proxies.append(&mut proxies);
483 return true;
484 }
485 }
486 }
487 IMAGES_SV.write().proxied_remove(proxies, key, purge)
488 }
489 fn proxied_remove(&mut self, mut proxies: Vec<Box<dyn ImageCacheProxy>>, key: &ImageHash, purge: bool) -> bool {
490 self.proxies.append(&mut proxies);
491 if purge || self.cache.get(key).map(|v| v.image.strong_count() > 1).unwrap_or(false) {
492 self.cache.remove(key).is_some()
493 } else {
494 false
495 }
496 }
497
498 fn proxy_then_get(
499 mut proxies: Vec<Box<dyn ImageCacheProxy>>,
500 source: ImageSource,
501 mode: ImageCacheMode,
502 limits: ImageLimits,
503 downscale: Option<ImageDownscale>,
504 mask: Option<ImageMaskMode>,
505 ) -> ImageVar {
506 let source = match source {
507 ImageSource::Read(path) => {
508 let path = crate::absolute_path(&path, || env::current_dir().expect("could not access current dir"), true);
509 if !limits.allow_path.allows(&path) {
510 let error = formatx!("limits filter blocked `{}`", path.display());
511 tracing::error!("{error}");
512 IMAGES_SV.write().proxies.append(&mut proxies);
513 return var(Img::dummy(Some(error))).read_only();
514 }
515 ImageSource::Read(path)
516 }
517 #[cfg(feature = "http")]
518 ImageSource::Download(uri, accepts) => {
519 if !limits.allow_uri.allows(&uri) {
520 let error = formatx!("limits filter blocked `{uri}`");
521 tracing::error!("{error}");
522 IMAGES_SV.write().proxies.append(&mut proxies);
523 return var(Img::dummy(Some(error))).read_only();
524 }
525 ImageSource::Download(uri, accepts)
526 }
527 ImageSource::Image(r) => {
528 IMAGES_SV.write().proxies.append(&mut proxies);
529 return r;
530 }
531 source => source,
532 };
533
534 let key = source.hash128(downscale, mask).unwrap();
535 for proxy in &mut proxies {
536 if proxy.is_data_proxy() && !matches!(source, ImageSource::Data(_, _, _)) {
537 continue;
538 }
539
540 let r = proxy.get(&key, &source, mode, downscale, mask);
541 match r {
542 ProxyGetResult::None => continue,
543 ProxyGetResult::Cache(source, mode, downscale, mask) => {
544 return IMAGES_SV.write().proxied_get(
545 proxies,
546 source.hash128(downscale, mask).unwrap(),
547 source,
548 mode,
549 limits,
550 downscale,
551 mask,
552 );
553 }
554 ProxyGetResult::Image(img) => {
555 IMAGES_SV.write().proxies.append(&mut proxies);
556 return img;
557 }
558 }
559 }
560 IMAGES_SV.write().proxied_get(proxies, key, source, mode, limits, downscale, mask)
561 }
562 #[allow(clippy::too_many_arguments)]
563 fn proxied_get(
564 &mut self,
565 mut proxies: Vec<Box<dyn ImageCacheProxy>>,
566 key: ImageHash,
567 source: ImageSource,
568 mode: ImageCacheMode,
569 limits: ImageLimits,
570 downscale: Option<ImageDownscale>,
571 mask: Option<ImageMaskMode>,
572 ) -> ImageVar {
573 self.proxies.append(&mut proxies);
574 match mode {
575 ImageCacheMode::Cache => {
576 if let Some(v) = self.cache.get(&key) {
577 return v.image.read_only();
578 }
579 }
580 ImageCacheMode::Retry => {
581 if let Some(e) = self.cache.get(&key)
582 && !e.error.load(Ordering::Relaxed)
583 {
584 return e.image.read_only();
585 }
586 }
587 ImageCacheMode::Ignore | ImageCacheMode::Reload => {}
588 }
589
590 if !VIEW_PROCESS.is_available() && !self.load_in_headless.get() {
591 tracing::warn!("loading dummy image, set `load_in_headless=true` to actually load without renderer");
592
593 let dummy = var(Img::new(ViewImage::dummy(None)));
594 self.cache.insert(
595 key,
596 CacheEntry {
597 image: dummy.clone(),
598 error: AtomicBool::new(false),
599 max_decoded_len: limits.max_decoded_len,
600 downscale,
601 mask,
602 },
603 );
604 return dummy.read_only();
605 }
606
607 let max_encoded_size = limits.max_encoded_len;
608
609 match source {
610 ImageSource::Read(path) => self.load_task(
611 key,
612 mode,
613 limits.max_decoded_len,
614 downscale,
615 mask,
616 true,
617 task::run(async move {
618 let mut r = ImageData {
619 format: path
620 .extension()
621 .and_then(|e| e.to_str())
622 .map(|s| ImageDataFormat::FileExtension(Txt::from_str(s)))
623 .unwrap_or(ImageDataFormat::Unknown),
624 r: Err(Txt::from_static("")),
625 };
626
627 let mut file = match task::fs::File::open(path).await {
628 Ok(f) => f,
629 Err(e) => {
630 r.r = Err(e.to_txt());
631 return r;
632 }
633 };
634
635 let len = match file.metadata().await {
636 Ok(m) => m.len() as usize,
637 Err(e) => {
638 r.r = Err(e.to_txt());
639 return r;
640 }
641 };
642
643 if len > max_encoded_size.0 {
644 r.r = Err(formatx!("file size `{}` exceeds the limit of `{max_encoded_size}`", len.bytes()));
645 return r;
646 }
647
648 let mut data = Vec::with_capacity(len);
649 r.r = match file.read_to_end(&mut data).await {
650 Ok(_) => match IpcBytes::from_vec_blocking(data) {
651 Ok(r) => Ok(r),
652 Err(e) => Err(e.to_txt()),
653 },
654 Err(e) => Err(e.to_txt()),
655 };
656
657 r
658 }),
659 ),
660 #[cfg(feature = "http")]
661 ImageSource::Download(uri, accept) => {
662 let accept = accept.unwrap_or_else(|| self.download_accept());
663
664 self.load_task(
665 key,
666 mode,
667 limits.max_decoded_len,
668 downscale,
669 mask,
670 true,
671 task::run(async move {
672 let mut r = ImageData {
673 format: ImageDataFormat::Unknown,
674 r: Err(Txt::from_static("")),
675 };
676
677 let request = task::http::Request::get(uri)
678 .unwrap()
679 .header(task::http::header::ACCEPT, accept.as_str())
680 .unwrap()
681 .max_length(max_encoded_size);
682
683 match task::http::send(request).await {
684 Ok(mut rsp) => {
685 if let Some(m) = rsp.header().get(&task::http::header::CONTENT_TYPE).and_then(|v| v.to_str().ok()) {
686 let m = m.to_lowercase();
687 if m.starts_with("image/") {
688 r.format = ImageDataFormat::MimeType(Txt::from_str(&m));
689 }
690 }
691
692 r.r = rsp.body().await.map_err(|e| formatx!("download error: {e}"));
693 }
694 Err(e) => {
695 r.r = Err(formatx!("request error: {e}"));
696 }
697 }
698
699 r
700 }),
701 )
702 }
703 ImageSource::Data(_, bytes, fmt) => {
704 let r = ImageData { format: fmt, r: Ok(bytes) };
705 self.load_task(key, mode, limits.max_decoded_len, downscale, mask, false, async { r })
706 }
707 ImageSource::Render(rfn, args) => {
708 let img = self.new_cache_image(key, mode, limits.max_decoded_len, downscale, mask);
709 self.render_img(mask, clmv!(rfn, || rfn(&args.unwrap_or_default())), &img);
710 img.read_only()
711 }
712 ImageSource::Image(_) => unreachable!(),
713 }
714 }
715
716 #[cfg(feature = "http")]
717 fn download_accept(&mut self) -> Txt {
718 if self.download_accept.is_empty() {
719 if VIEW_PROCESS.is_available() {
720 let mut r = String::new();
721 let mut sep = "";
722 for fmt in VIEW_PROCESS.image_decoders().unwrap_or_default() {
723 r.push_str(sep);
724 r.push_str("image/");
725 r.push_str(&fmt);
726 sep = ",";
727 }
728 }
729 if self.download_accept.is_empty() {
730 self.download_accept = "image/*".into();
731 }
732 }
733 self.download_accept.clone()
734 }
735
736 fn cleanup_not_cached(&mut self, force: bool) {
737 if force || self.not_cached.len() > 1000 {
738 self.not_cached.retain(|c| c.image.strong_count() > 0);
739 }
740 }
741
742 fn new_cache_image(
743 &mut self,
744 key: ImageHash,
745 mode: ImageCacheMode,
746 max_decoded_len: ByteLength,
747 downscale: Option<ImageDownscale>,
748 mask: Option<ImageMaskMode>,
749 ) -> Var<Img> {
750 self.cleanup_not_cached(false);
751
752 if let ImageCacheMode::Reload = mode {
753 self.cache
754 .entry(key)
755 .or_insert_with(|| CacheEntry {
756 image: var(Img::new_none(Some(key))),
757 error: AtomicBool::new(false),
758 max_decoded_len,
759 downscale,
760 mask,
761 })
762 .image
763 .clone()
764 } else if let ImageCacheMode::Ignore = mode {
765 let img = var(Img::new_none(None));
766 self.not_cached.push(NotCachedEntry {
767 image: img.downgrade(),
768 max_decoded_len,
769 downscale,
770 mask,
771 });
772 img
773 } else {
774 let img = var(Img::new_none(Some(key)));
775 self.cache.insert(
776 key,
777 CacheEntry {
778 image: img.clone(),
779 error: AtomicBool::new(false),
780 max_decoded_len,
781 downscale,
782 mask,
783 },
784 );
785 img
786 }
787 }
788
789 #[allow(clippy::too_many_arguments)]
791 fn load_task(
792 &mut self,
793 key: ImageHash,
794 mode: ImageCacheMode,
795 max_decoded_len: ByteLength,
796 downscale: Option<ImageDownscale>,
797 mask: Option<ImageMaskMode>,
798 is_data_proxy_source: bool,
799 fetch_bytes: impl Future<Output = ImageData> + Send + 'static,
800 ) -> ImageVar {
801 let img = self.new_cache_image(key, mode, max_decoded_len, downscale, mask);
802 let r = img.read_only();
803
804 self.loading.push(ImageLoadingTask {
805 task: Mutex::new(UiTask::new(None, fetch_bytes)),
806 image: img,
807 max_decoded_len,
808 downscale,
809 mask,
810 is_data_proxy_source: if is_data_proxy_source { Some((key, mode)) } else { None },
811 });
812 zng_app::update::UPDATES.update(None);
813
814 r
815 }
816}
817
818pub struct IMAGES;
830impl IMAGES {
831 pub fn load_in_headless(&self) -> Var<bool> {
842 IMAGES_SV.read().load_in_headless.clone()
843 }
844
845 pub fn limits(&self) -> Var<ImageLimits> {
847 IMAGES_SV.read().limits.clone()
848 }
849
850 pub fn dummy(&self, error: Option<Txt>) -> ImageVar {
852 var(Img::dummy(error)).read_only()
853 }
854
855 pub fn read(&self, path: impl Into<PathBuf>) -> ImageVar {
857 self.cache(path.into())
858 }
859
860 #[cfg(feature = "http")]
865 pub fn download<U>(&self, uri: U, accept: Option<Txt>) -> ImageVar
866 where
867 U: TryInto<task::http::Uri>,
868 <U as TryInto<task::http::Uri>>::Error: ToTxt,
869 {
870 match uri.try_into() {
871 Ok(uri) => self.cache(ImageSource::Download(uri, accept)),
872 Err(e) => self.dummy(Some(e.to_txt())),
873 }
874 }
875
876 pub fn from_static(&self, data: &'static [u8], format: impl Into<ImageDataFormat>) -> ImageVar {
893 self.cache((data, format.into()))
894 }
895
896 pub fn from_data(&self, data: IpcBytes, format: impl Into<ImageDataFormat>) -> ImageVar {
902 self.cache((data, format.into()))
903 }
904
905 pub fn cache(&self, source: impl Into<ImageSource>) -> ImageVar {
907 self.image(source, ImageCacheMode::Cache, None, None, None)
908 }
909
910 pub fn retry(&self, source: impl Into<ImageSource>) -> ImageVar {
912 self.image(source, ImageCacheMode::Retry, None, None, None)
913 }
914
915 pub fn reload(&self, source: impl Into<ImageSource>) -> ImageVar {
917 self.image(source, ImageCacheMode::Reload, None, None, None)
918 }
919
920 pub fn image(
926 &self,
927 source: impl Into<ImageSource>,
928 cache_mode: impl Into<ImageCacheMode>,
929 limits: Option<ImageLimits>,
930 downscale: Option<ImageDownscale>,
931 mask: Option<ImageMaskMode>,
932 ) -> ImageVar {
933 let limits = limits.unwrap_or_else(|| IMAGES_SV.read().limits.get());
934 let proxies = mem::take(&mut IMAGES_SV.write().proxies);
935 ImagesService::proxy_then_get(proxies, source.into(), cache_mode.into(), limits, downscale, mask)
936 }
937
938 pub fn image_task<F>(
949 &self,
950 source: impl IntoFuture<IntoFuture = F>,
951 cache_mode: impl Into<ImageCacheMode>,
952 cache_key: Option<ImageHash>,
953 limits: Option<ImageLimits>,
954 downscale: Option<ImageDownscale>,
955 mask: Option<ImageMaskMode>,
956 ) -> ImageVar
957 where
958 F: Future<Output = ImageSource> + Send + 'static,
959 {
960 let cache_mode = cache_mode.into();
961
962 if let Some(key) = cache_key {
963 match cache_mode {
964 ImageCacheMode::Cache => {
965 if let Some(v) = IMAGES_SV.read().cache.get(&key) {
966 return v.image.read_only();
967 }
968 }
969 ImageCacheMode::Retry => {
970 if let Some(e) = IMAGES_SV.read().cache.get(&key)
971 && !e.error.load(Ordering::Relaxed)
972 {
973 return e.image.read_only();
974 }
975 }
976 ImageCacheMode::Ignore | ImageCacheMode::Reload => {}
977 }
978 }
979
980 let source = source.into_future();
981 let img = var(Img::new_none(cache_key));
982
983 task::spawn(async_clmv!(img, {
984 let source = source.await;
985 let actual_img = IMAGES.image(source, cache_mode, limits, downscale, mask);
986 actual_img.set_bind(&img).perm();
987 img.hold(actual_img).perm();
988 }));
989 img.read_only()
990 }
991
992 pub fn register(&self, key: ImageHash, image: ViewImage) -> std::result::Result<ImageVar, (ViewImage, ImageVar)> {
997 IMAGES_SV.write().register(key, image, None)
998 }
999
1000 pub fn clean(&self, key: ImageHash) -> bool {
1007 ImagesService::proxy_then_remove(mem::take(&mut IMAGES_SV.write().proxies), &key, false)
1008 }
1009
1010 pub fn purge(&self, key: &ImageHash) -> bool {
1017 ImagesService::proxy_then_remove(mem::take(&mut IMAGES_SV.write().proxies), key, true)
1018 }
1019
1020 pub fn cache_key(&self, image: &Img) -> Option<ImageHash> {
1022 if let Some(key) = &image.cache_key
1023 && IMAGES_SV.read().cache.contains_key(key)
1024 {
1025 return Some(*key);
1026 }
1027 None
1028 }
1029
1030 pub fn is_cached(&self, image: &Img) -> bool {
1032 image
1033 .cache_key
1034 .as_ref()
1035 .map(|k| IMAGES_SV.read().cache.contains_key(k))
1036 .unwrap_or(false)
1037 }
1038
1039 pub fn detach(&self, image: ImageVar) -> ImageVar {
1044 IMAGES_SV.write().detach(image)
1045 }
1046
1047 pub fn clean_all(&self) {
1049 let mut img = IMAGES_SV.write();
1050 img.proxies.iter_mut().for_each(|p| p.clear(false));
1051 img.cache.retain(|_, v| v.image.strong_count() > 1);
1052 }
1053
1054 pub fn purge_all(&self) {
1059 let mut img = IMAGES_SV.write();
1060 img.cache.clear();
1061 img.proxies.iter_mut().for_each(|p| p.clear(true));
1062 }
1063
1064 pub fn install_proxy(&self, proxy: Box<dyn ImageCacheProxy>) {
1068 IMAGES_SV.write().proxies.push(proxy);
1069 }
1070
1071 pub fn available_decoders(&self) -> Vec<Txt> {
1075 VIEW_PROCESS.image_decoders().unwrap_or_default()
1076 }
1077
1078 pub fn available_encoders(&self) -> Vec<Txt> {
1082 VIEW_PROCESS.image_encoders().unwrap_or_default()
1083 }
1084}
1085struct ImageData {
1086 format: ImageDataFormat,
1087 r: std::result::Result<IpcBytes, Txt>,
1088}
1089
1090fn absolute_path(path: &Path, base: impl FnOnce() -> PathBuf, allow_escape: bool) -> PathBuf {
1091 if path.is_absolute() {
1092 normalize_path(path)
1093 } else {
1094 let mut dir = base();
1095 if allow_escape {
1096 dir.push(path);
1097 normalize_path(&dir)
1098 } else {
1099 dir.push(normalize_path(path));
1100 dir
1101 }
1102 }
1103}
1104fn normalize_path(path: &Path) -> PathBuf {
1108 use std::path::Component;
1109
1110 let mut components = path.components().peekable();
1111 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
1112 components.next();
1113 PathBuf::from(c.as_os_str())
1114 } else {
1115 PathBuf::new()
1116 };
1117
1118 for component in components {
1119 match component {
1120 Component::Prefix(..) => unreachable!(),
1121 Component::RootDir => {
1122 ret.push(component.as_os_str());
1123 }
1124 Component::CurDir => {}
1125 Component::ParentDir => {
1126 ret.pop();
1127 }
1128 Component::Normal(c) => {
1129 ret.push(c);
1130 }
1131 }
1132 }
1133 ret
1134}