zng_ext_image/
types.rs

1use std::{
2    any::Any,
3    env, fmt, fs, io, mem, ops,
4    path::{Path, PathBuf},
5    sync::Arc,
6};
7
8use parking_lot::Mutex;
9use zng_app::{
10    view_process::{EncodeError, VIEW_PROCESS, ViewImageHandle, ViewRenderer},
11    widget::node::UiNode,
12    window::WindowId,
13};
14use zng_color::{
15    Hsla, Rgba,
16    gradient::{ExtendMode, GradientStops},
17};
18use zng_layout::{
19    context::{LAYOUT, LayoutMetrics, LayoutPassId},
20    unit::{ByteLength, ByteUnits, FactorUnits as _, LayoutAxis, Px, PxDensity2d, PxLine, PxPoint, PxRect, PxSize, about_eq},
21};
22use zng_task::{
23    self as task,
24    channel::{IpcBytes, IpcBytesMut},
25};
26use zng_txt::Txt;
27use zng_var::{Var, VarEq, animation::Transitionable, impl_from_and_into_var};
28use zng_view_api::{
29    image::{ImageDecoded, ImageEncodeRequest, ImageEntryMetadata, ImageTextureId},
30    window::RenderMode,
31};
32
33use crate::{IMAGES_SV, ImageRenderWindowRoot};
34
35pub use zng_view_api::image::{
36    ColorType, ImageDataFormat, ImageDownscaleMode, ImageEntriesMode, ImageEntryKind, ImageFormat, ImageFormatCapability, ImageMaskMode,
37    PartialImageKind,
38};
39
40/// A custom extension for the [`IMAGES`] service.
41///
42/// Extensions can intercept and modify requests.
43///
44/// [`IMAGES`]: crate::IMAGES
45pub trait ImagesExtension: Send + Sync + Any {
46    /// Modify a [`IMAGES.image`] request.
47    ///
48    /// Note that all other request methods are shorthand helpers so this will be called for every request.
49    ///
50    /// Note that the [`IMAGES`] service can be used in extensions and [`ImageSource::Image`] is returned directly by the service.
51    /// This can be used to fully replace a request here.
52    ///
53    /// [`IMAGES.image`]: crate::IMAGES::image
54    /// [`IMAGES`]: crate::IMAGES
55    fn image(&mut self, limits: &ImageLimits, source: &mut ImageSource, options: &mut ImageOptions) {
56        let _ = (limits, source, options);
57    }
58
59    /// Image data loaded.
60    ///
61    /// This is called for [`ImageSource::Read`], [`ImageSource::Download`] and [`ImageSource::Data`] after the data is loaded and before
62    /// decoding starts.
63    ///
64    /// Return a replacement variable to skip decoding or redirect to a different image. Note that by the time this is called the service
65    /// has already returned a variable in loading state, that variable will be cached according to `mode`. The replacement variable
66    /// is bound to the return variable and lives as long as it does.
67    ///
68    /// Note that the [`IMAGES`] service can be used in extensions.
69    ///
70    /// [`IMAGES`]: crate::IMAGES
71    #[allow(clippy::too_many_arguments)]
72    fn image_data(
73        &mut self,
74        max_decoded_len: ByteLength,
75        key: &ImageHash,
76        data: &IpcBytes,
77        format: &ImageDataFormat,
78        options: &ImageOptions,
79    ) -> Option<ImageVar> {
80        let _ = (max_decoded_len, key, data, format, options);
81        None
82    }
83
84    /// Modify a [`IMAGES.clean`] or [`IMAGES.purge`] request.
85    ///
86    /// Return `false` to cancel the removal.
87    ///
88    /// [`IMAGES.clean`]: crate::IMAGES::clean
89    /// [`IMAGES.purge`]: crate::IMAGES::purge
90    fn remove(&mut self, key: &mut ImageHash, purge: &mut bool) -> bool {
91        let _ = (key, purge);
92        true
93    }
94
95    /// Called on [`IMAGES.clean_all`] and [`IMAGES.purge_all`].
96    ///
97    /// These operations cannot be intercepted, the service cache will be cleaned after this call.
98    ///
99    /// [`IMAGES.clean_all`]: crate::IMAGES::clean_all
100    /// [`IMAGES.purge_all`]: crate::IMAGES::purge_all
101    fn clear(&mut self, purge: bool) {
102        let _ = purge;
103    }
104
105    /// Add or remove formats this extension affects.
106    ///
107    /// The `formats` value starts with all formats implemented by the current view-process and will be returned
108    /// by [`IMAGES.available_formats`] after all proxies edit it.
109    ///
110    /// [`IMAGES.available_formats`]: crate::IMAGES::available_formats
111    fn available_formats(&self, formats: &mut Vec<ImageFormat>) {
112        let _ = formats;
113    }
114}
115
116/// Represents an [`ImageEntry`] tracked by the [`IMAGES`] cache.
117///
118/// The variable updates when the image updates.
119///
120/// [`IMAGES`]: super::IMAGES
121pub type ImageVar = Var<ImageEntry>;
122
123#[derive(Default, Debug)]
124struct ImgMut {
125    render_ids: Vec<RenderImage>,
126}
127
128/// State of an [`ImageVar`].
129///
130/// [`IMAGES`]: crate::IMAGES
131#[derive(Debug, Clone)]
132pub struct ImageEntry {
133    pub(crate) cache_key: Option<ImageHash>,
134
135    pub(crate) handle: ViewImageHandle,
136    pub(crate) data: ImageDecoded,
137    entries: Vec<VarEq<ImageEntry>>,
138
139    error: Txt,
140
141    img_mut: Arc<Mutex<ImgMut>>,
142}
143impl PartialEq for ImageEntry {
144    fn eq(&self, other: &Self) -> bool {
145        self.handle == other.handle
146            && self.cache_key == other.cache_key
147            && self.error == other.error
148            && self.data == other.data
149            && self.entries == other.entries
150    }
151}
152impl ImageEntry {
153    /// Create a dummy image in the loading state.
154    ///
155    /// This is the same as calling [`new_error`] with an empty error.
156    ///
157    /// [`new_error`]: Self::new_error
158    pub fn new_loading() -> Self {
159        Self::new_error(Txt::from_static(""))
160    }
161
162    /// Create a dummy image in the error state.
163    ///
164    /// If the `error` is empty the image is *loading*, not an error.
165    pub fn new_error(error: Txt) -> Self {
166        let mut s = Self::new(None, ViewImageHandle::dummy(), ImageDecoded::default());
167        s.error = error;
168        s
169    }
170
171    pub(crate) fn new(cache_key: Option<ImageHash>, handle: ViewImageHandle, data: ImageDecoded) -> Self {
172        Self {
173            cache_key,
174            handle,
175            data,
176            entries: vec![],
177            error: Txt::from_static(""),
178            img_mut: Arc::default(),
179        }
180    }
181
182    /// Returns `true` if the is still acquiring or decoding the image bytes.
183    pub fn is_loading(&self) -> bool {
184        self.error.is_empty() && (self.data.pixels.is_empty() || self.data.partial.is_some())
185    }
186
187    /// If the image has finished loading ok or due to error.
188    ///
189    /// The image variable may still update after
190    pub fn is_loaded(&self) -> bool {
191        !self.is_loading()
192    }
193
194    /// If the image failed to load.
195    pub fn is_error(&self) -> bool {
196        !self.error.is_empty()
197    }
198
199    /// Returns an error message if the image failed to load.
200    pub fn error(&self) -> Option<Txt> {
201        if self.error.is_empty() { None } else { Some(self.error.clone()) }
202    }
203
204    /// Pixel size of the image after it finishes loading.
205    ///
206    /// Note that this value is set as soon as the header finishes decoding, but the [`pixels`] will
207    /// only be set after it the entire image decodes.
208    ///
209    /// If the view-process implements progressive decoding you can use [`partial_size`] and [`partial_pixels`]
210    /// to use the partially decoded image top rows as it decodes.
211    ///
212    /// [`pixels`]: Self::pixels
213    /// [`partial_size`]: Self::partial_size
214    /// [`partial_pixels`]: Self::partial_pixels
215    pub fn size(&self) -> PxSize {
216        self.data.meta.size
217    }
218
219    /// Size of [`partial_pixels`].
220    ///
221    /// Can be different from [`size`] if the image is progressively decoding.
222    ///
223    /// [`size`]: Self::size
224    /// [`partial_pixels`]: Self::partial_pixels
225    pub fn partial_size(&self) -> Option<PxSize> {
226        match self.data.partial.as_ref()? {
227            PartialImageKind::Placeholder { pixel_size } => Some(*pixel_size),
228            PartialImageKind::Rows { height, .. } => Some(PxSize::new(self.data.meta.size.width, *height)),
229            _ => None,
230        }
231    }
232
233    /// Kind of [`partial_pixels`].
234    ///
235    /// [`partial_pixels`]: Self::partial_pixels
236    pub fn partial_kind(&self) -> Option<PartialImageKind> {
237        self.data.partial.clone()
238    }
239
240    /// Returns the image pixel density metadata if the image is loaded and the
241    /// metadata was retrieved.
242    pub fn density(&self) -> Option<PxDensity2d> {
243        self.data.meta.density
244    }
245
246    /// Image color type before it was converted to BGRA8 or A8.
247    pub fn original_color_type(&self) -> ColorType {
248        self.data.meta.original_color_type.clone()
249    }
250
251    /// Gets A8 for masks and BGRA8 for others.
252    pub fn color_type(&self) -> ColorType {
253        if self.is_mask() { ColorType::A8 } else { ColorType::BGRA8 }
254    }
255
256    /// Returns `true` if the image is fully opaque or it is not loaded.
257    pub fn is_opaque(&self) -> bool {
258        self.data.is_opaque
259    }
260
261    /// Returns `true` if the image pixels are a single channel (A8).
262    pub fn is_mask(&self) -> bool {
263        self.data.meta.is_mask
264    }
265
266    /// If [`entries`] is not empty.
267    ///
268    /// [`entries`]: Self::entries
269    pub fn has_entries(&self) -> bool {
270        !self.entries.is_empty()
271    }
272
273    /// Other images from the same container that are a *child* of this image.
274    pub fn entries(&self) -> Vec<ImageVar> {
275        self.entries.iter().map(|e| e.read_only()).collect()
276    }
277
278    /// All other images from the same container that are a *descendant* of this image.
279    ///
280    /// The values are a tuple of each entry and the length of descendants entries that follow it.
281    ///
282    /// The returned variable will update every time any entry descendant var updates.
283    ///
284    /// [`entries`]: Self::entries
285    pub fn flat_entries(&self) -> Var<Vec<(VarEq<ImageEntry>, usize)>> {
286        // idea here is to just rebuild the flat list on any update,
287        // assuming the image variables don't update much and tha there are not many entries
288        // this is more simple than some sort of recursive Var::flat_map_vec setup
289
290        // each entry updates this var on update
291        let update_signal = zng_var::var(());
292
293        // init value and update bindings
294        let mut out = vec![];
295        let mut update_handles = vec![];
296        self.flat_entries_init(&mut out, update_signal.clone(), &mut update_handles);
297        let out = zng_var::var(out);
298
299        // bind signal to rebuild list on update and rebind update signal
300        let self_ = self.clone();
301        let signal_weak = update_signal.downgrade();
302        update_signal
303            .bind_modify(&out, move |_, out| {
304                out.clear();
305                update_handles.clear();
306                self_.flat_entries_init(&mut *out, signal_weak.upgrade().unwrap(), &mut update_handles);
307            })
308            .perm();
309        out.hold(update_signal).perm();
310        out.read_only()
311    }
312    fn flat_entries_init(&self, out: &mut Vec<(VarEq<ImageEntry>, usize)>, update_signal: Var<()>, handles: &mut Vec<zng_var::VarHandle>) {
313        for entry in self.entries.iter() {
314            Self::flat_entries_recursive_init(entry.clone(), out, update_signal.clone(), handles);
315        }
316    }
317    fn flat_entries_recursive_init(
318        img: VarEq<ImageEntry>,
319        out: &mut Vec<(VarEq<ImageEntry>, usize)>,
320        signal: Var<()>,
321        handles: &mut Vec<zng_var::VarHandle>,
322    ) {
323        handles.push(img.hook(zng_clone_move::clmv!(signal, |_| {
324            signal.update();
325            true
326        })));
327        let i = out.len();
328        out.push((img.clone(), 0));
329        img.with(move |img| {
330            for entry in img.entries.iter() {
331                Self::flat_entries_recursive_init(entry.clone(), out, signal.clone(), handles);
332            }
333            let len = out.len() - i;
334            out[i].1 = len;
335        });
336    }
337
338    /// Kind of image entry this image is in the source container.
339    pub fn entry_kind(&self) -> ImageEntryKind {
340        match &self.data.meta.parent {
341            Some(p) => p.kind.clone(),
342            None => ImageEntryKind::Page,
343        }
344    }
345
346    /// Sort index of the image in the list of entries of the source container.
347    pub fn entry_index(&self) -> usize {
348        match &self.data.meta.parent {
349            Some(p) => p.index,
350            None => 0,
351        }
352    }
353
354    /// Calls `visit` with the image or [`ImageEntryKind::Reduced`] entry that is nearest to `size` and greater or equal to it.
355    ///
356    /// Does not call `visit` if none of the images are loaded, returns `None` in that case.
357    pub fn with_best_reduce<R>(&self, size: PxSize, visit: impl FnOnce(&ImageEntry) -> R) -> Option<R> {
358        fn cmp(target_size: PxSize, a: PxSize, b: PxSize) -> std::cmp::Ordering {
359            let target_ratio = target_size.width.0 as f32 / target_size.height.0 as f32;
360            let a_ratio = a.width.0 as f32 / b.height.0 as f32;
361            let b_ratio = b.width.0 as f32 / b.height.0 as f32;
362
363            let a_distortion = (target_ratio - a_ratio).abs();
364            let b_distortion = (target_ratio - b_ratio).abs();
365
366            if !about_eq(a_distortion, b_distortion, 0.01) && a_distortion < b_distortion {
367                // prefer a, has less distortion
368                return std::cmp::Ordering::Less;
369            }
370
371            let a_dist = a - target_size;
372            let b_dist = b - target_size;
373
374            if a_dist.width < Px(0) || a_dist.height < Px(0) {
375                if b_dist.width < Px(0) || b_dist.height < Px(0) {
376                    // a and b need upscaling, prefer near target_size
377                    a_dist.width.abs().cmp(&b_dist.width.abs())
378                } else {
379                    // prefer b, a needs upscaling
380                    std::cmp::Ordering::Greater
381                }
382            } else if b_dist.width < Px(0) || b_dist.height < Px(0) {
383                // prefer a, b needs upscaling
384                std::cmp::Ordering::Less
385            } else {
386                // a and b need downscaling, prefer near target_size
387                a_dist.width.cmp(&b_dist.width)
388            }
389        }
390
391        let mut best_i = usize::MAX;
392        let mut best_size = PxSize::zero();
393
394        if self.is_loaded() {
395            best_i = self.entries.len();
396            best_size = self.size();
397        }
398
399        for (i, entry) in self.entries.iter().enumerate() {
400            entry.with(|e| {
401                if e.is_loaded() {
402                    let entry_size = e.size();
403                    if cmp(size, entry_size, best_size).is_lt() {
404                        best_i = i;
405                        best_size = entry_size;
406                    }
407                }
408            })
409        }
410
411        if best_i == usize::MAX {
412            // image and all reduced are smaller than `size`, return the largest to reduce upscaling
413            None
414        } else if best_i == self.entries.len() {
415            Some(visit(self))
416        } else {
417            Some(self.entries[best_i].with(visit))
418        }
419    }
420
421    /// Connection to the image resource in the view-process.
422    pub fn view_handle(&self) -> &ViewImageHandle {
423        &self.handle
424    }
425
426    /// Calculate an *ideal* layout size for the image.
427    ///
428    /// The image is scaled considering the [`density`] and screen scale factor. If the
429    /// image has no [`density`] falls back to the [`screen_density`] in both dimensions.
430    ///
431    /// [`density`]: Self::density
432    /// [`screen_density`]: LayoutMetrics::screen_density
433    pub fn layout_size(&self, ctx: &LayoutMetrics) -> PxSize {
434        self.calc_size(ctx, PxDensity2d::splat(ctx.screen_density()), false)
435    }
436
437    /// Calculate a layout size for the image.
438    ///
439    /// # Parameters
440    ///
441    /// * `ctx`: Used to get the screen resolution.
442    /// * `fallback_density`: Resolution used if [`density`] is `None`.
443    /// * `ignore_image_density`: If `true` always uses the `fallback_density` as the resolution.
444    ///
445    /// [`density`]: Self::density
446    pub fn calc_size(&self, ctx: &LayoutMetrics, fallback_density: PxDensity2d, ignore_image_density: bool) -> PxSize {
447        let dpi = if ignore_image_density {
448            fallback_density
449        } else {
450            self.density().unwrap_or(fallback_density)
451        };
452
453        let s_density = ctx.screen_density();
454        let mut size = self.size();
455
456        let fct = ctx.scale_factor().0;
457        size.width *= (s_density.ppcm() / dpi.width.ppcm()) * fct;
458        size.height *= (s_density.ppcm() / dpi.height.ppcm()) * fct;
459
460        size
461    }
462
463    /// Reference the decoded pre-multiplied BGRA8 pixel buffer or A8 if [`is_mask`].
464    ///
465    /// [`is_mask`]: Self::is_mask
466    pub fn pixels(&self) -> Option<IpcBytes> {
467        if self.is_loaded() { Some(self.data.pixels.clone()) } else { None }
468    }
469
470    /// Reference the partially decoded pixels if the image is progressively decoding
471    /// and has not finished decoding.
472    ///
473    /// Format is BGRA8 for normal images or A8 if [`is_mask`].
474    ///
475    /// [`is_mask`]: Self::is_mask
476    pub fn partial_pixels(&self) -> Option<IpcBytes> {
477        if self.is_loading() && self.data.partial.is_some() {
478            Some(self.data.pixels.clone())
479        } else {
480            None
481        }
482    }
483
484    fn actual_pixels_and_size(&self) -> Option<(PxSize, IpcBytes)> {
485        match (self.partial_pixels(), self.partial_size()) {
486            (Some(b), Some(s)) => Some((s, b)),
487            _ => Some((self.size(), self.pixels()?)),
488        }
489    }
490
491    /// Copy the `rect` selection from `pixels` or `partial_pixels`.
492    ///
493    /// The `rect` is in pixels, with the origin (0, 0) at the top-left of the image.
494    ///
495    /// Returns the copied selection and the pixel buffer.
496    ///
497    /// Note that the selection can change if `rect` is not fully contained by the image area.
498    pub fn copy_pixels(&self, rect: PxRect) -> Option<(PxRect, IpcBytesMut)> {
499        self.actual_pixels_and_size().and_then(|(size, pixels)| {
500            let area = PxRect::from_size(size).intersection(&rect).unwrap_or_default();
501            if area.size.width.0 == 0 || area.size.height.0 == 0 {
502                Some((area, IpcBytes::new_mut_blocking(0).unwrap()))
503            } else {
504                let x = area.origin.x.0 as usize;
505                let y = area.origin.y.0 as usize;
506                let width = area.size.width.0 as usize;
507                let height = area.size.height.0 as usize;
508                let pixel = if self.is_mask() { 1 } else { 4 };
509                let mut bytes = IpcBytes::new_mut_blocking(width * height * pixel).ok()?;
510                let mut write = &mut bytes[..];
511                let row_stride = self.size().width.0 as usize * pixel;
512                for l in y..y + height {
513                    let line_start = l * row_stride + x * pixel;
514                    let line_end = line_start + width * pixel;
515                    let line = &pixels[line_start..line_end];
516                    write[..line.len()].copy_from_slice(line);
517                    write = &mut write[line.len()..];
518                }
519                Some((area, bytes))
520            }
521        })
522    }
523
524    /// Encode the image to the format.
525    ///
526    /// Note that [`entries`] are ignored, only this image is encoded. Use [`encode_with_entries`] to encode
527    /// multiple images in the same container.
528    ///
529    /// [`entries`]: Self::entries
530    /// [`encode_with_entries`]: Self::encode_with_entries
531    pub async fn encode(&self, format: Txt) -> std::result::Result<IpcBytes, EncodeError> {
532        self.encode_with_entries(&[], format).await
533    }
534
535    /// Encode the images to the format.
536    ///
537    /// This image is the first *page* followed by the `entries` in the given order.
538    pub async fn encode_with_entries(
539        &self,
540        entries: &[(ImageEntry, ImageEntryKind)],
541        format: Txt,
542    ) -> std::result::Result<IpcBytes, EncodeError> {
543        if self.is_loading() {
544            return Err(EncodeError::Loading);
545        } else if let Some(e) = self.error() {
546            return Err(e.into());
547        } else if self.handle.is_dummy() {
548            return Err(EncodeError::Dummy);
549        }
550
551        let mut r = ImageEncodeRequest::new(self.handle.image_id(), format);
552        r.entries = entries.iter().map(|(img, kind)| (img.handle.image_id(), kind.clone())).collect();
553
554        match VIEW_PROCESS.encode_image(r).recv().await {
555            Ok(r) => r,
556            Err(_) => Err(EncodeError::Disconnected),
557        }
558    }
559
560    /// Encode and write the image to `path`.
561    ///
562    /// The image format is guessed from the file extension. Use [`save_with_format`] to specify the format.
563    ///
564    /// Note that [`entries`] are ignored, only this image is encoded. Use [`save_with_entries`] to encode
565    /// multiple images in the same container.
566    ///
567    /// [`entries`]: Self::entries
568    /// [`save_with_format`]: Self::save_with_format
569    /// [`save_with_entries`]: Self::save_with_entries
570    pub async fn save(&self, path: impl Into<PathBuf>) -> io::Result<()> {
571        let path = path.into();
572        if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
573            self.save_impl(&[], Txt::from_str(ext), path).await
574        } else {
575            Err(io::Error::new(
576                io::ErrorKind::InvalidInput,
577                "could not determinate image format from path extension",
578            ))
579        }
580    }
581
582    /// Encode and write the image to `path`.
583    ///
584    /// The image is encoded to the `format`, the file extension can be anything.
585    ///
586    /// Note that [`entries`] are ignored, only this image is encoded. Use [`save_with_entries`] to encode
587    /// multiple images in the same container.
588    ///
589    /// [`entries`]: Self::entries
590    /// [`save_with_entries`]: Self::save_with_entries
591    pub async fn save_with_format(&self, format: impl Into<Txt>, path: impl Into<PathBuf>) -> io::Result<()> {
592        self.save_impl(&[], format.into(), path.into()).await
593    }
594
595    /// Encode and write the image to `path`.
596    ///
597    /// The image is encoded to the `format`, the file extension can be anything.
598    ///
599    /// This image is the first *page* followed by the `entries` in the given order.
600    pub async fn save_with_entries(
601        &self,
602        entries: &[(ImageEntry, ImageEntryKind)],
603        format: impl Into<Txt>,
604        path: impl Into<PathBuf>,
605    ) -> io::Result<()> {
606        self.save_impl(entries, format.into(), path.into()).await
607    }
608
609    async fn save_impl(&self, entries: &[(ImageEntry, ImageEntryKind)], format: Txt, path: PathBuf) -> io::Result<()> {
610        let data = self.encode_with_entries(entries, format).await.map_err(io::Error::other)?;
611        task::wait(move || fs::write(path, &data[..])).await
612    }
613
614    /// Insert `entry` in [`entries`].
615    ///
616    /// # Parent Metadata
617    ///
618    /// If the `entry` view-process metadata has another image as parent the metadata is replaced
619    /// and a warning is logged.
620    ///
621    /// [`entries`]: Self::entries
622    /// [`IMAGES.register`]: crate::IMAGES::register
623    pub fn insert_entry(&mut self, entry: ImageVar) {
624        let id = self.handle.image_id();
625        let (i, p) = entry.with(|i| (i.entry_index(), i.data.meta.parent.clone()));
626        let i = self
627            .entries
628            .iter()
629            .position(|v| {
630                let entry_i = v.with(|i| i.entry_index());
631                entry_i > i
632            })
633            .unwrap_or(self.entries.len());
634
635        if let Some(p) = &p {
636            if p.parent != id {
637                tracing::warn!("replacing entry parent from {:?} tp {:?}", p.parent, id);
638                entry.modify(move |e| {
639                    if let Some(p) = &mut e.data.meta.parent {
640                        p.parent = id;
641                    } else {
642                        e.data.meta.parent = Some(ImageEntryMetadata::new(id, i, ImageEntryKind::Page));
643                    }
644                });
645            }
646        } else {
647            entry.modify(move |e| e.data.meta.parent = Some(ImageEntryMetadata::new(id, i, ImageEntryKind::Page)));
648        }
649
650        self.entries.insert(i, VarEq(entry));
651    }
652}
653impl zng_app::render::Img for ImageEntry {
654    fn renderer_id(&self, renderer: &ViewRenderer) -> ImageTextureId {
655        if self.is_loaded() {
656            let mut img = self.img_mut.lock();
657            let rms = &mut img.render_ids;
658            if let Some(rm) = rms.iter().find(|k| &k.renderer == renderer) {
659                return rm.image_id;
660            }
661
662            let key = match renderer.use_image(&self.handle) {
663                Ok(k) => {
664                    if k == ImageTextureId::INVALID {
665                        tracing::error!("received INVALID from `use_image`");
666                        return k;
667                    }
668                    k
669                }
670                Err(_) => {
671                    tracing::debug!("respawned `add_image`, will return INVALID");
672                    return ImageTextureId::INVALID;
673                }
674            };
675
676            rms.push(RenderImage {
677                image_id: key,
678                renderer: renderer.clone(),
679            });
680            key
681        } else {
682            ImageTextureId::INVALID
683        }
684    }
685
686    fn size(&self) -> PxSize {
687        self.size()
688    }
689}
690
691struct RenderImage {
692    image_id: ImageTextureId,
693    renderer: ViewRenderer,
694}
695impl Drop for RenderImage {
696    fn drop(&mut self) {
697        // error here means the entire renderer was dropped.
698        let _ = self.renderer.delete_image_use(self.image_id);
699    }
700}
701impl fmt::Debug for RenderImage {
702    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
703        fmt::Debug::fmt(&self.image_id, f)
704    }
705}
706
707/// A 256-bit hash for image entries.
708///
709/// This hash is used to identify image files in the [`IMAGES`] cache.
710///
711/// Use [`ImageHasher`] to compute.
712///
713/// [`IMAGES`]: super::IMAGES
714#[derive(Clone, Copy)]
715pub struct ImageHash([u8; 32]);
716impl ImageHash {
717    /// Compute the hash for `data`.
718    pub fn compute(data: &[u8]) -> Self {
719        let mut h = Self::hasher();
720        h.update(data);
721        h.finish()
722    }
723
724    /// Start a new [`ImageHasher`].
725    pub fn hasher() -> ImageHasher {
726        ImageHasher::default()
727    }
728}
729impl fmt::Debug for ImageHash {
730    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
731        if f.alternate() {
732            f.debug_tuple("ImageHash").field(&self.0).finish()
733        } else {
734            use base64::*;
735            write!(f, "{}", base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(self.0))
736        }
737    }
738}
739impl fmt::Display for ImageHash {
740    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
741        write!(f, "{self:?}")
742    }
743}
744impl std::hash::Hash for ImageHash {
745    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
746        let h64 = [
747            self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], self.0[6], self.0[7],
748        ];
749        state.write_u64(u64::from_ne_bytes(h64))
750    }
751}
752impl PartialEq for ImageHash {
753    fn eq(&self, other: &Self) -> bool {
754        self.0 == other.0
755    }
756}
757impl Eq for ImageHash {}
758
759/// Hasher that computes a [`ImageHash`].
760pub struct ImageHasher(sha2::Sha512_256);
761impl Default for ImageHasher {
762    fn default() -> Self {
763        use sha2::Digest;
764        Self(sha2::Sha512_256::new())
765    }
766}
767impl ImageHasher {
768    /// New default hasher.
769    pub fn new() -> Self {
770        Self::default()
771    }
772
773    /// Process data, updating the internal state.
774    pub fn update(&mut self, data: &[u8]) {
775        use sha2::Digest;
776
777        // some gigantic images can take to long to hash, we just
778        // need the hash for identification so we sample the data
779        const NUM_SAMPLES: usize = 1000;
780        const SAMPLE_CHUNK_SIZE: usize = 1024;
781
782        let total_size = data.len();
783        if total_size == 0 {
784            return;
785        }
786        if total_size < 1000 * 1000 * 4 {
787            return self.0.update(data);
788        }
789
790        let step_size = total_size.checked_div(NUM_SAMPLES).unwrap_or(total_size);
791        for n in 0..NUM_SAMPLES {
792            let start_index = n * step_size;
793            if start_index >= total_size {
794                break;
795            }
796            let end_index = (start_index + SAMPLE_CHUNK_SIZE).min(total_size);
797            let s = &data[start_index..end_index];
798            self.0.update(s);
799        }
800    }
801
802    /// Finish computing the hash.
803    pub fn finish(self) -> ImageHash {
804        use sha2::Digest;
805        // dependencies `sha2 -> digest` need to upgrade
806        // https://github.com/RustCrypto/traits/issues/2036
807        // https://github.com/fizyk20/generic-array/issues/158
808        ImageHash(self.0.finalize().as_slice().try_into().unwrap())
809    }
810}
811impl std::hash::Hasher for ImageHasher {
812    fn finish(&self) -> u64 {
813        tracing::warn!("Hasher::finish called for ImageHasher");
814
815        use sha2::Digest;
816        let hash = self.0.clone().finalize();
817        u64::from_le_bytes(hash[..8].try_into().unwrap())
818    }
819
820    fn write(&mut self, bytes: &[u8]) {
821        self.update(bytes);
822    }
823}
824
825// We don't use Arc<dyn ..> because of this issue: https://github.com/rust-lang/rust/issues/69757
826pub(crate) type RenderFn = Arc<Box<dyn Fn(&ImageRenderArgs) -> Box<dyn ImageRenderWindowRoot> + Send + Sync>>;
827
828/// Arguments for the [`ImageSource::Render`] closure.
829///
830/// The arguments are set by the widget that will render the image.
831#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
832#[non_exhaustive]
833pub struct ImageRenderArgs {
834    /// Window that will render the image.
835    pub parent: Option<WindowId>,
836}
837impl ImageRenderArgs {
838    /// New with parent window.
839    pub fn new(parent: WindowId) -> Self {
840        Self { parent: Some(parent) }
841    }
842}
843
844/// The different sources of an image resource.
845#[derive(Clone)]
846#[non_exhaustive]
847pub enum ImageSource {
848    /// A path to an image file in the file system.
849    ///
850    /// Image equality is defined by the path, a copy of the image in another path is a different image.
851    Read(PathBuf),
852    /// A uri to an image resource downloaded using HTTP GET with an optional HTTP ACCEPT string.
853    ///
854    /// If the ACCEPT line is not given, all image formats supported by the view-process backend are accepted.
855    ///
856    /// Image equality is defined by the URI and ACCEPT string.
857    #[cfg(feature = "http")]
858    Download(zng_task::http::Uri, Option<Txt>),
859    /// Shared reference to bytes for an encoded or decoded image.
860    ///
861    /// Image equality is defined by the hash, it is usually the hash of the bytes but it does not need to be.
862    ///
863    /// Inside [`IMAGES`] the reference to the bytes is held only until the image finishes decoding.
864    ///
865    /// [`IMAGES`]: super::IMAGES
866    Data(ImageHash, IpcBytes, ImageDataFormat),
867
868    /// A boxed closure that instantiates a `WindowRoot` that draws the image.
869    ///
870    /// Use the [`render`](Self::render) or [`render_node`](Self::render_node) functions to construct this variant.
871    ///
872    /// The closure is set by the image widget user, the args is set by the image widget.
873    Render(RenderFn, Option<ImageRenderArgs>),
874
875    /// Already resolved (loaded or loading) image.
876    ///
877    /// The image is passed-through, not cached.
878    Image(ImageVar),
879}
880impl ImageSource {
881    /// New source from data.
882    pub fn from_data(data: IpcBytes, format: ImageDataFormat) -> Self {
883        let mut hasher = ImageHasher::default();
884        hasher.update(&data[..]);
885        let hash = hasher.finish();
886        Self::Data(hash, data, format)
887    }
888
889    /// Returns the image hash, unless the source is [`ImageEntry`].
890    pub fn hash128(&self, options: &ImageOptions) -> Option<ImageHash> {
891        match self {
892            ImageSource::Read(p) => Some(Self::hash128_read(p, options)),
893            #[cfg(feature = "http")]
894            ImageSource::Download(u, a) => Some(Self::hash128_download(u, a, options)),
895            ImageSource::Data(h, _, _) => Some(Self::hash128_data(*h, options)),
896            ImageSource::Render(rfn, args) => Some(Self::hash128_render(rfn, args, options)),
897            ImageSource::Image(_) => None,
898        }
899    }
900
901    /// Compute hash for a borrowed [`Data`] image.
902    ///
903    /// [`Data`]: Self::Data
904    pub fn hash128_data(data_hash: ImageHash, options: &ImageOptions) -> ImageHash {
905        if options.downscale.is_some() || options.mask.is_some() || !options.entries.is_empty() {
906            use std::hash::Hash;
907            let mut h = ImageHash::hasher();
908            data_hash.0.hash(&mut h);
909            options.downscale.hash(&mut h);
910            options.mask.hash(&mut h);
911            options.entries.hash(&mut h);
912            h.finish()
913        } else {
914            data_hash
915        }
916    }
917
918    /// Compute hash for a borrowed [`Read`] path.
919    ///
920    /// [`Read`]: Self::Read
921    pub fn hash128_read(path: &Path, options: &ImageOptions) -> ImageHash {
922        use std::hash::Hash;
923        let mut h = ImageHash::hasher();
924        0u8.hash(&mut h);
925        path.hash(&mut h);
926        options.downscale.hash(&mut h);
927        options.mask.hash(&mut h);
928        options.entries.hash(&mut h);
929        h.finish()
930    }
931
932    /// Compute hash for a borrowed [`Download`] URI and HTTP-ACCEPT.
933    ///
934    /// [`Download`]: Self::Download
935    #[cfg(feature = "http")]
936    pub fn hash128_download(uri: &zng_task::http::Uri, accept: &Option<Txt>, options: &ImageOptions) -> ImageHash {
937        use std::hash::Hash;
938        let mut h = ImageHash::hasher();
939        1u8.hash(&mut h);
940        uri.hash(&mut h);
941        accept.hash(&mut h);
942        options.downscale.hash(&mut h);
943        options.mask.hash(&mut h);
944        options.entries.hash(&mut h);
945        h.finish()
946    }
947
948    /// Compute hash for a borrowed [`Render`] source.
949    ///
950    /// Pointer equality is used to identify the node closure.
951    ///
952    /// [`Render`]: Self::Render
953    pub fn hash128_render(rfn: &RenderFn, args: &Option<ImageRenderArgs>, options: &ImageOptions) -> ImageHash {
954        use std::hash::Hash;
955        let mut h = ImageHash::hasher();
956        2u8.hash(&mut h);
957        (Arc::as_ptr(rfn) as usize).hash(&mut h);
958        args.hash(&mut h);
959        options.downscale.hash(&mut h);
960        options.mask.hash(&mut h);
961        options.entries.hash(&mut h);
962        h.finish()
963    }
964}
965
966impl ImageSource {
967    /// New image from a function that generates a headless window.
968    ///
969    /// The function is called every time the image source is resolved and it is not found in the cache.
970    ///
971    /// # Examples
972    ///
973    /// ```
974    /// # use zng_ext_image::*;
975    /// # use zng_color::colors;
976    /// # use std::any::Any;
977    /// # struct WindowRoot;
978    /// # impl ImageRenderWindowRoot for WindowRoot { }
979    /// # macro_rules! Window { ($($property:ident = $value:expr;)+) => { {$(let _ = $value;)+ WindowRoot } } }
980    /// # macro_rules! Text { ($($tt:tt)*) => { () } }
981    /// # fn main() { }
982    /// # fn demo() {
983    /// # let _ = ImageSource::render(
984    ///     |args| Window! {
985    ///         size = (500, 400);
986    ///         parent = args.parent;
987    ///         background_color = colors::GREEN;
988    ///         child = Text!("Rendered!");
989    ///     }
990    /// )
991    /// # ; }
992    /// ```
993    ///
994    pub fn render<F, R>(new_img: F) -> Self
995    where
996        F: Fn(&ImageRenderArgs) -> R + Send + Sync + 'static,
997        R: ImageRenderWindowRoot,
998    {
999        let window = IMAGES_SV.read().render_windows();
1000        Self::Render(
1001            Arc::new(Box::new(move |args| {
1002                if let Some(parent) = args.parent {
1003                    window.set_parent_in_window_context(parent);
1004                }
1005                let r = new_img(args);
1006                window.enable_frame_capture_in_window_context(None);
1007                Box::new(r)
1008            })),
1009            None,
1010        )
1011    }
1012
1013    /// New image from a function that generates a new [`UiNode`].
1014    ///
1015    /// The function is called every time the image source is resolved and it is not found in the cache.
1016    ///
1017    /// Note that the generated [`UiNode`] is not a child of the widget that renders the image, it is the root widget of a headless
1018    /// surface, not a part of the context where it is rendered. See [`IMAGES.render`] for more information.
1019    ///
1020    /// # Examples
1021    ///
1022    /// ```
1023    /// # use zng_ext_image::*;
1024    /// # use zng_view_api::window::RenderMode;
1025    /// # use std::any::Any;
1026    /// # struct WindowRoot;
1027    /// # impl ImageRenderWindowRoot for WindowRoot { }
1028    /// # macro_rules! Container { ($($tt:tt)*) => { zng_app::widget::node::UiNode::nil() } }
1029    /// # fn main() { }
1030    /// # fn demo() {
1031    /// # let _ =
1032    /// ImageSource::render_node(RenderMode::Software, |_args| {
1033    ///     Container! {
1034    ///         size = (500, 400);
1035    ///         background_color = colors::GREEN;
1036    ///         child = Text!("Rendered!");
1037    ///     }
1038    /// })
1039    /// # ; }
1040    /// ```
1041    ///
1042    /// [`IMAGES.render`]: crate::IMAGES::render
1043    /// [`UiNode`]: zng_app::widget::node::UiNode
1044    pub fn render_node(render_mode: RenderMode, render: impl Fn(&ImageRenderArgs) -> UiNode + Send + Sync + 'static) -> Self {
1045        let window = IMAGES_SV.read().render_windows();
1046        Self::Render(
1047            Arc::new(Box::new(move |args| {
1048                if let Some(parent) = args.parent {
1049                    window.set_parent_in_window_context(parent);
1050                }
1051                let node = render(args);
1052                window.enable_frame_capture_in_window_context(None);
1053                window.new_window_root(node, render_mode)
1054            })),
1055            None,
1056        )
1057    }
1058}
1059
1060impl ImageSource {
1061    /// New image data from solid color.
1062    pub fn flood(size: impl Into<PxSize>, color: impl Into<Rgba>, density: Option<PxDensity2d>) -> Self {
1063        Self::flood_impl(size.into(), color.into(), density)
1064    }
1065    fn flood_impl(size: PxSize, color: Rgba, density: Option<PxDensity2d>) -> Self {
1066        let pixels = size.width.0 as usize * size.height.0 as usize;
1067        let bgra = color.to_bgra_bytes();
1068        let mut b = IpcBytes::new_mut_blocking(pixels * 4).expect("cannot allocate IpcBytes");
1069        for b in b.chunks_exact_mut(4) {
1070            b.copy_from_slice(&bgra);
1071        }
1072        Self::from_data(
1073            b.finish_blocking().expect("cannot allocate IpcBytes"),
1074            ImageDataFormat::Bgra8 {
1075                size,
1076                density,
1077                original_color_type: ColorType::RGBA8,
1078            },
1079        )
1080    }
1081
1082    /// New image data from vertical linear gradient.
1083    pub fn linear_vertical(
1084        size: impl Into<PxSize>,
1085        stops: impl Into<GradientStops>,
1086        density: Option<PxDensity2d>,
1087        mask: Option<ImageMaskMode>,
1088    ) -> Self {
1089        Self::linear_vertical_impl(size.into(), stops.into(), density, mask)
1090    }
1091    fn linear_vertical_impl(size: PxSize, stops: GradientStops, density: Option<PxDensity2d>, mask: Option<ImageMaskMode>) -> Self {
1092        assert!(size.width > Px(0));
1093        assert!(size.height > Px(0));
1094
1095        let mut line = PxLine::new(PxPoint::splat(Px(0)), PxPoint::new(Px(0), size.height));
1096        let mut render_stops = vec![];
1097
1098        LAYOUT.with_root_context(LayoutPassId::new(), LayoutMetrics::new(1.fct(), size, Px(14)), || {
1099            stops.layout_linear(LayoutAxis::Y, ExtendMode::Clamp, &mut line, &mut render_stops);
1100        });
1101        let line_a = line.start.y.0 as f32;
1102        let line_b = line.end.y.0 as f32;
1103
1104        let mut bgra = Vec::with_capacity(size.height.0 as usize);
1105        let mut render_stops = render_stops.into_iter();
1106        let mut stop_a = render_stops.next().unwrap();
1107        let mut stop_b = render_stops.next().unwrap();
1108        'outer: for y in 0..size.height.0 {
1109            let yf = y as f32;
1110            let yf = (yf - line_a) / (line_b - line_a);
1111            if yf < stop_a.offset {
1112                // clamp start
1113                bgra.push(stop_a.color.to_bgra_bytes());
1114                continue;
1115            }
1116            while yf > stop_b.offset {
1117                if let Some(next_b) = render_stops.next() {
1118                    // advance
1119                    stop_a = stop_b;
1120                    stop_b = next_b;
1121                } else {
1122                    // clamp end
1123                    for _ in y..size.height.0 {
1124                        bgra.push(stop_b.color.to_bgra_bytes());
1125                    }
1126                    break 'outer;
1127                }
1128            }
1129
1130            // lerp a-b
1131            let yf = (yf - stop_a.offset) / (stop_b.offset - stop_a.offset);
1132            let sample = stop_a.color.lerp(&stop_b.color, yf.fct());
1133            bgra.push(sample.to_bgra_bytes());
1134        }
1135
1136        match mask {
1137            Some(m) => {
1138                let len = size.width.0 as usize * size.height.0 as usize;
1139                let mut data = Vec::with_capacity(len);
1140
1141                for y in 0..size.height.0 {
1142                    let c = bgra[y as usize];
1143                    let c = match m {
1144                        ImageMaskMode::A => c[3],
1145                        ImageMaskMode::B => c[0],
1146                        ImageMaskMode::G => c[1],
1147                        ImageMaskMode::R => c[2],
1148                        ImageMaskMode::Luminance => {
1149                            let hsla = Hsla::from(Rgba::new(c[2], c[1], c[0], c[3]));
1150                            (hsla.lightness * 255.0).round().clamp(0.0, 255.0) as u8
1151                        }
1152                        _ => unreachable!(),
1153                    };
1154                    for _x in 0..size.width.0 {
1155                        data.push(c);
1156                    }
1157                }
1158
1159                Self::from_data(
1160                    IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"),
1161                    ImageDataFormat::A8 { size },
1162                )
1163            }
1164            None => {
1165                let len = size.width.0 as usize * size.height.0 as usize * 4;
1166                let mut data = Vec::with_capacity(len);
1167
1168                for y in 0..size.height.0 {
1169                    let c = bgra[y as usize];
1170                    for _x in 0..size.width.0 {
1171                        data.extend_from_slice(&c);
1172                    }
1173                }
1174
1175                Self::from_data(
1176                    IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"),
1177                    ImageDataFormat::Bgra8 {
1178                        size,
1179                        density,
1180                        original_color_type: ColorType::RGBA8,
1181                    },
1182                )
1183            }
1184        }
1185    }
1186
1187    /// New image data from horizontal linear gradient.
1188    pub fn linear_horizontal(
1189        size: impl Into<PxSize>,
1190        stops: impl Into<GradientStops>,
1191        density: Option<PxDensity2d>,
1192        mask: Option<ImageMaskMode>,
1193    ) -> Self {
1194        Self::linear_horizontal_impl(size.into(), stops.into(), density, mask)
1195    }
1196    fn linear_horizontal_impl(size: PxSize, stops: GradientStops, density: Option<PxDensity2d>, mask: Option<ImageMaskMode>) -> Self {
1197        assert!(size.width > Px(0));
1198        assert!(size.height > Px(0));
1199
1200        let mut line = PxLine::new(PxPoint::splat(Px(0)), PxPoint::new(size.width, Px(0)));
1201        let mut render_stops = vec![];
1202        LAYOUT.with_root_context(LayoutPassId::new(), LayoutMetrics::new(1.fct(), size, Px(14)), || {
1203            stops.layout_linear(LayoutAxis::Y, ExtendMode::Clamp, &mut line, &mut render_stops);
1204        });
1205        let line_a = line.start.x.0 as f32;
1206        let line_b = line.end.x.0 as f32;
1207
1208        let mut bgra = Vec::with_capacity(size.width.0 as usize);
1209        let mut render_stops = render_stops.into_iter();
1210        let mut stop_a = render_stops.next().unwrap();
1211        let mut stop_b = render_stops.next().unwrap();
1212        'outer: for x in 0..size.width.0 {
1213            let xf = x as f32;
1214            let xf = (xf - line_a) / (line_b - line_a);
1215            if xf < stop_a.offset {
1216                // clamp start
1217                bgra.push(stop_a.color.to_bgra_bytes());
1218                continue;
1219            }
1220            while xf > stop_b.offset {
1221                if let Some(next_b) = render_stops.next() {
1222                    // advance
1223                    stop_a = stop_b;
1224                    stop_b = next_b;
1225                } else {
1226                    // clamp end
1227                    for _ in x..size.width.0 {
1228                        bgra.push(stop_b.color.to_bgra_bytes());
1229                    }
1230                    break 'outer;
1231                }
1232            }
1233
1234            // lerp a-b
1235            let xf = (xf - stop_a.offset) / (stop_b.offset - stop_a.offset);
1236            let sample = stop_a.color.lerp(&stop_b.color, xf.fct());
1237            bgra.push(sample.to_bgra_bytes());
1238        }
1239
1240        match mask {
1241            Some(m) => {
1242                let len = size.width.0 as usize * size.height.0 as usize;
1243                let mut data = Vec::with_capacity(len);
1244
1245                for _y in 0..size.height.0 {
1246                    for c in &bgra {
1247                        let c = match m {
1248                            ImageMaskMode::A => c[3],
1249                            ImageMaskMode::B => c[0],
1250                            ImageMaskMode::G => c[1],
1251                            ImageMaskMode::R => c[2],
1252                            ImageMaskMode::Luminance => {
1253                                let hsla = Hsla::from(Rgba::new(c[2], c[1], c[0], c[3]));
1254                                (hsla.lightness * 255.0).round().clamp(0.0, 255.0) as u8
1255                            }
1256                            _ => unreachable!(),
1257                        };
1258                        data.push(c);
1259                    }
1260                }
1261
1262                Self::from_data(
1263                    IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"),
1264                    ImageDataFormat::A8 { size },
1265                )
1266            }
1267            None => {
1268                let len = size.width.0 as usize * size.height.0 as usize * 4;
1269                let mut data = Vec::with_capacity(len);
1270
1271                for _y in 0..size.height.0 {
1272                    for c in &bgra {
1273                        data.extend_from_slice(c);
1274                    }
1275                }
1276
1277                Self::from_data(
1278                    IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"),
1279                    ImageDataFormat::Bgra8 {
1280                        size,
1281                        density,
1282                        original_color_type: ColorType::RGBA8,
1283                    },
1284                )
1285            }
1286        }
1287    }
1288}
1289
1290impl PartialEq for ImageSource {
1291    fn eq(&self, other: &Self) -> bool {
1292        match (self, other) {
1293            (Self::Read(l), Self::Read(r)) => l == r,
1294            #[cfg(feature = "http")]
1295            (Self::Download(lu, la), Self::Download(ru, ra)) => lu == ru && la == ra,
1296            (Self::Render(lf, la), Self::Render(rf, ra)) => Arc::ptr_eq(lf, rf) && la == ra,
1297            (Self::Image(l), Self::Image(r)) => l.var_eq(r),
1298            (l, r) => {
1299                let l_hash = match l {
1300                    ImageSource::Data(h, _, _) => h,
1301                    _ => return false,
1302                };
1303                let r_hash = match r {
1304                    ImageSource::Data(h, _, _) => h,
1305                    _ => return false,
1306                };
1307
1308                l_hash == r_hash
1309            }
1310        }
1311    }
1312}
1313impl fmt::Debug for ImageSource {
1314    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1315        if f.alternate() {
1316            write!(f, "ImageSource::")?;
1317        }
1318        match self {
1319            ImageSource::Read(p) => f.debug_tuple("Read").field(p).finish(),
1320            #[cfg(feature = "http")]
1321            ImageSource::Download(u, a) => f.debug_tuple("Download").field(u).field(a).finish(),
1322            ImageSource::Data(key, bytes, fmt) => f.debug_tuple("Data").field(key).field(bytes).field(fmt).finish(),
1323
1324            ImageSource::Render(_, _) => write!(f, "Render(_, _)"),
1325            ImageSource::Image(_) => write!(f, "Image(_)"),
1326        }
1327    }
1328}
1329
1330#[cfg(feature = "http")]
1331impl_from_and_into_var! {
1332    fn from(uri: zng_task::http::Uri) -> ImageSource {
1333        ImageSource::Download(uri, None)
1334    }
1335    /// From (URI, HTTP-ACCEPT).
1336    fn from((uri, accept): (zng_task::http::Uri, &'static str)) -> ImageSource {
1337        ImageSource::Download(uri, Some(accept.into()))
1338    }
1339
1340    /// Converts `http://` and `https://` to [`Download`], `file://` to
1341    /// [`Read`] the path component, and the rest to [`Read`] the string as a path.
1342    ///
1343    /// [`Download`]: ImageSource::Download
1344    /// [`Read`]: ImageSource::Read
1345    fn from(s: &str) -> ImageSource {
1346        use zng_task::http::*;
1347        if let Ok(uri) = Uri::try_from(s)
1348            && let Some(scheme) = uri.scheme()
1349        {
1350            if scheme == &uri::Scheme::HTTPS || scheme == &uri::Scheme::HTTP {
1351                return ImageSource::Download(uri, None);
1352            } else if scheme.as_str() == "file" {
1353                return PathBuf::from(uri.path()).into();
1354            }
1355        }
1356        PathBuf::from(s).into()
1357    }
1358}
1359
1360#[cfg(not(feature = "http"))]
1361impl_from_and_into_var! {
1362    /// Converts to [`Read`].
1363    ///
1364    /// [`Read`]: ImageSource::Read
1365    fn from(s: &str) -> ImageSource {
1366        PathBuf::from(s).into()
1367    }
1368}
1369
1370impl_from_and_into_var! {
1371    fn from(image: ImageVar) -> ImageSource {
1372        ImageSource::Image(image)
1373    }
1374    fn from(path: PathBuf) -> ImageSource {
1375        ImageSource::Read(path)
1376    }
1377    fn from(path: &Path) -> ImageSource {
1378        path.to_owned().into()
1379    }
1380
1381    /// Same as conversion from `&str`.
1382    fn from(s: String) -> ImageSource {
1383        s.as_str().into()
1384    }
1385    /// Same as conversion from `&str`.
1386    fn from(s: Txt) -> ImageSource {
1387        s.as_str().into()
1388    }
1389    /// From encoded data of [`Unknown`] format.
1390    ///
1391    /// [`Unknown`]: ImageDataFormat::Unknown
1392    fn from(data: &[u8]) -> ImageSource {
1393        ImageSource::Data(
1394            ImageHash::compute(data),
1395            IpcBytes::from_slice_blocking(data).expect("cannot allocate IpcBytes"),
1396            ImageDataFormat::Unknown,
1397        )
1398    }
1399    /// From encoded data of [`Unknown`] format.
1400    ///
1401    /// [`Unknown`]: ImageDataFormat::Unknown
1402    fn from<const N: usize>(data: &[u8; N]) -> ImageSource {
1403        (&data[..]).into()
1404    }
1405    /// From encoded data of [`Unknown`] format.
1406    ///
1407    /// [`Unknown`]: ImageDataFormat::Unknown
1408    fn from(data: IpcBytes) -> ImageSource {
1409        ImageSource::Data(ImageHash::compute(&data[..]), data, ImageDataFormat::Unknown)
1410    }
1411    /// From encoded data of [`Unknown`] format.
1412    ///
1413    /// [`Unknown`]: ImageDataFormat::Unknown
1414    fn from(data: Vec<u8>) -> ImageSource {
1415        IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes").into()
1416    }
1417    /// From encoded data of known format.
1418    fn from<F: Into<ImageDataFormat>>((data, format): (&[u8], F)) -> ImageSource {
1419        ImageSource::Data(
1420            ImageHash::compute(data),
1421            IpcBytes::from_slice_blocking(data).expect("cannot allocate IpcBytes"),
1422            format.into(),
1423        )
1424    }
1425    /// From encoded data of known format.
1426    fn from<F: Into<ImageDataFormat>, const N: usize>((data, format): (&[u8; N], F)) -> ImageSource {
1427        (&data[..], format).into()
1428    }
1429    /// From encoded data of known format.
1430    fn from<F: Into<ImageDataFormat>>((data, format): (Vec<u8>, F)) -> ImageSource {
1431        (IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"), format).into()
1432    }
1433    /// From encoded data of known format.
1434    fn from<F: Into<ImageDataFormat>>((data, format): (IpcBytes, F)) -> ImageSource {
1435        ImageSource::Data(ImageHash::compute(&data[..]), data, format.into())
1436    }
1437}
1438
1439/// Cache mode of [`IMAGES`].
1440///
1441/// [`IMAGES`]: super::IMAGES
1442#[derive(Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1443pub enum ImageCacheMode {
1444    /// Don't hit the cache, just loads the image.
1445    Ignore,
1446    /// Gets a cached image or loads the image and caches it.
1447    Cache,
1448    /// Cache or reload if the cached image is an error.
1449    Retry,
1450    /// Reloads the cache image or loads the image and caches it.
1451    ///
1452    /// The [`ImageVar`] is not replaced, other references to the image also receive the update.
1453    Reload,
1454}
1455impl fmt::Debug for ImageCacheMode {
1456    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1457        if f.alternate() {
1458            write!(f, "CacheMode::")?;
1459        }
1460        match self {
1461            Self::Ignore => write!(f, "Ignore"),
1462            Self::Cache => write!(f, "Cache"),
1463            Self::Retry => write!(f, "Retry"),
1464            Self::Reload => write!(f, "Reload"),
1465        }
1466    }
1467}
1468impl_from_and_into_var! {
1469    fn from(cache: bool) -> ImageCacheMode {
1470        if cache { ImageCacheMode::Cache } else { ImageCacheMode::Ignore }
1471    }
1472}
1473
1474/// Represents a [`PathFilter`] and [`UriFilter`].
1475#[derive(Clone)]
1476pub enum ImageSourceFilter<U> {
1477    /// Block all requests of this type.
1478    BlockAll,
1479    /// Allow all requests of this type.
1480    AllowAll,
1481    /// Custom filter, returns `true` to allow a request, `false` to block.
1482    Custom(Arc<dyn Fn(&U) -> bool + Send + Sync>),
1483}
1484impl<U> ImageSourceFilter<U> {
1485    /// New [`Custom`] filter.
1486    ///
1487    /// [`Custom`]: Self::Custom
1488    pub fn custom(allow: impl Fn(&U) -> bool + Send + Sync + 'static) -> Self {
1489        Self::Custom(Arc::new(allow))
1490    }
1491
1492    /// Combine `self` with `other`, if they both are [`Custom`], otherwise is [`BlockAll`] if any is [`BlockAll`], else
1493    /// is [`AllowAll`] if any is [`AllowAll`].
1494    ///
1495    /// If both are [`Custom`] both filters must allow a request to pass the new filter.
1496    ///
1497    /// [`Custom`]: Self::Custom
1498    /// [`BlockAll`]: Self::BlockAll
1499    /// [`AllowAll`]: Self::AllowAll
1500    pub fn and(self, other: Self) -> Self
1501    where
1502        U: 'static,
1503    {
1504        use ImageSourceFilter::*;
1505        match (self, other) {
1506            (BlockAll, _) | (_, BlockAll) => BlockAll,
1507            (AllowAll, _) | (_, AllowAll) => AllowAll,
1508            (Custom(c0), Custom(c1)) => Custom(Arc::new(move |u| c0(u) && c1(u))),
1509        }
1510    }
1511
1512    /// Combine `self` with `other`, if they both are [`Custom`], otherwise is [`AllowAll`] if any is [`AllowAll`], else
1513    /// is [`BlockAll`] if any is [`BlockAll`].
1514    ///
1515    /// If both are [`Custom`] at least one of the filters must allow a request to pass the new filter.
1516    ///
1517    /// [`Custom`]: Self::Custom
1518    /// [`BlockAll`]: Self::BlockAll
1519    /// [`AllowAll`]: Self::AllowAll
1520    pub fn or(self, other: Self) -> Self
1521    where
1522        U: 'static,
1523    {
1524        use ImageSourceFilter::*;
1525        match (self, other) {
1526            (AllowAll, _) | (_, AllowAll) => AllowAll,
1527            (BlockAll, _) | (_, BlockAll) => BlockAll,
1528            (Custom(c0), Custom(c1)) => Custom(Arc::new(move |u| c0(u) || c1(u))),
1529        }
1530    }
1531
1532    /// Returns `true` if the filter allows the request.
1533    pub fn allows(&self, item: &U) -> bool {
1534        match self {
1535            ImageSourceFilter::BlockAll => false,
1536            ImageSourceFilter::AllowAll => true,
1537            ImageSourceFilter::Custom(f) => f(item),
1538        }
1539    }
1540}
1541impl<U> fmt::Debug for ImageSourceFilter<U> {
1542    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1543        match self {
1544            Self::BlockAll => write!(f, "BlockAll"),
1545            Self::AllowAll => write!(f, "AllowAll"),
1546            Self::Custom(_) => write!(f, "Custom(_)"),
1547        }
1548    }
1549}
1550impl<U: 'static> ops::BitAnd for ImageSourceFilter<U> {
1551    type Output = Self;
1552
1553    fn bitand(self, rhs: Self) -> Self::Output {
1554        self.and(rhs)
1555    }
1556}
1557impl<U: 'static> ops::BitOr for ImageSourceFilter<U> {
1558    type Output = Self;
1559
1560    fn bitor(self, rhs: Self) -> Self::Output {
1561        self.or(rhs)
1562    }
1563}
1564impl<U: 'static> ops::BitAndAssign for ImageSourceFilter<U> {
1565    fn bitand_assign(&mut self, rhs: Self) {
1566        *self = mem::replace(self, Self::BlockAll).and(rhs);
1567    }
1568}
1569impl<U: 'static> ops::BitOrAssign for ImageSourceFilter<U> {
1570    fn bitor_assign(&mut self, rhs: Self) {
1571        *self = mem::replace(self, Self::BlockAll).or(rhs);
1572    }
1573}
1574impl<U> PartialEq for ImageSourceFilter<U> {
1575    fn eq(&self, other: &Self) -> bool {
1576        match (self, other) {
1577            (Self::Custom(l0), Self::Custom(r0)) => Arc::ptr_eq(l0, r0),
1578            _ => core::mem::discriminant(self) == core::mem::discriminant(other),
1579        }
1580    }
1581}
1582
1583/// Represents an [`ImageSource::Read`] path request filter.
1584///
1585/// Only absolute, normalized paths are shared with the [`Custom`] filter, there is no relative paths or `..` components.
1586///
1587/// The paths are **not** canonicalized and existence is not verified, no system requests are made with unfiltered paths.
1588///
1589/// See [`ImageLimits::allow_path`] for more information.
1590///
1591/// [`Custom`]: ImageSourceFilter::Custom
1592pub type PathFilter = ImageSourceFilter<PathBuf>;
1593impl PathFilter {
1594    /// Allow any file inside `dir` or sub-directories of `dir`.
1595    pub fn allow_dir(dir: impl AsRef<Path>) -> Self {
1596        let dir = absolute_path(dir.as_ref(), || env::current_dir().expect("could not access current dir"), true);
1597        PathFilter::custom(move |r| r.starts_with(&dir))
1598    }
1599
1600    /// Allow any path with the `ext` extension.
1601    pub fn allow_ext(ext: impl Into<std::ffi::OsString>) -> Self {
1602        let ext = ext.into();
1603        PathFilter::custom(move |r| r.extension().map(|e| e == ext).unwrap_or(false))
1604    }
1605
1606    /// Allow any file inside the [`env::current_dir`] or sub-directories.
1607    ///
1608    /// Note that the current directory can be changed and the filter always uses the
1609    /// *fresh* current directory, use [`allow_dir`] to create a filter the always points
1610    /// to the current directory at the filter creation time.
1611    ///
1612    /// [`allow_dir`]: Self::allow_dir
1613    pub fn allow_current_dir() -> Self {
1614        PathFilter::custom(|r| env::current_dir().map(|d| r.starts_with(d)).unwrap_or(false))
1615    }
1616
1617    /// Allow any file inside the current executable directory or sub-directories.
1618    pub fn allow_exe_dir() -> Self {
1619        if let Ok(mut p) = env::current_exe().and_then(dunce::canonicalize)
1620            && p.pop()
1621        {
1622            return Self::allow_dir(p);
1623        }
1624
1625        // not `BlockAll` so this can still be composed using `or`.
1626        Self::custom(|_| false)
1627    }
1628
1629    /// Allow any file inside the [`zng::env::res`] directory or sub-directories.
1630    ///
1631    /// [`zng::env::res`]: zng_env::res
1632    pub fn allow_res() -> Self {
1633        Self::allow_dir(zng_env::res(""))
1634    }
1635}
1636
1637/// Represents an [`ImageSource::Download`] path request filter.
1638///
1639/// See [`ImageLimits::allow_uri`] for more information.
1640#[cfg(feature = "http")]
1641pub type UriFilter = ImageSourceFilter<zng_task::http::Uri>;
1642#[cfg(feature = "http")]
1643impl UriFilter {
1644    /// Allow any file from the `host` site.
1645    pub fn allow_host(host: impl Into<Txt>) -> Self {
1646        let host = host.into();
1647        UriFilter::custom(move |u| u.authority().map(|a| a.host() == host).unwrap_or(false))
1648    }
1649}
1650
1651impl<F: Fn(&PathBuf) -> bool + Send + Sync + 'static> From<F> for PathFilter {
1652    fn from(custom: F) -> Self {
1653        PathFilter::custom(custom)
1654    }
1655}
1656
1657#[cfg(feature = "http")]
1658impl<F: Fn(&task::http::Uri) -> bool + Send + Sync + 'static> From<F> for UriFilter {
1659    fn from(custom: F) -> Self {
1660        UriFilter::custom(custom)
1661    }
1662}
1663
1664/// Limits for image loading and decoding.
1665#[derive(Clone, Debug, PartialEq)]
1666#[non_exhaustive]
1667pub struct ImageLimits {
1668    /// Maximum encoded file size allowed.
1669    ///
1670    /// An error is returned if the file size surpasses this value. If the size can read before
1671    /// read/download the validation happens before download starts, otherwise the error happens when this limit
1672    /// is reached and all already downloaded bytes are dropped.
1673    ///
1674    /// The default is `100mb`.
1675    pub max_encoded_len: ByteLength,
1676    /// Maximum decoded file size allowed.
1677    ///
1678    /// An error is returned if the decoded image memory (width * height * 4) would surpass this.
1679    pub max_decoded_len: ByteLength,
1680
1681    /// Filter for [`ImageSource::Read`] paths.
1682    pub allow_path: PathFilter,
1683
1684    /// Filter for [`ImageSource::Download`] URIs.
1685    #[cfg(feature = "http")]
1686    pub allow_uri: UriFilter,
1687}
1688impl ImageLimits {
1689    /// No size limits, allow all paths and URIs.
1690    pub fn none() -> Self {
1691        ImageLimits {
1692            max_encoded_len: ByteLength::MAX,
1693            max_decoded_len: ByteLength::MAX,
1694            allow_path: PathFilter::AllowAll,
1695            #[cfg(feature = "http")]
1696            allow_uri: UriFilter::AllowAll,
1697        }
1698    }
1699
1700    /// Set the [`max_encoded_len`].
1701    ///
1702    /// [`max_encoded_len`]: Self::max_encoded_len
1703    pub fn with_max_encoded_len(mut self, max_encoded_len: impl Into<ByteLength>) -> Self {
1704        self.max_encoded_len = max_encoded_len.into();
1705        self
1706    }
1707
1708    /// Set the [`max_decoded_len`].
1709    ///
1710    /// [`max_decoded_len`]: Self::max_encoded_len
1711    pub fn with_max_decoded_len(mut self, max_decoded_len: impl Into<ByteLength>) -> Self {
1712        self.max_decoded_len = max_decoded_len.into();
1713        self
1714    }
1715
1716    /// Set the [`allow_path`].
1717    ///
1718    /// [`allow_path`]: Self::allow_path
1719    pub fn with_allow_path(mut self, allow_path: impl Into<PathFilter>) -> Self {
1720        self.allow_path = allow_path.into();
1721        self
1722    }
1723
1724    /// Set the [`allow_uri`].
1725    ///
1726    /// [`allow_uri`]: Self::allow_uri
1727    #[cfg(feature = "http")]
1728    pub fn with_allow_uri(mut self, allow_url: impl Into<UriFilter>) -> Self {
1729        self.allow_uri = allow_url.into();
1730        self
1731    }
1732}
1733impl Default for ImageLimits {
1734    /// 100 megabytes encoded and 4096 megabytes decoded (BMP max).
1735    ///
1736    /// Allows only paths in `zng::env::res`, blocks all downloads.
1737    fn default() -> Self {
1738        Self {
1739            max_encoded_len: 100.megabytes(),
1740            max_decoded_len: 4096.megabytes(),
1741            allow_path: PathFilter::allow_res(),
1742            #[cfg(feature = "http")]
1743            allow_uri: UriFilter::BlockAll,
1744        }
1745    }
1746}
1747impl_from_and_into_var! {
1748    fn from(some: ImageLimits) -> Option<ImageLimits>;
1749}
1750
1751/// Options for [`IMAGES.image`].
1752///
1753/// [`IMAGES.image`]: crate::IMAGES::image
1754#[derive(Debug, Clone, PartialEq)]
1755#[non_exhaustive]
1756pub struct ImageOptions {
1757    /// If and how the image is cached.
1758    pub cache_mode: ImageCacheMode,
1759    /// How the image is downscaled after decoding.
1760    pub downscale: Option<ImageDownscaleMode>,
1761    /// How to convert the decoded image to an alpha mask.
1762    pub mask: Option<ImageMaskMode>,
1763    /// How to decode containers with multiple images.
1764    pub entries: ImageEntriesMode,
1765}
1766
1767impl ImageOptions {
1768    /// New.
1769    pub fn new(
1770        cache_mode: ImageCacheMode,
1771        downscale: Option<ImageDownscaleMode>,
1772        mask: Option<ImageMaskMode>,
1773        entries: ImageEntriesMode,
1774    ) -> Self {
1775        Self {
1776            cache_mode,
1777            downscale,
1778            mask,
1779            entries,
1780        }
1781    }
1782
1783    /// New with only cache enabled.
1784    pub fn cache() -> Self {
1785        Self::new(ImageCacheMode::Cache, None, None, ImageEntriesMode::empty())
1786    }
1787
1788    /// New with nothing enabled, no caching.
1789    pub fn none() -> Self {
1790        Self::new(ImageCacheMode::Ignore, None, None, ImageEntriesMode::empty())
1791    }
1792}
1793
1794fn absolute_path(path: &Path, base: impl FnOnce() -> PathBuf, allow_escape: bool) -> PathBuf {
1795    if path.is_absolute() {
1796        normalize_path(path)
1797    } else {
1798        let mut dir = base();
1799        if allow_escape {
1800            dir.push(path);
1801            normalize_path(&dir)
1802        } else {
1803            dir.push(normalize_path(path));
1804            dir
1805        }
1806    }
1807}
1808/// Resolves `..` components, without any system request.
1809///
1810/// Source: https://github.com/rust-lang/cargo/blob/fede83ccf973457de319ba6fa0e36ead454d2e20/src/cargo/util/paths.rs#L61
1811fn normalize_path(path: &Path) -> PathBuf {
1812    use std::path::Component;
1813
1814    let mut components = path.components().peekable();
1815    let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
1816        components.next();
1817        PathBuf::from(c.as_os_str())
1818    } else {
1819        PathBuf::new()
1820    };
1821
1822    for component in components {
1823        match component {
1824            Component::Prefix(..) => unreachable!(),
1825            Component::RootDir => {
1826                ret.push(component.as_os_str());
1827            }
1828            Component::CurDir => {}
1829            Component::ParentDir => {
1830                ret.pop();
1831            }
1832            Component::Normal(c) => {
1833                ret.push(c);
1834            }
1835        }
1836    }
1837    ret
1838}