1#![doc(html_favicon_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/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 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::{AnyVar, AnyWeakVar, ArcVar, Var, WeakVar, types::WeakArcVar, var};
47use zng_view_api::{ViewProcessOffline, image::ImageRequest, ipc::IpcBytes};
48
49#[derive(Default)]
57pub struct ImageManager {}
58impl AppExtension for ImageManager {
59 fn event_preview(&mut self, update: &mut EventUpdate) {
60 if let Some(args) = RAW_IMAGE_METADATA_LOADED_EVENT.on(update) {
61 let images = IMAGES_SV.read();
62
63 if let Some(var) = images
64 .decoding
65 .iter()
66 .map(|t| &t.image)
67 .find(|v| v.with(|img| img.view.get().unwrap() == &args.image))
68 {
69 var.update();
70 }
71 } else if let Some(args) = RAW_IMAGE_LOADED_EVENT.on(update) {
72 let image = &args.image;
73
74 let mut images = IMAGES_SV.write();
77
78 if let Some(i) = images
79 .decoding
80 .iter()
81 .position(|t| t.image.with(|img| img.view.get().unwrap() == image))
82 {
83 let ImageDecodingTask { image, .. } = images.decoding.swap_remove(i);
84 image.update();
85 image.with(|img| img.done_signal.set());
86 }
87 } else if let Some(args) = RAW_IMAGE_LOAD_ERROR_EVENT.on(update) {
88 let image = &args.image;
89
90 let mut images = IMAGES_SV.write();
93
94 if let Some(i) = images
95 .decoding
96 .iter()
97 .position(|t| t.image.with(|img| img.view.get().unwrap() == image))
98 {
99 let ImageDecodingTask { image, .. } = images.decoding.swap_remove(i);
100 image.update();
101 image.with(|img| {
102 img.done_signal.set();
103
104 if let Some(k) = &img.cache_key {
105 if let Some(e) = images.cache.get(k) {
106 e.error.store(true, Ordering::Relaxed);
107 }
108 }
109
110 tracing::error!("decode error: {:?}", img.error().unwrap());
111 });
112 }
113 } else if let Some(args) = VIEW_PROCESS_INITED_EVENT.on(update) {
114 if !args.is_respawn {
115 return;
116 }
117
118 let mut images = IMAGES_SV.write();
119 let images = &mut *images;
120 images.cleanup_not_cached(true);
121 images.download_accept.clear();
122
123 let decoding_interrupted = mem::take(&mut images.decoding);
124 for (img_var, max_decoded_len, downscale, mask) in images
125 .cache
126 .values()
127 .map(|e| (e.image.clone(), e.max_decoded_len, e.downscale, e.mask))
128 .chain(
129 images
130 .not_cached
131 .iter()
132 .filter_map(|e| e.image.upgrade().map(|v| (v, e.max_decoded_len, e.downscale, e.mask))),
133 )
134 {
135 let img = img_var.get();
136
137 if let Some(view) = img.view.get() {
138 if view.generation() == args.generation {
139 continue; }
141 if let Some(e) = view.error() {
142 img_var.set(Img::dummy(Some(e.to_owned())));
144 } else if let Some(task) = decoding_interrupted.iter().find(|e| e.image.with(|img| img.view() == Some(view))) {
145 match VIEW_PROCESS.add_image(ImageRequest {
148 format: task.format.clone(),
149 data: task.data.clone(),
150 max_decoded_len: max_decoded_len.0 as u64,
151 downscale,
152 mask,
153 }) {
154 Ok(img) => {
155 img_var.set(Img::new(img));
156 }
157 Err(ViewProcessOffline) => { }
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 ppi: view.ppi(),
173 }
174 };
175
176 let data = view.pixels().unwrap();
177 let img = match VIEW_PROCESS.add_image(ImageRequest {
178 format: img_format.clone(),
179 data: data.clone(),
180 max_decoded_len: max_decoded_len.0 as u64,
181 downscale,
182 mask,
183 }) {
184 Ok(img) => img,
185 Err(ViewProcessOffline) => 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 } }
198 } else if LOW_MEMORY_EVENT.on(update).is_some() {
199 IMAGES.clean_all();
200 } else {
201 self.event_preview_render(update);
202 }
203 }
204
205 fn update_preview(&mut self) {
206 let mut images = IMAGES_SV.write();
209 let mut loading = Vec::with_capacity(images.loading.len());
210 let loading_tasks = mem::take(&mut images.loading);
211 let mut proxies = mem::take(&mut images.proxies);
212 drop(images); 'loading_tasks: for t in loading_tasks {
215 t.task.lock().update();
216 match t.task.into_inner().into_result() {
217 Ok(d) => {
218 match d.r {
219 Ok(data) => {
220 if let Some((key, mode)) = &t.is_data_proxy_source {
221 for proxy in &mut proxies {
222 if proxy.is_data_proxy() {
223 if let Some(replaced) = proxy.data(key, &data, &d.format, *mode, t.downscale, t.mask, true) {
224 replaced.set_bind(&t.image).perm();
225 t.image.hold(replaced).perm();
226 continue 'loading_tasks;
227 }
228 }
229 }
230 }
231
232 if VIEW_PROCESS.is_available() {
233 match VIEW_PROCESS.add_image(ImageRequest {
235 format: d.format.clone(),
236 data: data.clone(),
237 max_decoded_len: t.max_decoded_len.0 as u64,
238 downscale: t.downscale,
239 mask: t.mask,
240 }) {
241 Ok(img) => {
242 t.image.modify(move |v| {
246 v.to_mut().view.set(img).unwrap();
247 });
248 }
249 Err(ViewProcessOffline) => {
250 }
252 }
253 IMAGES_SV.write().decoding.push(ImageDecodingTask {
254 format: d.format,
255 data,
256 image: t.image,
257 });
258 } else {
259 let img = ViewImage::dummy(None);
261 t.image.modify(move |v| {
262 let v = v.to_mut();
263 v.view.set(img).unwrap();
264 v.done_signal.set();
265 });
266 }
267 }
268 Err(e) => {
269 tracing::error!("load error: {e:?}");
270 let img = ViewImage::dummy(Some(e));
272 t.image.modify(move |v| {
273 let v = v.to_mut();
274 v.view.set(img).unwrap();
275 v.done_signal.set();
276 });
277
278 if let Some(k) = &t.image.with(|img| img.cache_key) {
280 if let Some(e) = IMAGES_SV.read().cache.get(k) {
281 e.error.store(true, Ordering::Relaxed);
282 }
283 }
284 }
285 }
286 }
287 Err(task) => {
288 loading.push(ImageLoadingTask {
289 task: Mutex::new(task),
290 image: t.image,
291 max_decoded_len: t.max_decoded_len,
292 downscale: t.downscale,
293 mask: t.mask,
294 is_data_proxy_source: t.is_data_proxy_source,
295 });
296 }
297 }
298 }
299 let mut images = IMAGES_SV.write();
300 images.loading = loading;
301 images.proxies = proxies;
302 }
303
304 fn update(&mut self) {
305 self.update_render();
306 }
307}
308
309app_local! {
310 static IMAGES_SV: ImagesService = ImagesService::new();
311}
312
313struct ImageLoadingTask {
314 task: Mutex<UiTask<ImageData>>,
315 image: ArcVar<Img>,
316 max_decoded_len: ByteLength,
317 downscale: Option<ImageDownscale>,
318 mask: Option<ImageMaskMode>,
319 is_data_proxy_source: Option<(ImageHash, ImageCacheMode)>,
320}
321
322struct ImageDecodingTask {
323 format: ImageDataFormat,
324 data: IpcBytes,
325 image: ArcVar<Img>,
326}
327
328struct CacheEntry {
329 image: ArcVar<Img>,
330 error: AtomicBool,
331 max_decoded_len: ByteLength,
332 downscale: Option<ImageDownscale>,
333 mask: Option<ImageMaskMode>,
334}
335
336struct NotCachedEntry {
337 image: WeakArcVar<Img>,
338 max_decoded_len: ByteLength,
339 downscale: Option<ImageDownscale>,
340 mask: Option<ImageMaskMode>,
341}
342
343struct ImagesService {
344 load_in_headless: ArcVar<bool>,
345 limits: ArcVar<ImageLimits>,
346
347 download_accept: Txt,
348 proxies: Vec<Box<dyn ImageCacheProxy>>,
349
350 loading: Vec<ImageLoadingTask>,
351 decoding: Vec<ImageDecodingTask>,
352 cache: IdMap<ImageHash, CacheEntry>,
353 not_cached: Vec<NotCachedEntry>,
354
355 render: render::ImagesRender,
356}
357impl ImagesService {
358 fn new() -> Self {
359 Self {
360 load_in_headless: var(false),
361 limits: var(ImageLimits::default()),
362 proxies: vec![],
363 loading: vec![],
364 decoding: vec![],
365 download_accept: Txt::from_static(""),
366 cache: IdMap::new(),
367 not_cached: vec![],
368 render: render::ImagesRender::default(),
369 }
370 }
371
372 fn register(
373 &mut self,
374 key: ImageHash,
375 image: ViewImage,
376 downscale: Option<ImageDownscale>,
377 ) -> std::result::Result<ImageVar, (ViewImage, ImageVar)> {
378 let limits = self.limits.get();
379 let limits = ImageLimits {
380 max_encoded_len: limits.max_encoded_len,
381 max_decoded_len: limits.max_decoded_len.max(image.pixels().map(|b| b.len()).unwrap_or(0).bytes()),
382 allow_path: PathFilter::BlockAll,
383 #[cfg(feature = "http")]
384 allow_uri: UriFilter::BlockAll,
385 };
386
387 match self.cache.entry(key) {
388 IdEntry::Occupied(e) => Err((image, e.get().image.read_only())),
389 IdEntry::Vacant(e) => {
390 let is_error = image.is_error();
391 let is_loading = !is_error && !image.is_loaded();
392 let is_mask = image.is_mask();
393 let format = if is_mask {
394 ImageDataFormat::A8 { size: image.size() }
395 } else {
396 ImageDataFormat::Bgra8 {
397 size: image.size(),
398 ppi: image.ppi(),
399 }
400 };
401 let img_var = var(Img::new(image));
402 if is_loading {
403 self.decoding.push(ImageDecodingTask {
404 format,
405 data: IpcBytes::from_vec(vec![]),
406 image: img_var.clone(),
407 });
408 }
409
410 Ok(e.insert(CacheEntry {
411 error: AtomicBool::new(is_error),
412 image: img_var,
413 max_decoded_len: limits.max_decoded_len,
414 downscale,
415 mask: if is_mask { Some(ImageMaskMode::A) } else { None },
416 })
417 .image
418 .read_only())
419 }
420 }
421 }
422
423 fn detach(&mut self, image: ImageVar) -> ImageVar {
424 if let Some(key) = &image.with(|i| i.cache_key) {
425 let decoded_size = image.with(|img| img.pixels().map(|b| b.len()).unwrap_or(0).bytes());
426 let mut max_decoded_len = self.limits.with(|l| l.max_decoded_len.max(decoded_size));
427 let mut downscale = None;
428 let mut mask = None;
429
430 if let Some(e) = self.cache.get(key) {
431 max_decoded_len = e.max_decoded_len;
432 downscale = e.downscale;
433 mask = e.mask;
434
435 if image.strong_count() == 2 {
437 self.cache.remove(key);
438 }
439 }
440
441 let mut img = image.into_value();
443 img.cache_key = None;
444 let img = var(img);
445 self.not_cached.push(NotCachedEntry {
446 image: img.downgrade(),
447 max_decoded_len,
448 downscale,
449 mask,
450 });
451 img.read_only()
452 } else {
453 image
455 }
456 }
457
458 fn proxy_then_remove(mut proxies: Vec<Box<dyn ImageCacheProxy>>, key: &ImageHash, purge: bool) -> bool {
459 for proxy in &mut proxies {
460 let r = proxy.remove(key, purge);
461 match r {
462 ProxyRemoveResult::None => continue,
463 ProxyRemoveResult::Remove(r, p) => return IMAGES_SV.write().proxied_remove(proxies, &r, p),
464 ProxyRemoveResult::Removed => {
465 IMAGES_SV.write().proxies.append(&mut proxies);
466 return true;
467 }
468 }
469 }
470 IMAGES_SV.write().proxied_remove(proxies, key, purge)
471 }
472 fn proxied_remove(&mut self, mut proxies: Vec<Box<dyn ImageCacheProxy>>, key: &ImageHash, purge: bool) -> bool {
473 self.proxies.append(&mut proxies);
474 if purge || self.cache.get(key).map(|v| v.image.strong_count() > 1).unwrap_or(false) {
475 self.cache.remove(key).is_some()
476 } else {
477 false
478 }
479 }
480
481 fn proxy_then_get(
482 mut proxies: Vec<Box<dyn ImageCacheProxy>>,
483 source: ImageSource,
484 mode: ImageCacheMode,
485 limits: ImageLimits,
486 downscale: Option<ImageDownscale>,
487 mask: Option<ImageMaskMode>,
488 ) -> ImageVar {
489 let source = match source {
490 ImageSource::Read(path) => {
491 let path = crate::absolute_path(&path, || env::current_dir().expect("could not access current dir"), true);
492 if !limits.allow_path.allows(&path) {
493 let error = formatx!("limits filter blocked `{}`", path.display());
494 tracing::error!("{error}");
495 IMAGES_SV.write().proxies.append(&mut proxies);
496 return var(Img::dummy(Some(error))).read_only();
497 }
498 ImageSource::Read(path)
499 }
500 #[cfg(feature = "http")]
501 ImageSource::Download(uri, accepts) => {
502 if !limits.allow_uri.allows(&uri) {
503 let error = formatx!("limits filter blocked `{uri}`");
504 tracing::error!("{error}");
505 IMAGES_SV.write().proxies.append(&mut proxies);
506 return var(Img::dummy(Some(error))).read_only();
507 }
508 ImageSource::Download(uri, accepts)
509 }
510 ImageSource::Image(r) => {
511 IMAGES_SV.write().proxies.append(&mut proxies);
512 return r;
513 }
514 source => source,
515 };
516
517 let key = source.hash128(downscale, mask).unwrap();
518 for proxy in &mut proxies {
519 if proxy.is_data_proxy() && !matches!(source, ImageSource::Data(_, _, _) | ImageSource::Static(_, _, _)) {
520 continue;
521 }
522
523 let r = proxy.get(&key, &source, mode, downscale, mask);
524 match r {
525 ProxyGetResult::None => continue,
526 ProxyGetResult::Cache(source, mode, downscale, mask) => {
527 return IMAGES_SV.write().proxied_get(
528 proxies,
529 source.hash128(downscale, mask).unwrap(),
530 source,
531 mode,
532 limits,
533 downscale,
534 mask,
535 );
536 }
537 ProxyGetResult::Image(img) => {
538 IMAGES_SV.write().proxies.append(&mut proxies);
539 return img;
540 }
541 }
542 }
543 IMAGES_SV.write().proxied_get(proxies, key, source, mode, limits, downscale, mask)
544 }
545 #[allow(clippy::too_many_arguments)]
546 fn proxied_get(
547 &mut self,
548 mut proxies: Vec<Box<dyn ImageCacheProxy>>,
549 key: ImageHash,
550 source: ImageSource,
551 mode: ImageCacheMode,
552 limits: ImageLimits,
553 downscale: Option<ImageDownscale>,
554 mask: Option<ImageMaskMode>,
555 ) -> ImageVar {
556 self.proxies.append(&mut proxies);
557 match mode {
558 ImageCacheMode::Cache => {
559 if let Some(v) = self.cache.get(&key) {
560 return v.image.read_only();
561 }
562 }
563 ImageCacheMode::Retry => {
564 if let Some(e) = self.cache.get(&key) {
565 if !e.error.load(Ordering::Relaxed) {
566 return e.image.read_only();
567 }
568 }
569 }
570 ImageCacheMode::Ignore | ImageCacheMode::Reload => {}
571 }
572
573 if !VIEW_PROCESS.is_available() && !self.load_in_headless.get() {
574 tracing::warn!("loading dummy image, set `load_in_headless=true` to actually load without renderer");
575
576 let dummy = var(Img::new(ViewImage::dummy(None)));
577 self.cache.insert(
578 key,
579 CacheEntry {
580 image: dummy.clone(),
581 error: AtomicBool::new(false),
582 max_decoded_len: limits.max_decoded_len,
583 downscale,
584 mask,
585 },
586 );
587 return dummy.read_only();
588 }
589
590 let max_encoded_size = limits.max_encoded_len;
591
592 match source {
593 ImageSource::Read(path) => self.load_task(
594 key,
595 mode,
596 limits.max_decoded_len,
597 downscale,
598 mask,
599 true,
600 task::run(async move {
601 let mut r = ImageData {
602 format: path
603 .extension()
604 .and_then(|e| e.to_str())
605 .map(|s| ImageDataFormat::FileExtension(Txt::from_str(s)))
606 .unwrap_or(ImageDataFormat::Unknown),
607 r: Err(Txt::from_static("")),
608 };
609
610 let mut file = match task::fs::File::open(path).await {
611 Ok(f) => f,
612 Err(e) => {
613 r.r = Err(e.to_txt());
614 return r;
615 }
616 };
617
618 let len = match file.metadata().await {
619 Ok(m) => m.len() as usize,
620 Err(e) => {
621 r.r = Err(e.to_txt());
622 return r;
623 }
624 };
625
626 if len > max_encoded_size.0 {
627 r.r = Err(formatx!("file size `{}` exceeds the limit of `{max_encoded_size}`", len.bytes()));
628 return r;
629 }
630
631 let mut data = Vec::with_capacity(len);
632 r.r = match file.read_to_end(&mut data).await {
633 Ok(_) => Ok(IpcBytes::from_vec(data)),
634 Err(e) => Err(e.to_txt()),
635 };
636
637 r
638 }),
639 ),
640 #[cfg(feature = "http")]
641 ImageSource::Download(uri, accept) => {
642 let accept = accept.unwrap_or_else(|| self.download_accept());
643
644 self.load_task(
645 key,
646 mode,
647 limits.max_decoded_len,
648 downscale,
649 mask,
650 true,
651 task::run(async move {
652 let mut r = ImageData {
653 format: ImageDataFormat::Unknown,
654 r: Err(Txt::from_static("")),
655 };
656
657 let request = task::http::Request::get(uri)
658 .unwrap()
659 .header(task::http::header::ACCEPT, accept.as_str())
660 .unwrap()
661 .max_length(max_encoded_size)
662 .build();
663
664 match task::http::send(request).await {
665 Ok(mut rsp) => {
666 if let Some(m) = rsp.headers().get(&task::http::header::CONTENT_TYPE).and_then(|v| v.to_str().ok()) {
667 let m = m.to_lowercase();
668 if m.starts_with("image/") {
669 r.format = ImageDataFormat::MimeType(Txt::from_str(&m));
670 }
671 }
672
673 match rsp.bytes().await {
674 Ok(d) => r.r = Ok(IpcBytes::from_vec(d)),
675 Err(e) => {
676 r.r = Err(formatx!("download error: {e}"));
677 }
678 }
679
680 let _ = rsp.consume().await;
681 }
682 Err(e) => {
683 r.r = Err(formatx!("request error: {e}"));
684 }
685 }
686
687 r
688 }),
689 )
690 }
691 ImageSource::Static(_, bytes, fmt) => {
692 let r = ImageData {
693 format: fmt,
694 r: Ok(IpcBytes::from_slice(bytes)),
695 };
696 self.load_task(key, mode, limits.max_decoded_len, downscale, mask, false, async { r })
697 }
698 ImageSource::Data(_, bytes, fmt) => {
699 let r = ImageData {
700 format: fmt,
701 r: Ok(IpcBytes::from_slice(&bytes)),
702 };
703 self.load_task(key, mode, limits.max_decoded_len, downscale, mask, false, async { r })
704 }
705 ImageSource::Render(rfn, args) => {
706 let img = self.new_cache_image(key, mode, limits.max_decoded_len, downscale, mask);
707 self.render_img(mask, clmv!(rfn, || rfn(&args.unwrap_or_default())), &img);
708 img.read_only()
709 }
710 ImageSource::Image(_) => unreachable!(),
711 }
712 }
713
714 #[cfg(feature = "http")]
715 fn download_accept(&mut self) -> Txt {
716 if self.download_accept.is_empty() {
717 if VIEW_PROCESS.is_available() {
718 let mut r = String::new();
719 let mut sep = "";
720 for fmt in VIEW_PROCESS.image_decoders().unwrap_or_default() {
721 r.push_str(sep);
722 r.push_str("image/");
723 r.push_str(&fmt);
724 sep = ",";
725 }
726 }
727 if self.download_accept.is_empty() {
728 self.download_accept = "image/*".into();
729 }
730 }
731 self.download_accept.clone()
732 }
733
734 fn cleanup_not_cached(&mut self, force: bool) {
735 if force || self.not_cached.len() > 1000 {
736 self.not_cached.retain(|c| c.image.strong_count() > 0);
737 }
738 }
739
740 fn new_cache_image(
741 &mut self,
742 key: ImageHash,
743 mode: ImageCacheMode,
744 max_decoded_len: ByteLength,
745 downscale: Option<ImageDownscale>,
746 mask: Option<ImageMaskMode>,
747 ) -> ArcVar<Img> {
748 self.cleanup_not_cached(false);
749
750 if let ImageCacheMode::Reload = mode {
751 self.cache
752 .entry(key)
753 .or_insert_with(|| CacheEntry {
754 image: var(Img::new_none(Some(key))),
755 error: AtomicBool::new(false),
756 max_decoded_len,
757 downscale,
758 mask,
759 })
760 .image
761 .clone()
762 } else if let ImageCacheMode::Ignore = mode {
763 let img = var(Img::new_none(None));
764 self.not_cached.push(NotCachedEntry {
765 image: img.downgrade(),
766 max_decoded_len,
767 downscale,
768 mask,
769 });
770 img
771 } else {
772 let img = var(Img::new_none(Some(key)));
773 self.cache.insert(
774 key,
775 CacheEntry {
776 image: img.clone(),
777 error: AtomicBool::new(false),
778 max_decoded_len,
779 downscale,
780 mask,
781 },
782 );
783 img
784 }
785 }
786
787 #[allow(clippy::too_many_arguments)]
789 fn load_task(
790 &mut self,
791 key: ImageHash,
792 mode: ImageCacheMode,
793 max_decoded_len: ByteLength,
794 downscale: Option<ImageDownscale>,
795 mask: Option<ImageMaskMode>,
796 is_data_proxy_source: bool,
797 fetch_bytes: impl Future<Output = ImageData> + Send + 'static,
798 ) -> ImageVar {
799 let img = self.new_cache_image(key, mode, max_decoded_len, downscale, mask);
800 let r = img.read_only();
801
802 self.loading.push(ImageLoadingTask {
803 task: Mutex::new(UiTask::new(None, fetch_bytes)),
804 image: img,
805 max_decoded_len,
806 downscale,
807 mask,
808 is_data_proxy_source: if is_data_proxy_source { Some((key, mode)) } else { None },
809 });
810 zng_app::update::UPDATES.update(None);
811
812 r
813 }
814}
815
816pub struct IMAGES;
824impl IMAGES {
825 pub fn load_in_headless(&self) -> ArcVar<bool> {
836 IMAGES_SV.read().load_in_headless.clone()
837 }
838
839 pub fn limits(&self) -> ArcVar<ImageLimits> {
841 IMAGES_SV.read().limits.clone()
842 }
843
844 pub fn dummy(&self, error: Option<Txt>) -> ImageVar {
846 var(Img::dummy(error)).read_only()
847 }
848
849 pub fn read(&self, path: impl Into<PathBuf>) -> ImageVar {
851 self.cache(path.into())
852 }
853
854 #[cfg(feature = "http")]
859 pub fn download(&self, uri: impl task::http::TryUri, accept: Option<Txt>) -> ImageVar {
860 match uri.try_uri() {
861 Ok(uri) => self.cache(ImageSource::Download(uri, accept)),
862 Err(e) => self.dummy(Some(e.to_txt())),
863 }
864 }
865
866 pub fn from_static(&self, data: &'static [u8], format: impl Into<ImageDataFormat>) -> ImageVar {
883 self.cache((data, format.into()))
884 }
885
886 pub fn from_data(&self, data: Arc<Vec<u8>>, format: impl Into<ImageDataFormat>) -> ImageVar {
892 self.cache((data, format.into()))
893 }
894
895 pub fn cache(&self, source: impl Into<ImageSource>) -> ImageVar {
897 self.image(source, ImageCacheMode::Cache, None, None, None)
898 }
899
900 pub fn retry(&self, source: impl Into<ImageSource>) -> ImageVar {
902 self.image(source, ImageCacheMode::Retry, None, None, None)
903 }
904
905 pub fn reload(&self, source: impl Into<ImageSource>) -> ImageVar {
907 self.image(source, ImageCacheMode::Reload, None, None, None)
908 }
909
910 pub fn image(
916 &self,
917 source: impl Into<ImageSource>,
918 cache_mode: impl Into<ImageCacheMode>,
919 limits: Option<ImageLimits>,
920 downscale: Option<ImageDownscale>,
921 mask: Option<ImageMaskMode>,
922 ) -> ImageVar {
923 let limits = limits.unwrap_or_else(|| IMAGES_SV.read().limits.get());
924 let proxies = mem::take(&mut IMAGES_SV.write().proxies);
925 ImagesService::proxy_then_get(proxies, source.into(), cache_mode.into(), limits, downscale, mask)
926 }
927
928 pub fn image_task<F>(
939 &self,
940 source: impl IntoFuture<IntoFuture = F>,
941 cache_mode: impl Into<ImageCacheMode>,
942 cache_key: Option<ImageHash>,
943 limits: Option<ImageLimits>,
944 downscale: Option<ImageDownscale>,
945 mask: Option<ImageMaskMode>,
946 ) -> ImageVar
947 where
948 F: Future<Output = ImageSource> + Send + 'static,
949 {
950 let cache_mode = cache_mode.into();
951
952 if let Some(key) = cache_key {
953 match cache_mode {
954 ImageCacheMode::Cache => {
955 if let Some(v) = IMAGES_SV.read().cache.get(&key) {
956 return v.image.read_only();
957 }
958 }
959 ImageCacheMode::Retry => {
960 if let Some(e) = IMAGES_SV.read().cache.get(&key) {
961 if !e.error.load(Ordering::Relaxed) {
962 return e.image.read_only();
963 }
964 }
965 }
966 ImageCacheMode::Ignore | ImageCacheMode::Reload => {}
967 }
968 }
969
970 let source = source.into_future();
971 let img = var(Img::new_none(cache_key));
972
973 task::spawn(async_clmv!(img, {
974 let source = source.await;
975 let actual_img = IMAGES.image(source, cache_mode, limits, downscale, mask);
976 actual_img.set_bind(&img).perm();
977 img.hold(actual_img).perm();
978 }));
979 img.read_only()
980 }
981
982 pub fn register(&self, key: ImageHash, image: ViewImage) -> std::result::Result<ImageVar, (ViewImage, ImageVar)> {
987 IMAGES_SV.write().register(key, image, None)
988 }
989
990 pub fn clean(&self, key: ImageHash) -> bool {
997 ImagesService::proxy_then_remove(mem::take(&mut IMAGES_SV.write().proxies), &key, false)
998 }
999
1000 pub fn purge(&self, key: &ImageHash) -> bool {
1007 ImagesService::proxy_then_remove(mem::take(&mut IMAGES_SV.write().proxies), key, true)
1008 }
1009
1010 pub fn cache_key(&self, image: &Img) -> Option<ImageHash> {
1012 if let Some(key) = &image.cache_key {
1013 if IMAGES_SV.read().cache.contains_key(key) {
1014 return Some(*key);
1015 }
1016 }
1017 None
1018 }
1019
1020 pub fn is_cached(&self, image: &Img) -> bool {
1022 image
1023 .cache_key
1024 .as_ref()
1025 .map(|k| IMAGES_SV.read().cache.contains_key(k))
1026 .unwrap_or(false)
1027 }
1028
1029 pub fn detach(&self, image: ImageVar) -> ImageVar {
1034 IMAGES_SV.write().detach(image)
1035 }
1036
1037 pub fn clean_all(&self) {
1039 let mut img = IMAGES_SV.write();
1040 img.proxies.iter_mut().for_each(|p| p.clear(false));
1041 img.cache.retain(|_, v| v.image.strong_count() > 1);
1042 }
1043
1044 pub fn purge_all(&self) {
1049 let mut img = IMAGES_SV.write();
1050 img.cache.clear();
1051 img.proxies.iter_mut().for_each(|p| p.clear(true));
1052 }
1053
1054 pub fn install_proxy(&self, proxy: Box<dyn ImageCacheProxy>) {
1058 IMAGES_SV.write().proxies.push(proxy);
1059 }
1060
1061 pub fn available_decoders(&self) -> Vec<Txt> {
1063 VIEW_PROCESS.image_decoders().unwrap_or_default()
1064 }
1065
1066 pub fn available_encoders(&self) -> Vec<Txt> {
1068 VIEW_PROCESS.image_encoders().unwrap_or_default()
1069 }
1070}
1071struct ImageData {
1072 format: ImageDataFormat,
1073 r: std::result::Result<IpcBytes, Txt>,
1074}
1075
1076fn absolute_path(path: &Path, base: impl FnOnce() -> PathBuf, allow_escape: bool) -> PathBuf {
1077 if path.is_absolute() {
1078 normalize_path(path)
1079 } else {
1080 let mut dir = base();
1081 if allow_escape {
1082 dir.push(path);
1083 normalize_path(&dir)
1084 } else {
1085 dir.push(normalize_path(path));
1086 dir
1087 }
1088 }
1089}
1090fn normalize_path(path: &Path) -> PathBuf {
1094 use std::path::Component;
1095
1096 let mut components = path.components().peekable();
1097 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
1098 components.next();
1099 PathBuf::from(c.as_os_str())
1100 } else {
1101 PathBuf::new()
1102 };
1103
1104 for component in components {
1105 match component {
1106 Component::Prefix(..) => unreachable!(),
1107 Component::RootDir => {
1108 ret.push(component.as_os_str());
1109 }
1110 Component::CurDir => {}
1111 Component::ParentDir => {
1112 ret.pop();
1113 }
1114 Component::Normal(c) => {
1115 ret.push(c);
1116 }
1117 }
1118 }
1119 ret
1120}