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