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