zng_ext_image/
lib.rs

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//!
4//! Image loading and cache.
5//!
6//! # Crate
7//!
8#![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/// Application extension that provides an image cache.
50///
51/// # Services
52///
53/// Services this extension provides.
54///
55/// * [`IMAGES`]
56#[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            // image finished decoding, remove from `decoding`
76            // and notify image var value update.
77            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            // image failed to decode, remove from `decoding`
92            // and notify image var value update.
93            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; // already recovered, can this happen?
137                    }
138                    if let Some(e) = view.error() {
139                        // respawned, but image was an error.
140                        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                        // respawned, but image was decoding, need to restart decode.
147                        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(_) => { /*will receive another event.*/ }
158                        }
159                        images.decoding.push(ImageDecodingTask {
160                            format: task.format.clone(),
161                            data: task.data.clone(),
162                            image: img_var,
163                        });
164                    } else {
165                        // respawned and image was loaded.
166
167                        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, // we will receive another event.
186                        };
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                    // respawned, but image had not started decoding, start it now.
198                    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(_) => { /*will receive another event.*/ }
210                    }
211                    images.decoding.push(ImageDecodingTask {
212                        format: task.format.clone(),
213                        data: task.data.clone(),
214                        image: img_var,
215                    });
216                }
217                // else { *is loading, will continue normally in self.update_preview()* }
218            }
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        // update loading tasks:
228
229        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); // proxies can use IMAGES
234
235        '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                                // success and we have a view-process.
255                                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                                        // request sent, add to `decoding` will receive
264                                        // `RawImageLoadedEvent` or `RawImageLoadErrorEvent` event
265                                        // when done.
266                                        t.image.modify(move |v| {
267                                            v.inner_set_or_replace(img, false);
268                                        });
269                                    }
270                                    Err(_) => {
271                                        // will recover in VIEW_PROCESS_INITED_EVENT
272                                    }
273                                }
274                                IMAGES_SV.write().decoding.push(ImageDecodingTask {
275                                    format: d.format,
276                                    data,
277                                    image: t.image,
278                                });
279                            } else {
280                                // success, but we are only doing `load_in_headless` validation.
281                                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                            // load error.
290                            let img = ViewImage::dummy(Some(e));
291                            t.image.modify(move |v| {
292                                v.inner_set_or_replace(img, true);
293                            });
294
295                            // flag error for user retry
296                            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                // is cached, `clean` if is only external reference.
456                if image.strong_count() == 2 {
457                    self.cache.remove(key);
458                }
459            }
460
461            // remove `cache_key` from image, this clones the `Img` only-if is still in cache.
462            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            // already not cached
474            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    /// The `fetch_bytes` future is polled in the UI thread, use `task::run` for futures that poll a lot.
808    #[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
836/// Image loading, cache and render service.
837///
838/// If the app is running without a [`VIEW_PROCESS`] all images are dummy, see [`load_in_headless`] for
839/// details.
840///
841/// # Provider
842///
843/// This service is provided by the [`ImageManager`] extension, it will panic if used in an app not extended.
844///
845/// [`load_in_headless`]: IMAGES::load_in_headless
846/// [`VIEW_PROCESS`]: zng_app::view_process::VIEW_PROCESS
847pub struct IMAGES;
848impl IMAGES {
849    /// If should still download/read image bytes in headless/renderless mode.
850    ///
851    /// When an app is in headless mode without renderer no [`VIEW_PROCESS`] is available, so
852    /// images cannot be decoded, in this case all images are the [`dummy`] image and no attempt
853    /// to download/read the image files is made. You can enable loading in headless tests to detect
854    /// IO errors, in this case if there is an error acquiring the image file the image will be a
855    /// [`dummy`] with error.
856    ///
857    /// [`dummy`]: IMAGES::dummy
858    /// [`VIEW_PROCESS`]: zng_app::view_process::VIEW_PROCESS
859    pub fn load_in_headless(&self) -> Var<bool> {
860        IMAGES_SV.read().load_in_headless.clone()
861    }
862
863    /// Default loading and decoding limits for each image.
864    pub fn limits(&self) -> Var<ImageLimits> {
865        IMAGES_SV.read().limits.clone()
866    }
867
868    /// Returns a dummy image that reports it is loaded or an error.
869    pub fn dummy(&self, error: Option<Txt>) -> ImageVar {
870        var(Img::dummy(error)).read_only()
871    }
872
873    /// Cache or load an image file from a file system `path`.
874    pub fn read(&self, path: impl Into<PathBuf>) -> ImageVar {
875        self.cache(path.into())
876    }
877
878    /// Get a cached `uri` or download it.
879    ///
880    /// Optionally define the HTTP ACCEPT header, if not set all image formats supported by the view-process
881    /// backend are accepted.
882    #[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    /// Get a cached image from `&'static [u8]` data.
891    ///
892    /// The data can be any of the formats described in [`ImageDataFormat`].
893    ///
894    /// The image key is a [`ImageHash`] of the image data.
895    ///
896    /// # Examples
897    ///
898    /// Get an image from a PNG file embedded in the app executable using [`include_bytes!`].
899    ///
900    /// ```
901    /// # use zng_ext_image::*;
902    /// # macro_rules! include_bytes { ($tt:tt) => { &[] } }
903    /// # fn demo() {
904    /// let image_var = IMAGES.from_static(include_bytes!("ico.png"), "png");
905    /// # }
906    pub fn from_static(&self, data: &'static [u8], format: impl Into<ImageDataFormat>) -> ImageVar {
907        self.cache((data, format.into()))
908    }
909
910    /// Get a cached image from shared data.
911    ///
912    /// The image key is a [`ImageHash`] of the image data. The data reference is held only until the image is decoded.
913    ///
914    /// The data can be any of the formats described in [`ImageDataFormat`].
915    pub fn from_data(&self, data: Arc<Vec<u8>>, format: impl Into<ImageDataFormat>) -> ImageVar {
916        self.cache((data, format.into()))
917    }
918
919    /// Get a cached image or add it to the cache.
920    pub fn cache(&self, source: impl Into<ImageSource>) -> ImageVar {
921        self.image(source, ImageCacheMode::Cache, None, None, None)
922    }
923
924    /// Get a cached image or add it to the cache or retry if the cached image is an error.
925    pub fn retry(&self, source: impl Into<ImageSource>) -> ImageVar {
926        self.image(source, ImageCacheMode::Retry, None, None, None)
927    }
928
929    /// Load an image, if it was already cached update the cached image with the reloaded data.
930    pub fn reload(&self, source: impl Into<ImageSource>) -> ImageVar {
931        self.image(source, ImageCacheMode::Reload, None, None, None)
932    }
933
934    /// Get or load an image.
935    ///
936    /// If `limits` is `None` the [`IMAGES.limits`] is used.
937    ///
938    /// [`IMAGES.limits`]: IMAGES::limits
939    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    /// Await for an image source, then get or load the image.
953    ///
954    /// If `limits` is `None` the [`IMAGES.limits`] is used.
955    ///
956    /// This method returns immediately with a loading [`ImageVar`], when `source` is ready it
957    /// is used to get the actual [`ImageVar`] and binds it to the returned image.
958    ///
959    /// Note that the `cache_mode` always applies to the inner image, and only to the return image if `cache_key` is set.
960    ///
961    /// [`IMAGES.limits`]: IMAGES::limits
962    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    /// Associate the `image` with the `key` in the cache.
1007    ///
1008    /// Returns `Ok(ImageVar)` with the new image var that tracks `image`, or `Err(ViewImage, ImageVar)`
1009    /// that returns the `image` and a clone of the var already associated with the `key`.
1010    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    /// Remove the image from the cache, if it is only held by the cache.
1015    ///
1016    /// You can use [`ImageSource::hash128_read`] and [`ImageSource::hash128_download`] to get the `key`
1017    /// for files or downloads.
1018    ///
1019    /// Returns `true` if the image was removed.
1020    pub fn clean(&self, key: ImageHash) -> bool {
1021        ImagesService::proxy_then_remove(mem::take(&mut IMAGES_SV.write().proxies), &key, false)
1022    }
1023
1024    /// Remove the image from the cache, even if it is still referenced outside of the cache.
1025    ///
1026    /// You can use [`ImageSource::hash128_read`] and [`ImageSource::hash128_download`] to get the `key`
1027    /// for files or downloads.
1028    ///
1029    /// Returns `true` if the image was cached.
1030    pub fn purge(&self, key: &ImageHash) -> bool {
1031        ImagesService::proxy_then_remove(mem::take(&mut IMAGES_SV.write().proxies), key, true)
1032    }
1033
1034    /// Gets the cache key of an image.
1035    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    /// If the image is cached.
1045    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    /// Returns an image that is not cached.
1054    ///
1055    /// If the `image` is the only reference returns it and removes it from the cache. If there are other
1056    /// references a new [`ImageVar`] is generated from a clone of the image.
1057    pub fn detach(&self, image: ImageVar) -> ImageVar {
1058        IMAGES_SV.write().detach(image)
1059    }
1060
1061    /// Clear cached images that are not referenced outside of the cache.
1062    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    /// Clear all cached images, including images that are still referenced outside of the cache.
1069    ///
1070    /// Image memory only drops when all strong references are removed, so if an image is referenced
1071    /// outside of the cache it will merely be disconnected from the cache by this method.
1072    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    /// Add a cache proxy.
1079    ///
1080    /// Proxies can intercept cache requests and map to a different request or return an image directly.
1081    pub fn install_proxy(&self, proxy: Box<dyn ImageCacheProxy>) {
1082        IMAGES_SV.write().proxies.push(proxy);
1083    }
1084
1085    /// Image format decoders implemented by the current view-process.
1086    ///
1087    /// Each text is the lower-case file extension, without the dot.
1088    pub fn available_decoders(&self) -> Vec<Txt> {
1089        VIEW_PROCESS.image_decoders().unwrap_or_default()
1090    }
1091
1092    /// Image format encoders implemented by the current view-process.
1093    ///
1094    /// Each text is the lower-case file extension, without the dot.
1095    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}
1118/// Resolves `..` components, without any system request.
1119///
1120/// Source: https://github.com/rust-lang/cargo/blob/fede83ccf973457de319ba6fa0e36ead454d2e20/src/cargo/util/paths.rs#L61
1121fn 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}