1use std::{
2 any::Any,
3 env, fmt, fs, io, mem, ops,
4 path::{Path, PathBuf},
5 sync::Arc,
6};
7
8use parking_lot::Mutex;
9use zng_app::{
10 view_process::{EncodeError, VIEW_PROCESS, ViewImageHandle, ViewRenderer},
11 widget::node::UiNode,
12 window::WindowId,
13};
14use zng_color::{
15 Hsla, Rgba,
16 gradient::{ExtendMode, GradientStops},
17};
18use zng_layout::{
19 context::{LAYOUT, LayoutMetrics, LayoutPassId},
20 unit::{ByteLength, ByteUnits, FactorUnits as _, LayoutAxis, Px, PxDensity2d, PxLine, PxPoint, PxRect, PxSize, about_eq},
21};
22use zng_task::{
23 self as task,
24 channel::{IpcBytes, IpcBytesMut},
25};
26use zng_txt::Txt;
27use zng_var::{Var, VarEq, animation::Transitionable, impl_from_and_into_var};
28use zng_view_api::{
29 image::{ImageDecoded, ImageEncodeRequest, ImageEntryMetadata, ImageTextureId},
30 window::RenderMode,
31};
32
33use crate::{IMAGES_SV, ImageRenderWindowRoot};
34
35pub use zng_view_api::image::{
36 ColorType, ImageDataFormat, ImageDownscaleMode, ImageEntriesMode, ImageEntryKind, ImageFormat, ImageFormatCapability, ImageMaskMode,
37 PartialImageKind,
38};
39
40pub trait ImagesExtension: Send + Sync + Any {
46 fn image(&mut self, limits: &ImageLimits, source: &mut ImageSource, options: &mut ImageOptions) {
56 let _ = (limits, source, options);
57 }
58
59 #[allow(clippy::too_many_arguments)]
72 fn image_data(
73 &mut self,
74 max_decoded_len: ByteLength,
75 key: &ImageHash,
76 data: &IpcBytes,
77 format: &ImageDataFormat,
78 options: &ImageOptions,
79 ) -> Option<ImageVar> {
80 let _ = (max_decoded_len, key, data, format, options);
81 None
82 }
83
84 fn remove(&mut self, key: &mut ImageHash, purge: &mut bool) -> bool {
91 let _ = (key, purge);
92 true
93 }
94
95 fn clear(&mut self, purge: bool) {
102 let _ = purge;
103 }
104
105 fn available_formats(&self, formats: &mut Vec<ImageFormat>) {
112 let _ = formats;
113 }
114}
115
116pub type ImageVar = Var<ImageEntry>;
122
123#[derive(Default, Debug)]
124struct ImgMut {
125 render_ids: Vec<RenderImage>,
126}
127
128#[derive(Debug, Clone)]
132pub struct ImageEntry {
133 pub(crate) cache_key: Option<ImageHash>,
134
135 pub(crate) handle: ViewImageHandle,
136 pub(crate) data: ImageDecoded,
137 entries: Vec<VarEq<ImageEntry>>,
138
139 error: Txt,
140
141 img_mut: Arc<Mutex<ImgMut>>,
142}
143impl PartialEq for ImageEntry {
144 fn eq(&self, other: &Self) -> bool {
145 self.handle == other.handle
146 && self.cache_key == other.cache_key
147 && self.error == other.error
148 && self.data == other.data
149 && self.entries == other.entries
150 }
151}
152impl ImageEntry {
153 pub fn new_loading() -> Self {
159 Self::new_error(Txt::from_static(""))
160 }
161
162 pub fn new_error(error: Txt) -> Self {
166 let mut s = Self::new(None, ViewImageHandle::dummy(), ImageDecoded::default());
167 s.error = error;
168 s
169 }
170
171 pub(crate) fn new(cache_key: Option<ImageHash>, handle: ViewImageHandle, data: ImageDecoded) -> Self {
172 Self {
173 cache_key,
174 handle,
175 data,
176 entries: vec![],
177 error: Txt::from_static(""),
178 img_mut: Arc::default(),
179 }
180 }
181
182 pub fn is_loading(&self) -> bool {
184 self.error.is_empty() && (self.data.pixels.is_empty() || self.data.partial.is_some())
185 }
186
187 pub fn is_loaded(&self) -> bool {
191 !self.is_loading()
192 }
193
194 pub fn is_error(&self) -> bool {
196 !self.error.is_empty()
197 }
198
199 pub fn error(&self) -> Option<Txt> {
201 if self.error.is_empty() { None } else { Some(self.error.clone()) }
202 }
203
204 pub fn size(&self) -> PxSize {
216 self.data.meta.size
217 }
218
219 pub fn partial_size(&self) -> Option<PxSize> {
226 match self.data.partial.as_ref()? {
227 PartialImageKind::Placeholder { pixel_size } => Some(*pixel_size),
228 PartialImageKind::Rows { height, .. } => Some(PxSize::new(self.data.meta.size.width, *height)),
229 _ => None,
230 }
231 }
232
233 pub fn partial_kind(&self) -> Option<PartialImageKind> {
237 self.data.partial.clone()
238 }
239
240 pub fn density(&self) -> Option<PxDensity2d> {
243 self.data.meta.density
244 }
245
246 pub fn original_color_type(&self) -> ColorType {
248 self.data.meta.original_color_type.clone()
249 }
250
251 pub fn color_type(&self) -> ColorType {
253 if self.is_mask() { ColorType::A8 } else { ColorType::BGRA8 }
254 }
255
256 pub fn is_opaque(&self) -> bool {
258 self.data.is_opaque
259 }
260
261 pub fn is_mask(&self) -> bool {
263 self.data.meta.is_mask
264 }
265
266 pub fn has_entries(&self) -> bool {
270 !self.entries.is_empty()
271 }
272
273 pub fn entries(&self) -> Vec<ImageVar> {
275 self.entries.iter().map(|e| e.read_only()).collect()
276 }
277
278 pub fn flat_entries(&self) -> Var<Vec<(VarEq<ImageEntry>, usize)>> {
286 let update_signal = zng_var::var(());
292
293 let mut out = vec![];
295 let mut update_handles = vec![];
296 self.flat_entries_init(&mut out, update_signal.clone(), &mut update_handles);
297 let out = zng_var::var(out);
298
299 let self_ = self.clone();
301 let signal_weak = update_signal.downgrade();
302 update_signal
303 .bind_modify(&out, move |_, out| {
304 out.clear();
305 update_handles.clear();
306 self_.flat_entries_init(&mut *out, signal_weak.upgrade().unwrap(), &mut update_handles);
307 })
308 .perm();
309 out.hold(update_signal).perm();
310 out.read_only()
311 }
312 fn flat_entries_init(&self, out: &mut Vec<(VarEq<ImageEntry>, usize)>, update_signal: Var<()>, handles: &mut Vec<zng_var::VarHandle>) {
313 for entry in self.entries.iter() {
314 Self::flat_entries_recursive_init(entry.clone(), out, update_signal.clone(), handles);
315 }
316 }
317 fn flat_entries_recursive_init(
318 img: VarEq<ImageEntry>,
319 out: &mut Vec<(VarEq<ImageEntry>, usize)>,
320 signal: Var<()>,
321 handles: &mut Vec<zng_var::VarHandle>,
322 ) {
323 handles.push(img.hook(zng_clone_move::clmv!(signal, |_| {
324 signal.update();
325 true
326 })));
327 let i = out.len();
328 out.push((img.clone(), 0));
329 img.with(move |img| {
330 for entry in img.entries.iter() {
331 Self::flat_entries_recursive_init(entry.clone(), out, signal.clone(), handles);
332 }
333 let len = out.len() - i;
334 out[i].1 = len;
335 });
336 }
337
338 pub fn entry_kind(&self) -> ImageEntryKind {
340 match &self.data.meta.parent {
341 Some(p) => p.kind.clone(),
342 None => ImageEntryKind::Page,
343 }
344 }
345
346 pub fn entry_index(&self) -> usize {
348 match &self.data.meta.parent {
349 Some(p) => p.index,
350 None => 0,
351 }
352 }
353
354 pub fn with_best_reduce<R>(&self, size: PxSize, visit: impl FnOnce(&ImageEntry) -> R) -> Option<R> {
358 fn cmp(target_size: PxSize, a: PxSize, b: PxSize) -> std::cmp::Ordering {
359 let target_ratio = target_size.width.0 as f32 / target_size.height.0 as f32;
360 let a_ratio = a.width.0 as f32 / b.height.0 as f32;
361 let b_ratio = b.width.0 as f32 / b.height.0 as f32;
362
363 let a_distortion = (target_ratio - a_ratio).abs();
364 let b_distortion = (target_ratio - b_ratio).abs();
365
366 if !about_eq(a_distortion, b_distortion, 0.01) && a_distortion < b_distortion {
367 return std::cmp::Ordering::Less;
369 }
370
371 let a_dist = a - target_size;
372 let b_dist = b - target_size;
373
374 if a_dist.width < Px(0) || a_dist.height < Px(0) {
375 if b_dist.width < Px(0) || b_dist.height < Px(0) {
376 a_dist.width.abs().cmp(&b_dist.width.abs())
378 } else {
379 std::cmp::Ordering::Greater
381 }
382 } else if b_dist.width < Px(0) || b_dist.height < Px(0) {
383 std::cmp::Ordering::Less
385 } else {
386 a_dist.width.cmp(&b_dist.width)
388 }
389 }
390
391 let mut best_i = usize::MAX;
392 let mut best_size = PxSize::zero();
393
394 if self.is_loaded() {
395 best_i = self.entries.len();
396 best_size = self.size();
397 }
398
399 for (i, entry) in self.entries.iter().enumerate() {
400 entry.with(|e| {
401 if e.is_loaded() {
402 let entry_size = e.size();
403 if cmp(size, entry_size, best_size).is_lt() {
404 best_i = i;
405 best_size = entry_size;
406 }
407 }
408 })
409 }
410
411 if best_i == usize::MAX {
412 None
414 } else if best_i == self.entries.len() {
415 Some(visit(self))
416 } else {
417 Some(self.entries[best_i].with(visit))
418 }
419 }
420
421 pub fn view_handle(&self) -> &ViewImageHandle {
423 &self.handle
424 }
425
426 pub fn layout_size(&self, ctx: &LayoutMetrics) -> PxSize {
434 self.calc_size(ctx, PxDensity2d::splat(ctx.screen_density()), false)
435 }
436
437 pub fn calc_size(&self, ctx: &LayoutMetrics, fallback_density: PxDensity2d, ignore_image_density: bool) -> PxSize {
447 let dpi = if ignore_image_density {
448 fallback_density
449 } else {
450 self.density().unwrap_or(fallback_density)
451 };
452
453 let s_density = ctx.screen_density();
454 let mut size = self.size();
455
456 let fct = ctx.scale_factor().0;
457 size.width *= (s_density.ppcm() / dpi.width.ppcm()) * fct;
458 size.height *= (s_density.ppcm() / dpi.height.ppcm()) * fct;
459
460 size
461 }
462
463 pub fn pixels(&self) -> Option<IpcBytes> {
467 if self.is_loaded() { Some(self.data.pixels.clone()) } else { None }
468 }
469
470 pub fn partial_pixels(&self) -> Option<IpcBytes> {
477 if self.is_loading() && self.data.partial.is_some() {
478 Some(self.data.pixels.clone())
479 } else {
480 None
481 }
482 }
483
484 fn actual_pixels_and_size(&self) -> Option<(PxSize, IpcBytes)> {
485 match (self.partial_pixels(), self.partial_size()) {
486 (Some(b), Some(s)) => Some((s, b)),
487 _ => Some((self.size(), self.pixels()?)),
488 }
489 }
490
491 pub fn copy_pixels(&self, rect: PxRect) -> Option<(PxRect, IpcBytesMut)> {
499 self.actual_pixels_and_size().and_then(|(size, pixels)| {
500 let area = PxRect::from_size(size).intersection(&rect).unwrap_or_default();
501 if area.size.width.0 == 0 || area.size.height.0 == 0 {
502 Some((area, IpcBytes::new_mut_blocking(0).unwrap()))
503 } else {
504 let x = area.origin.x.0 as usize;
505 let y = area.origin.y.0 as usize;
506 let width = area.size.width.0 as usize;
507 let height = area.size.height.0 as usize;
508 let pixel = if self.is_mask() { 1 } else { 4 };
509 let mut bytes = IpcBytes::new_mut_blocking(width * height * pixel).ok()?;
510 let mut write = &mut bytes[..];
511 let row_stride = self.size().width.0 as usize * pixel;
512 for l in y..y + height {
513 let line_start = l * row_stride + x * pixel;
514 let line_end = line_start + width * pixel;
515 let line = &pixels[line_start..line_end];
516 write[..line.len()].copy_from_slice(line);
517 write = &mut write[line.len()..];
518 }
519 Some((area, bytes))
520 }
521 })
522 }
523
524 pub async fn encode(&self, format: Txt) -> std::result::Result<IpcBytes, EncodeError> {
532 self.encode_with_entries(&[], format).await
533 }
534
535 pub async fn encode_with_entries(
539 &self,
540 entries: &[(ImageEntry, ImageEntryKind)],
541 format: Txt,
542 ) -> std::result::Result<IpcBytes, EncodeError> {
543 if self.is_loading() {
544 return Err(EncodeError::Loading);
545 } else if let Some(e) = self.error() {
546 return Err(e.into());
547 } else if self.handle.is_dummy() {
548 return Err(EncodeError::Dummy);
549 }
550
551 let mut r = ImageEncodeRequest::new(self.handle.image_id(), format);
552 r.entries = entries.iter().map(|(img, kind)| (img.handle.image_id(), kind.clone())).collect();
553
554 match VIEW_PROCESS.encode_image(r).recv().await {
555 Ok(r) => r,
556 Err(_) => Err(EncodeError::Disconnected),
557 }
558 }
559
560 pub async fn save(&self, path: impl Into<PathBuf>) -> io::Result<()> {
571 let path = path.into();
572 if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
573 self.save_impl(&[], Txt::from_str(ext), path).await
574 } else {
575 Err(io::Error::new(
576 io::ErrorKind::InvalidInput,
577 "could not determinate image format from path extension",
578 ))
579 }
580 }
581
582 pub async fn save_with_format(&self, format: impl Into<Txt>, path: impl Into<PathBuf>) -> io::Result<()> {
592 self.save_impl(&[], format.into(), path.into()).await
593 }
594
595 pub async fn save_with_entries(
601 &self,
602 entries: &[(ImageEntry, ImageEntryKind)],
603 format: impl Into<Txt>,
604 path: impl Into<PathBuf>,
605 ) -> io::Result<()> {
606 self.save_impl(entries, format.into(), path.into()).await
607 }
608
609 async fn save_impl(&self, entries: &[(ImageEntry, ImageEntryKind)], format: Txt, path: PathBuf) -> io::Result<()> {
610 let data = self.encode_with_entries(entries, format).await.map_err(io::Error::other)?;
611 task::wait(move || fs::write(path, &data[..])).await
612 }
613
614 pub fn insert_entry(&mut self, entry: ImageVar) {
624 let id = self.handle.image_id();
625 let (i, p) = entry.with(|i| (i.entry_index(), i.data.meta.parent.clone()));
626 let i = self
627 .entries
628 .iter()
629 .position(|v| {
630 let entry_i = v.with(|i| i.entry_index());
631 entry_i > i
632 })
633 .unwrap_or(self.entries.len());
634
635 if let Some(p) = &p {
636 if p.parent != id {
637 tracing::warn!("replacing entry parent from {:?} tp {:?}", p.parent, id);
638 entry.modify(move |e| {
639 if let Some(p) = &mut e.data.meta.parent {
640 p.parent = id;
641 } else {
642 e.data.meta.parent = Some(ImageEntryMetadata::new(id, i, ImageEntryKind::Page));
643 }
644 });
645 }
646 } else {
647 entry.modify(move |e| e.data.meta.parent = Some(ImageEntryMetadata::new(id, i, ImageEntryKind::Page)));
648 }
649
650 self.entries.insert(i, VarEq(entry));
651 }
652}
653impl zng_app::render::Img for ImageEntry {
654 fn renderer_id(&self, renderer: &ViewRenderer) -> ImageTextureId {
655 if self.is_loaded() {
656 let mut img = self.img_mut.lock();
657 let rms = &mut img.render_ids;
658 if let Some(rm) = rms.iter().find(|k| &k.renderer == renderer) {
659 return rm.image_id;
660 }
661
662 let key = match renderer.use_image(&self.handle) {
663 Ok(k) => {
664 if k == ImageTextureId::INVALID {
665 tracing::error!("received INVALID from `use_image`");
666 return k;
667 }
668 k
669 }
670 Err(_) => {
671 tracing::debug!("respawned `add_image`, will return INVALID");
672 return ImageTextureId::INVALID;
673 }
674 };
675
676 rms.push(RenderImage {
677 image_id: key,
678 renderer: renderer.clone(),
679 });
680 key
681 } else {
682 ImageTextureId::INVALID
683 }
684 }
685
686 fn size(&self) -> PxSize {
687 self.size()
688 }
689}
690
691struct RenderImage {
692 image_id: ImageTextureId,
693 renderer: ViewRenderer,
694}
695impl Drop for RenderImage {
696 fn drop(&mut self) {
697 let _ = self.renderer.delete_image_use(self.image_id);
699 }
700}
701impl fmt::Debug for RenderImage {
702 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
703 fmt::Debug::fmt(&self.image_id, f)
704 }
705}
706
707#[derive(Clone, Copy)]
715pub struct ImageHash([u8; 32]);
716impl ImageHash {
717 pub fn compute(data: &[u8]) -> Self {
719 let mut h = Self::hasher();
720 h.update(data);
721 h.finish()
722 }
723
724 pub fn hasher() -> ImageHasher {
726 ImageHasher::default()
727 }
728}
729impl fmt::Debug for ImageHash {
730 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
731 if f.alternate() {
732 f.debug_tuple("ImageHash").field(&self.0).finish()
733 } else {
734 use base64::*;
735 write!(f, "{}", base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(self.0))
736 }
737 }
738}
739impl fmt::Display for ImageHash {
740 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
741 write!(f, "{self:?}")
742 }
743}
744impl std::hash::Hash for ImageHash {
745 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
746 let h64 = [
747 self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], self.0[6], self.0[7],
748 ];
749 state.write_u64(u64::from_ne_bytes(h64))
750 }
751}
752impl PartialEq for ImageHash {
753 fn eq(&self, other: &Self) -> bool {
754 self.0 == other.0
755 }
756}
757impl Eq for ImageHash {}
758
759pub struct ImageHasher(sha2::Sha512_256);
761impl Default for ImageHasher {
762 fn default() -> Self {
763 use sha2::Digest;
764 Self(sha2::Sha512_256::new())
765 }
766}
767impl ImageHasher {
768 pub fn new() -> Self {
770 Self::default()
771 }
772
773 pub fn update(&mut self, data: &[u8]) {
775 use sha2::Digest;
776
777 const NUM_SAMPLES: usize = 1000;
780 const SAMPLE_CHUNK_SIZE: usize = 1024;
781
782 let total_size = data.len();
783 if total_size == 0 {
784 return;
785 }
786 if total_size < 1000 * 1000 * 4 {
787 return self.0.update(data);
788 }
789
790 let step_size = total_size.checked_div(NUM_SAMPLES).unwrap_or(total_size);
791 for n in 0..NUM_SAMPLES {
792 let start_index = n * step_size;
793 if start_index >= total_size {
794 break;
795 }
796 let end_index = (start_index + SAMPLE_CHUNK_SIZE).min(total_size);
797 let s = &data[start_index..end_index];
798 self.0.update(s);
799 }
800 }
801
802 pub fn finish(self) -> ImageHash {
804 use sha2::Digest;
805 ImageHash(self.0.finalize().as_slice().try_into().unwrap())
809 }
810}
811impl std::hash::Hasher for ImageHasher {
812 fn finish(&self) -> u64 {
813 tracing::warn!("Hasher::finish called for ImageHasher");
814
815 use sha2::Digest;
816 let hash = self.0.clone().finalize();
817 u64::from_le_bytes(hash[..8].try_into().unwrap())
818 }
819
820 fn write(&mut self, bytes: &[u8]) {
821 self.update(bytes);
822 }
823}
824
825pub(crate) type RenderFn = Arc<Box<dyn Fn(&ImageRenderArgs) -> Box<dyn ImageRenderWindowRoot> + Send + Sync>>;
827
828#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
832#[non_exhaustive]
833pub struct ImageRenderArgs {
834 pub parent: Option<WindowId>,
836}
837impl ImageRenderArgs {
838 pub fn new(parent: WindowId) -> Self {
840 Self { parent: Some(parent) }
841 }
842}
843
844#[derive(Clone)]
846#[non_exhaustive]
847pub enum ImageSource {
848 Read(PathBuf),
852 #[cfg(feature = "http")]
858 Download(zng_task::http::Uri, Option<Txt>),
859 Data(ImageHash, IpcBytes, ImageDataFormat),
867
868 Render(RenderFn, Option<ImageRenderArgs>),
874
875 Image(ImageVar),
879}
880impl ImageSource {
881 pub fn from_data(data: IpcBytes, format: ImageDataFormat) -> Self {
883 let mut hasher = ImageHasher::default();
884 hasher.update(&data[..]);
885 let hash = hasher.finish();
886 Self::Data(hash, data, format)
887 }
888
889 pub fn hash128(&self, options: &ImageOptions) -> Option<ImageHash> {
891 match self {
892 ImageSource::Read(p) => Some(Self::hash128_read(p, options)),
893 #[cfg(feature = "http")]
894 ImageSource::Download(u, a) => Some(Self::hash128_download(u, a, options)),
895 ImageSource::Data(h, _, _) => Some(Self::hash128_data(*h, options)),
896 ImageSource::Render(rfn, args) => Some(Self::hash128_render(rfn, args, options)),
897 ImageSource::Image(_) => None,
898 }
899 }
900
901 pub fn hash128_data(data_hash: ImageHash, options: &ImageOptions) -> ImageHash {
905 if options.downscale.is_some() || options.mask.is_some() || !options.entries.is_empty() {
906 use std::hash::Hash;
907 let mut h = ImageHash::hasher();
908 data_hash.0.hash(&mut h);
909 options.downscale.hash(&mut h);
910 options.mask.hash(&mut h);
911 options.entries.hash(&mut h);
912 h.finish()
913 } else {
914 data_hash
915 }
916 }
917
918 pub fn hash128_read(path: &Path, options: &ImageOptions) -> ImageHash {
922 use std::hash::Hash;
923 let mut h = ImageHash::hasher();
924 0u8.hash(&mut h);
925 path.hash(&mut h);
926 options.downscale.hash(&mut h);
927 options.mask.hash(&mut h);
928 options.entries.hash(&mut h);
929 h.finish()
930 }
931
932 #[cfg(feature = "http")]
936 pub fn hash128_download(uri: &zng_task::http::Uri, accept: &Option<Txt>, options: &ImageOptions) -> ImageHash {
937 use std::hash::Hash;
938 let mut h = ImageHash::hasher();
939 1u8.hash(&mut h);
940 uri.hash(&mut h);
941 accept.hash(&mut h);
942 options.downscale.hash(&mut h);
943 options.mask.hash(&mut h);
944 options.entries.hash(&mut h);
945 h.finish()
946 }
947
948 pub fn hash128_render(rfn: &RenderFn, args: &Option<ImageRenderArgs>, options: &ImageOptions) -> ImageHash {
954 use std::hash::Hash;
955 let mut h = ImageHash::hasher();
956 2u8.hash(&mut h);
957 (Arc::as_ptr(rfn) as usize).hash(&mut h);
958 args.hash(&mut h);
959 options.downscale.hash(&mut h);
960 options.mask.hash(&mut h);
961 options.entries.hash(&mut h);
962 h.finish()
963 }
964}
965
966impl ImageSource {
967 pub fn render<F, R>(new_img: F) -> Self
995 where
996 F: Fn(&ImageRenderArgs) -> R + Send + Sync + 'static,
997 R: ImageRenderWindowRoot,
998 {
999 let window = IMAGES_SV.read().render_windows();
1000 Self::Render(
1001 Arc::new(Box::new(move |args| {
1002 if let Some(parent) = args.parent {
1003 window.set_parent_in_window_context(parent);
1004 }
1005 let r = new_img(args);
1006 window.enable_frame_capture_in_window_context(None);
1007 Box::new(r)
1008 })),
1009 None,
1010 )
1011 }
1012
1013 pub fn render_node(render_mode: RenderMode, render: impl Fn(&ImageRenderArgs) -> UiNode + Send + Sync + 'static) -> Self {
1045 let window = IMAGES_SV.read().render_windows();
1046 Self::Render(
1047 Arc::new(Box::new(move |args| {
1048 if let Some(parent) = args.parent {
1049 window.set_parent_in_window_context(parent);
1050 }
1051 let node = render(args);
1052 window.enable_frame_capture_in_window_context(None);
1053 window.new_window_root(node, render_mode)
1054 })),
1055 None,
1056 )
1057 }
1058}
1059
1060impl ImageSource {
1061 pub fn flood(size: impl Into<PxSize>, color: impl Into<Rgba>, density: Option<PxDensity2d>) -> Self {
1063 Self::flood_impl(size.into(), color.into(), density)
1064 }
1065 fn flood_impl(size: PxSize, color: Rgba, density: Option<PxDensity2d>) -> Self {
1066 let pixels = size.width.0 as usize * size.height.0 as usize;
1067 let bgra = color.to_bgra_bytes();
1068 let mut b = IpcBytes::new_mut_blocking(pixels * 4).expect("cannot allocate IpcBytes");
1069 for b in b.chunks_exact_mut(4) {
1070 b.copy_from_slice(&bgra);
1071 }
1072 Self::from_data(
1073 b.finish_blocking().expect("cannot allocate IpcBytes"),
1074 ImageDataFormat::Bgra8 {
1075 size,
1076 density,
1077 original_color_type: ColorType::RGBA8,
1078 },
1079 )
1080 }
1081
1082 pub fn linear_vertical(
1084 size: impl Into<PxSize>,
1085 stops: impl Into<GradientStops>,
1086 density: Option<PxDensity2d>,
1087 mask: Option<ImageMaskMode>,
1088 ) -> Self {
1089 Self::linear_vertical_impl(size.into(), stops.into(), density, mask)
1090 }
1091 fn linear_vertical_impl(size: PxSize, stops: GradientStops, density: Option<PxDensity2d>, mask: Option<ImageMaskMode>) -> Self {
1092 assert!(size.width > Px(0));
1093 assert!(size.height > Px(0));
1094
1095 let mut line = PxLine::new(PxPoint::splat(Px(0)), PxPoint::new(Px(0), size.height));
1096 let mut render_stops = vec![];
1097
1098 LAYOUT.with_root_context(LayoutPassId::new(), LayoutMetrics::new(1.fct(), size, Px(14)), || {
1099 stops.layout_linear(LayoutAxis::Y, ExtendMode::Clamp, &mut line, &mut render_stops);
1100 });
1101 let line_a = line.start.y.0 as f32;
1102 let line_b = line.end.y.0 as f32;
1103
1104 let mut bgra = Vec::with_capacity(size.height.0 as usize);
1105 let mut render_stops = render_stops.into_iter();
1106 let mut stop_a = render_stops.next().unwrap();
1107 let mut stop_b = render_stops.next().unwrap();
1108 'outer: for y in 0..size.height.0 {
1109 let yf = y as f32;
1110 let yf = (yf - line_a) / (line_b - line_a);
1111 if yf < stop_a.offset {
1112 bgra.push(stop_a.color.to_bgra_bytes());
1114 continue;
1115 }
1116 while yf > stop_b.offset {
1117 if let Some(next_b) = render_stops.next() {
1118 stop_a = stop_b;
1120 stop_b = next_b;
1121 } else {
1122 for _ in y..size.height.0 {
1124 bgra.push(stop_b.color.to_bgra_bytes());
1125 }
1126 break 'outer;
1127 }
1128 }
1129
1130 let yf = (yf - stop_a.offset) / (stop_b.offset - stop_a.offset);
1132 let sample = stop_a.color.lerp(&stop_b.color, yf.fct());
1133 bgra.push(sample.to_bgra_bytes());
1134 }
1135
1136 match mask {
1137 Some(m) => {
1138 let len = size.width.0 as usize * size.height.0 as usize;
1139 let mut data = Vec::with_capacity(len);
1140
1141 for y in 0..size.height.0 {
1142 let c = bgra[y as usize];
1143 let c = match m {
1144 ImageMaskMode::A => c[3],
1145 ImageMaskMode::B => c[0],
1146 ImageMaskMode::G => c[1],
1147 ImageMaskMode::R => c[2],
1148 ImageMaskMode::Luminance => {
1149 let hsla = Hsla::from(Rgba::new(c[2], c[1], c[0], c[3]));
1150 (hsla.lightness * 255.0).round().clamp(0.0, 255.0) as u8
1151 }
1152 _ => unreachable!(),
1153 };
1154 for _x in 0..size.width.0 {
1155 data.push(c);
1156 }
1157 }
1158
1159 Self::from_data(
1160 IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"),
1161 ImageDataFormat::A8 { size },
1162 )
1163 }
1164 None => {
1165 let len = size.width.0 as usize * size.height.0 as usize * 4;
1166 let mut data = Vec::with_capacity(len);
1167
1168 for y in 0..size.height.0 {
1169 let c = bgra[y as usize];
1170 for _x in 0..size.width.0 {
1171 data.extend_from_slice(&c);
1172 }
1173 }
1174
1175 Self::from_data(
1176 IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"),
1177 ImageDataFormat::Bgra8 {
1178 size,
1179 density,
1180 original_color_type: ColorType::RGBA8,
1181 },
1182 )
1183 }
1184 }
1185 }
1186
1187 pub fn linear_horizontal(
1189 size: impl Into<PxSize>,
1190 stops: impl Into<GradientStops>,
1191 density: Option<PxDensity2d>,
1192 mask: Option<ImageMaskMode>,
1193 ) -> Self {
1194 Self::linear_horizontal_impl(size.into(), stops.into(), density, mask)
1195 }
1196 fn linear_horizontal_impl(size: PxSize, stops: GradientStops, density: Option<PxDensity2d>, mask: Option<ImageMaskMode>) -> Self {
1197 assert!(size.width > Px(0));
1198 assert!(size.height > Px(0));
1199
1200 let mut line = PxLine::new(PxPoint::splat(Px(0)), PxPoint::new(size.width, Px(0)));
1201 let mut render_stops = vec![];
1202 LAYOUT.with_root_context(LayoutPassId::new(), LayoutMetrics::new(1.fct(), size, Px(14)), || {
1203 stops.layout_linear(LayoutAxis::Y, ExtendMode::Clamp, &mut line, &mut render_stops);
1204 });
1205 let line_a = line.start.x.0 as f32;
1206 let line_b = line.end.x.0 as f32;
1207
1208 let mut bgra = Vec::with_capacity(size.width.0 as usize);
1209 let mut render_stops = render_stops.into_iter();
1210 let mut stop_a = render_stops.next().unwrap();
1211 let mut stop_b = render_stops.next().unwrap();
1212 'outer: for x in 0..size.width.0 {
1213 let xf = x as f32;
1214 let xf = (xf - line_a) / (line_b - line_a);
1215 if xf < stop_a.offset {
1216 bgra.push(stop_a.color.to_bgra_bytes());
1218 continue;
1219 }
1220 while xf > stop_b.offset {
1221 if let Some(next_b) = render_stops.next() {
1222 stop_a = stop_b;
1224 stop_b = next_b;
1225 } else {
1226 for _ in x..size.width.0 {
1228 bgra.push(stop_b.color.to_bgra_bytes());
1229 }
1230 break 'outer;
1231 }
1232 }
1233
1234 let xf = (xf - stop_a.offset) / (stop_b.offset - stop_a.offset);
1236 let sample = stop_a.color.lerp(&stop_b.color, xf.fct());
1237 bgra.push(sample.to_bgra_bytes());
1238 }
1239
1240 match mask {
1241 Some(m) => {
1242 let len = size.width.0 as usize * size.height.0 as usize;
1243 let mut data = Vec::with_capacity(len);
1244
1245 for _y in 0..size.height.0 {
1246 for c in &bgra {
1247 let c = match m {
1248 ImageMaskMode::A => c[3],
1249 ImageMaskMode::B => c[0],
1250 ImageMaskMode::G => c[1],
1251 ImageMaskMode::R => c[2],
1252 ImageMaskMode::Luminance => {
1253 let hsla = Hsla::from(Rgba::new(c[2], c[1], c[0], c[3]));
1254 (hsla.lightness * 255.0).round().clamp(0.0, 255.0) as u8
1255 }
1256 _ => unreachable!(),
1257 };
1258 data.push(c);
1259 }
1260 }
1261
1262 Self::from_data(
1263 IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"),
1264 ImageDataFormat::A8 { size },
1265 )
1266 }
1267 None => {
1268 let len = size.width.0 as usize * size.height.0 as usize * 4;
1269 let mut data = Vec::with_capacity(len);
1270
1271 for _y in 0..size.height.0 {
1272 for c in &bgra {
1273 data.extend_from_slice(c);
1274 }
1275 }
1276
1277 Self::from_data(
1278 IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"),
1279 ImageDataFormat::Bgra8 {
1280 size,
1281 density,
1282 original_color_type: ColorType::RGBA8,
1283 },
1284 )
1285 }
1286 }
1287 }
1288}
1289
1290impl PartialEq for ImageSource {
1291 fn eq(&self, other: &Self) -> bool {
1292 match (self, other) {
1293 (Self::Read(l), Self::Read(r)) => l == r,
1294 #[cfg(feature = "http")]
1295 (Self::Download(lu, la), Self::Download(ru, ra)) => lu == ru && la == ra,
1296 (Self::Render(lf, la), Self::Render(rf, ra)) => Arc::ptr_eq(lf, rf) && la == ra,
1297 (Self::Image(l), Self::Image(r)) => l.var_eq(r),
1298 (l, r) => {
1299 let l_hash = match l {
1300 ImageSource::Data(h, _, _) => h,
1301 _ => return false,
1302 };
1303 let r_hash = match r {
1304 ImageSource::Data(h, _, _) => h,
1305 _ => return false,
1306 };
1307
1308 l_hash == r_hash
1309 }
1310 }
1311 }
1312}
1313impl fmt::Debug for ImageSource {
1314 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1315 if f.alternate() {
1316 write!(f, "ImageSource::")?;
1317 }
1318 match self {
1319 ImageSource::Read(p) => f.debug_tuple("Read").field(p).finish(),
1320 #[cfg(feature = "http")]
1321 ImageSource::Download(u, a) => f.debug_tuple("Download").field(u).field(a).finish(),
1322 ImageSource::Data(key, bytes, fmt) => f.debug_tuple("Data").field(key).field(bytes).field(fmt).finish(),
1323
1324 ImageSource::Render(_, _) => write!(f, "Render(_, _)"),
1325 ImageSource::Image(_) => write!(f, "Image(_)"),
1326 }
1327 }
1328}
1329
1330#[cfg(feature = "http")]
1331impl_from_and_into_var! {
1332 fn from(uri: zng_task::http::Uri) -> ImageSource {
1333 ImageSource::Download(uri, None)
1334 }
1335 fn from((uri, accept): (zng_task::http::Uri, &'static str)) -> ImageSource {
1337 ImageSource::Download(uri, Some(accept.into()))
1338 }
1339
1340 fn from(s: &str) -> ImageSource {
1346 use zng_task::http::*;
1347 if let Ok(uri) = Uri::try_from(s)
1348 && let Some(scheme) = uri.scheme()
1349 {
1350 if scheme == &uri::Scheme::HTTPS || scheme == &uri::Scheme::HTTP {
1351 return ImageSource::Download(uri, None);
1352 } else if scheme.as_str() == "file" {
1353 return PathBuf::from(uri.path()).into();
1354 }
1355 }
1356 PathBuf::from(s).into()
1357 }
1358}
1359
1360#[cfg(not(feature = "http"))]
1361impl_from_and_into_var! {
1362 fn from(s: &str) -> ImageSource {
1366 PathBuf::from(s).into()
1367 }
1368}
1369
1370impl_from_and_into_var! {
1371 fn from(image: ImageVar) -> ImageSource {
1372 ImageSource::Image(image)
1373 }
1374 fn from(path: PathBuf) -> ImageSource {
1375 ImageSource::Read(path)
1376 }
1377 fn from(path: &Path) -> ImageSource {
1378 path.to_owned().into()
1379 }
1380
1381 fn from(s: String) -> ImageSource {
1383 s.as_str().into()
1384 }
1385 fn from(s: Txt) -> ImageSource {
1387 s.as_str().into()
1388 }
1389 fn from(data: &[u8]) -> ImageSource {
1393 ImageSource::Data(
1394 ImageHash::compute(data),
1395 IpcBytes::from_slice_blocking(data).expect("cannot allocate IpcBytes"),
1396 ImageDataFormat::Unknown,
1397 )
1398 }
1399 fn from<const N: usize>(data: &[u8; N]) -> ImageSource {
1403 (&data[..]).into()
1404 }
1405 fn from(data: IpcBytes) -> ImageSource {
1409 ImageSource::Data(ImageHash::compute(&data[..]), data, ImageDataFormat::Unknown)
1410 }
1411 fn from(data: Vec<u8>) -> ImageSource {
1415 IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes").into()
1416 }
1417 fn from<F: Into<ImageDataFormat>>((data, format): (&[u8], F)) -> ImageSource {
1419 ImageSource::Data(
1420 ImageHash::compute(data),
1421 IpcBytes::from_slice_blocking(data).expect("cannot allocate IpcBytes"),
1422 format.into(),
1423 )
1424 }
1425 fn from<F: Into<ImageDataFormat>, const N: usize>((data, format): (&[u8; N], F)) -> ImageSource {
1427 (&data[..], format).into()
1428 }
1429 fn from<F: Into<ImageDataFormat>>((data, format): (Vec<u8>, F)) -> ImageSource {
1431 (IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"), format).into()
1432 }
1433 fn from<F: Into<ImageDataFormat>>((data, format): (IpcBytes, F)) -> ImageSource {
1435 ImageSource::Data(ImageHash::compute(&data[..]), data, format.into())
1436 }
1437}
1438
1439#[derive(Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1443pub enum ImageCacheMode {
1444 Ignore,
1446 Cache,
1448 Retry,
1450 Reload,
1454}
1455impl fmt::Debug for ImageCacheMode {
1456 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1457 if f.alternate() {
1458 write!(f, "CacheMode::")?;
1459 }
1460 match self {
1461 Self::Ignore => write!(f, "Ignore"),
1462 Self::Cache => write!(f, "Cache"),
1463 Self::Retry => write!(f, "Retry"),
1464 Self::Reload => write!(f, "Reload"),
1465 }
1466 }
1467}
1468impl_from_and_into_var! {
1469 fn from(cache: bool) -> ImageCacheMode {
1470 if cache { ImageCacheMode::Cache } else { ImageCacheMode::Ignore }
1471 }
1472}
1473
1474#[derive(Clone)]
1476pub enum ImageSourceFilter<U> {
1477 BlockAll,
1479 AllowAll,
1481 Custom(Arc<dyn Fn(&U) -> bool + Send + Sync>),
1483}
1484impl<U> ImageSourceFilter<U> {
1485 pub fn custom(allow: impl Fn(&U) -> bool + Send + Sync + 'static) -> Self {
1489 Self::Custom(Arc::new(allow))
1490 }
1491
1492 pub fn and(self, other: Self) -> Self
1501 where
1502 U: 'static,
1503 {
1504 use ImageSourceFilter::*;
1505 match (self, other) {
1506 (BlockAll, _) | (_, BlockAll) => BlockAll,
1507 (AllowAll, _) | (_, AllowAll) => AllowAll,
1508 (Custom(c0), Custom(c1)) => Custom(Arc::new(move |u| c0(u) && c1(u))),
1509 }
1510 }
1511
1512 pub fn or(self, other: Self) -> Self
1521 where
1522 U: 'static,
1523 {
1524 use ImageSourceFilter::*;
1525 match (self, other) {
1526 (AllowAll, _) | (_, AllowAll) => AllowAll,
1527 (BlockAll, _) | (_, BlockAll) => BlockAll,
1528 (Custom(c0), Custom(c1)) => Custom(Arc::new(move |u| c0(u) || c1(u))),
1529 }
1530 }
1531
1532 pub fn allows(&self, item: &U) -> bool {
1534 match self {
1535 ImageSourceFilter::BlockAll => false,
1536 ImageSourceFilter::AllowAll => true,
1537 ImageSourceFilter::Custom(f) => f(item),
1538 }
1539 }
1540}
1541impl<U> fmt::Debug for ImageSourceFilter<U> {
1542 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1543 match self {
1544 Self::BlockAll => write!(f, "BlockAll"),
1545 Self::AllowAll => write!(f, "AllowAll"),
1546 Self::Custom(_) => write!(f, "Custom(_)"),
1547 }
1548 }
1549}
1550impl<U: 'static> ops::BitAnd for ImageSourceFilter<U> {
1551 type Output = Self;
1552
1553 fn bitand(self, rhs: Self) -> Self::Output {
1554 self.and(rhs)
1555 }
1556}
1557impl<U: 'static> ops::BitOr for ImageSourceFilter<U> {
1558 type Output = Self;
1559
1560 fn bitor(self, rhs: Self) -> Self::Output {
1561 self.or(rhs)
1562 }
1563}
1564impl<U: 'static> ops::BitAndAssign for ImageSourceFilter<U> {
1565 fn bitand_assign(&mut self, rhs: Self) {
1566 *self = mem::replace(self, Self::BlockAll).and(rhs);
1567 }
1568}
1569impl<U: 'static> ops::BitOrAssign for ImageSourceFilter<U> {
1570 fn bitor_assign(&mut self, rhs: Self) {
1571 *self = mem::replace(self, Self::BlockAll).or(rhs);
1572 }
1573}
1574impl<U> PartialEq for ImageSourceFilter<U> {
1575 fn eq(&self, other: &Self) -> bool {
1576 match (self, other) {
1577 (Self::Custom(l0), Self::Custom(r0)) => Arc::ptr_eq(l0, r0),
1578 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
1579 }
1580 }
1581}
1582
1583pub type PathFilter = ImageSourceFilter<PathBuf>;
1593impl PathFilter {
1594 pub fn allow_dir(dir: impl AsRef<Path>) -> Self {
1596 let dir = absolute_path(dir.as_ref(), || env::current_dir().expect("could not access current dir"), true);
1597 PathFilter::custom(move |r| r.starts_with(&dir))
1598 }
1599
1600 pub fn allow_ext(ext: impl Into<std::ffi::OsString>) -> Self {
1602 let ext = ext.into();
1603 PathFilter::custom(move |r| r.extension().map(|e| e == ext).unwrap_or(false))
1604 }
1605
1606 pub fn allow_current_dir() -> Self {
1614 PathFilter::custom(|r| env::current_dir().map(|d| r.starts_with(d)).unwrap_or(false))
1615 }
1616
1617 pub fn allow_exe_dir() -> Self {
1619 if let Ok(mut p) = env::current_exe().and_then(dunce::canonicalize)
1620 && p.pop()
1621 {
1622 return Self::allow_dir(p);
1623 }
1624
1625 Self::custom(|_| false)
1627 }
1628
1629 pub fn allow_res() -> Self {
1633 Self::allow_dir(zng_env::res(""))
1634 }
1635}
1636
1637#[cfg(feature = "http")]
1641pub type UriFilter = ImageSourceFilter<zng_task::http::Uri>;
1642#[cfg(feature = "http")]
1643impl UriFilter {
1644 pub fn allow_host(host: impl Into<Txt>) -> Self {
1646 let host = host.into();
1647 UriFilter::custom(move |u| u.authority().map(|a| a.host() == host).unwrap_or(false))
1648 }
1649}
1650
1651impl<F: Fn(&PathBuf) -> bool + Send + Sync + 'static> From<F> for PathFilter {
1652 fn from(custom: F) -> Self {
1653 PathFilter::custom(custom)
1654 }
1655}
1656
1657#[cfg(feature = "http")]
1658impl<F: Fn(&task::http::Uri) -> bool + Send + Sync + 'static> From<F> for UriFilter {
1659 fn from(custom: F) -> Self {
1660 UriFilter::custom(custom)
1661 }
1662}
1663
1664#[derive(Clone, Debug, PartialEq)]
1666#[non_exhaustive]
1667pub struct ImageLimits {
1668 pub max_encoded_len: ByteLength,
1676 pub max_decoded_len: ByteLength,
1680
1681 pub allow_path: PathFilter,
1683
1684 #[cfg(feature = "http")]
1686 pub allow_uri: UriFilter,
1687}
1688impl ImageLimits {
1689 pub fn none() -> Self {
1691 ImageLimits {
1692 max_encoded_len: ByteLength::MAX,
1693 max_decoded_len: ByteLength::MAX,
1694 allow_path: PathFilter::AllowAll,
1695 #[cfg(feature = "http")]
1696 allow_uri: UriFilter::AllowAll,
1697 }
1698 }
1699
1700 pub fn with_max_encoded_len(mut self, max_encoded_len: impl Into<ByteLength>) -> Self {
1704 self.max_encoded_len = max_encoded_len.into();
1705 self
1706 }
1707
1708 pub fn with_max_decoded_len(mut self, max_decoded_len: impl Into<ByteLength>) -> Self {
1712 self.max_decoded_len = max_decoded_len.into();
1713 self
1714 }
1715
1716 pub fn with_allow_path(mut self, allow_path: impl Into<PathFilter>) -> Self {
1720 self.allow_path = allow_path.into();
1721 self
1722 }
1723
1724 #[cfg(feature = "http")]
1728 pub fn with_allow_uri(mut self, allow_url: impl Into<UriFilter>) -> Self {
1729 self.allow_uri = allow_url.into();
1730 self
1731 }
1732}
1733impl Default for ImageLimits {
1734 fn default() -> Self {
1738 Self {
1739 max_encoded_len: 100.megabytes(),
1740 max_decoded_len: 4096.megabytes(),
1741 allow_path: PathFilter::allow_res(),
1742 #[cfg(feature = "http")]
1743 allow_uri: UriFilter::BlockAll,
1744 }
1745 }
1746}
1747impl_from_and_into_var! {
1748 fn from(some: ImageLimits) -> Option<ImageLimits>;
1749}
1750
1751#[derive(Debug, Clone, PartialEq)]
1755#[non_exhaustive]
1756pub struct ImageOptions {
1757 pub cache_mode: ImageCacheMode,
1759 pub downscale: Option<ImageDownscaleMode>,
1761 pub mask: Option<ImageMaskMode>,
1763 pub entries: ImageEntriesMode,
1765}
1766
1767impl ImageOptions {
1768 pub fn new(
1770 cache_mode: ImageCacheMode,
1771 downscale: Option<ImageDownscaleMode>,
1772 mask: Option<ImageMaskMode>,
1773 entries: ImageEntriesMode,
1774 ) -> Self {
1775 Self {
1776 cache_mode,
1777 downscale,
1778 mask,
1779 entries,
1780 }
1781 }
1782
1783 pub fn cache() -> Self {
1785 Self::new(ImageCacheMode::Cache, None, None, ImageEntriesMode::empty())
1786 }
1787
1788 pub fn none() -> Self {
1790 Self::new(ImageCacheMode::Ignore, None, None, ImageEntriesMode::empty())
1791 }
1792}
1793
1794fn absolute_path(path: &Path, base: impl FnOnce() -> PathBuf, allow_escape: bool) -> PathBuf {
1795 if path.is_absolute() {
1796 normalize_path(path)
1797 } else {
1798 let mut dir = base();
1799 if allow_escape {
1800 dir.push(path);
1801 normalize_path(&dir)
1802 } else {
1803 dir.push(normalize_path(path));
1804 dir
1805 }
1806 }
1807}
1808fn normalize_path(path: &Path) -> PathBuf {
1812 use std::path::Component;
1813
1814 let mut components = path.components().peekable();
1815 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
1816 components.next();
1817 PathBuf::from(c.as_os_str())
1818 } else {
1819 PathBuf::new()
1820 };
1821
1822 for component in components {
1823 match component {
1824 Component::Prefix(..) => unreachable!(),
1825 Component::RootDir => {
1826 ret.push(component.as_os_str());
1827 }
1828 Component::CurDir => {}
1829 Component::ParentDir => {
1830 ret.pop();
1831 }
1832 Component::Normal(c) => {
1833 ret.push(c);
1834 }
1835 }
1836 }
1837 ret
1838}