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