Skip to main content

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