1use std::{
2 any::Any,
3 env, fmt, fs, io, mem, ops,
4 path::{Path, PathBuf},
5 sync::Arc,
6};
7
8use parking_lot::Mutex;
9use zng_app::{
10 view_process::{EncodeError, VIEW_PROCESS, ViewImageHandle, ViewRenderer},
11 widget::node::UiNode,
12 window::WindowId,
13};
14use zng_color::{
15 Hsla, Rgba,
16 gradient::{ExtendMode, GradientStops},
17};
18use zng_layout::{
19 context::{LAYOUT, LayoutMetrics, LayoutPassId},
20 unit::{ByteLength, ByteUnits, FactorUnits as _, LayoutAxis, Px, PxDensity2d, PxLine, PxPoint, PxRect, PxSize, about_eq},
21};
22use zng_task::{
23 self as task,
24 channel::{IpcBytes, IpcBytesMut},
25};
26use zng_txt::Txt;
27use zng_var::{Var, VarEq, animation::Transitionable, impl_from_and_into_var};
28use zng_view_api::{
29 api_extension::{ApiExtensionId, ApiExtensionPayload},
30 image::{ImageDecoded, ImageEncodeRequest, ImageEntryMetadata, ImageTextureId},
31 window::RenderMode,
32};
33
34use crate::{IMAGES_SV, ImageRenderWindowRoot};
35
36pub use zng_view_api::image::{
37 ColorType, ImageDataFormat, ImageDownscaleMode, ImageEntriesMode, ImageEntryKind, ImageFormat, ImageFormatCapability, ImageMaskMode,
38 PartialImageKind,
39};
40
41pub trait ImagesExtension: Send + Sync + Any {
47 fn image(&mut self, limits: &ImageLimits, source: &mut ImageSource, options: &mut ImageOptions) {
57 let _ = (limits, source, options);
58 }
59
60 #[allow(clippy::too_many_arguments)]
73 fn image_data(
74 &mut self,
75 max_decoded_len: ByteLength,
76 key: &ImageHash,
77 data: &IpcBytes,
78 format: &ImageDataFormat,
79 options: &ImageOptions,
80 ) -> Option<ImageVar> {
81 let _ = (max_decoded_len, key, data, format, options);
82 None
83 }
84
85 fn remove(&mut self, key: &mut ImageHash, purge: &mut bool) -> bool {
92 let _ = (key, purge);
93 true
94 }
95
96 fn clear(&mut self, purge: bool) {
103 let _ = purge;
104 }
105
106 fn available_formats(&self, formats: &mut Vec<ImageFormat>) {
113 let _ = formats;
114 }
115}
116
117pub type ImageVar = Var<ImageEntry>;
123
124#[derive(Default, Debug)]
125struct ImgMut {
126 render_ids: Vec<RenderImage>,
127}
128
129#[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 pub fn new_loading() -> Self {
160 Self::new_error(Txt::from_static(""))
161 }
162
163 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 pub fn is_loading(&self) -> bool {
185 self.error.is_empty() && (self.data.pixels.is_empty() || self.data.partial.is_some())
186 }
187
188 pub fn is_loaded(&self) -> bool {
192 !self.is_loading()
193 }
194
195 pub fn is_error(&self) -> bool {
197 !self.error.is_empty()
198 }
199
200 pub fn error(&self) -> Option<Txt> {
202 if self.error.is_empty() { None } else { Some(self.error.clone()) }
203 }
204
205 pub fn size(&self) -> PxSize {
217 self.data.meta.size
218 }
219
220 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 pub fn partial_kind(&self) -> Option<PartialImageKind> {
238 self.data.partial.clone()
239 }
240
241 pub fn density(&self) -> Option<PxDensity2d> {
244 self.data.meta.density
245 }
246
247 pub fn original_color_type(&self) -> ColorType {
249 self.data.meta.original_color_type.clone()
250 }
251
252 pub fn color_type(&self) -> ColorType {
254 if self.is_mask() { ColorType::A8 } else { ColorType::BGRA8 }
255 }
256
257 pub fn is_opaque(&self) -> bool {
259 self.data.is_opaque
260 }
261
262 pub fn is_mask(&self) -> bool {
264 self.data.meta.is_mask
265 }
266
267 pub fn has_entries(&self) -> bool {
271 !self.entries.is_empty()
272 }
273
274 pub fn entries(&self) -> Vec<ImageVar> {
276 self.entries.iter().map(|e| e.read_only()).collect()
277 }
278
279 pub fn flat_entries(&self) -> Var<Vec<(VarEq<ImageEntry>, usize)>> {
287 let update_signal = zng_var::var(());
293
294 let mut out = vec![];
296 let mut update_handles = vec![];
297 self.flat_entries_init(&mut out, update_signal.clone(), &mut update_handles);
298 let out = zng_var::var(out);
299
300 let self_ = self.clone();
302 let signal_weak = update_signal.downgrade();
303 update_signal
304 .bind_modify(&out, move |_, out| {
305 out.clear();
306 update_handles.clear();
307 self_.flat_entries_init(&mut *out, signal_weak.upgrade().unwrap(), &mut update_handles);
308 })
309 .perm();
310 out.hold(update_signal).perm();
311 out.read_only()
312 }
313 fn flat_entries_init(&self, out: &mut Vec<(VarEq<ImageEntry>, usize)>, update_signal: Var<()>, handles: &mut Vec<zng_var::VarHandle>) {
314 for entry in self.entries.iter() {
315 Self::flat_entries_recursive_init(entry.clone(), out, update_signal.clone(), handles);
316 }
317 }
318 fn flat_entries_recursive_init(
319 img: VarEq<ImageEntry>,
320 out: &mut Vec<(VarEq<ImageEntry>, usize)>,
321 signal: Var<()>,
322 handles: &mut Vec<zng_var::VarHandle>,
323 ) {
324 handles.push(img.hook(zng_clone_move::clmv!(signal, |_| {
325 signal.modify(|a| {
328 let _ = a.value_mut();
329 });
330 true
331 })));
332 let i = out.len();
333 out.push((img.clone(), 0));
334 img.with(move |img| {
335 for entry in img.entries.iter() {
336 Self::flat_entries_recursive_init(entry.clone(), out, signal.clone(), handles);
337 }
338 let len = out.len() - i;
339 out[i].1 = len;
340 });
341 }
342
343 pub fn entry_kind(&self) -> ImageEntryKind {
345 match &self.data.meta.parent {
346 Some(p) => p.kind.clone(),
347 None => ImageEntryKind::Page,
348 }
349 }
350
351 pub fn entry_index(&self) -> usize {
353 match &self.data.meta.parent {
354 Some(p) => p.index,
355 None => 0,
356 }
357 }
358
359 pub fn with_best_reduce<R>(&self, size: PxSize, visit: impl FnOnce(&ImageEntry) -> R) -> Option<R> {
363 fn cmp(target_size: PxSize, a: PxSize, b: PxSize) -> std::cmp::Ordering {
364 let target_ratio = target_size.width.0 as f32 / target_size.height.0 as f32;
365 let a_ratio = a.width.0 as f32 / b.height.0 as f32;
366 let b_ratio = b.width.0 as f32 / b.height.0 as f32;
367
368 let a_distortion = (target_ratio - a_ratio).abs();
369 let b_distortion = (target_ratio - b_ratio).abs();
370
371 if !about_eq(a_distortion, b_distortion, 0.01) && a_distortion < b_distortion {
372 return std::cmp::Ordering::Less;
374 }
375
376 let a_dist = a - target_size;
377 let b_dist = b - target_size;
378
379 if a_dist.width < Px(0) || a_dist.height < Px(0) {
380 if b_dist.width < Px(0) || b_dist.height < Px(0) {
381 a_dist.width.abs().cmp(&b_dist.width.abs())
383 } else {
384 std::cmp::Ordering::Greater
386 }
387 } else if b_dist.width < Px(0) || b_dist.height < Px(0) {
388 std::cmp::Ordering::Less
390 } else {
391 a_dist.width.cmp(&b_dist.width)
393 }
394 }
395
396 let mut best_i = usize::MAX;
397 let mut best_size = PxSize::zero();
398
399 if self.is_loaded() {
400 best_i = self.entries.len();
401 best_size = self.size();
402 }
403
404 for (i, entry) in self.entries.iter().enumerate() {
405 entry.with(|e| {
406 if e.is_loaded() {
407 let entry_size = e.size();
408 if cmp(size, entry_size, best_size).is_lt() {
409 best_i = i;
410 best_size = entry_size;
411 }
412 }
413 })
414 }
415
416 if best_i == usize::MAX {
417 None
419 } else if best_i == self.entries.len() {
420 Some(visit(self))
421 } else {
422 Some(self.entries[best_i].with(visit))
423 }
424 }
425
426 pub fn view_handle(&self) -> &ViewImageHandle {
428 &self.handle
429 }
430
431 pub fn layout_size(&self, ctx: &LayoutMetrics) -> PxSize {
439 self.calc_size(ctx, PxDensity2d::splat(ctx.screen_density()), false)
440 }
441
442 pub fn calc_size(&self, ctx: &LayoutMetrics, fallback_density: PxDensity2d, ignore_image_density: bool) -> PxSize {
452 let dpi = if ignore_image_density {
453 fallback_density
454 } else {
455 self.density().unwrap_or(fallback_density)
456 };
457
458 let s_density = ctx.screen_density();
459 let mut size = self.size();
460
461 let fct = ctx.scale_factor().0;
462 size.width *= (s_density.ppcm() / dpi.width.ppcm()) * fct;
463 size.height *= (s_density.ppcm() / dpi.height.ppcm()) * fct;
464
465 size
466 }
467
468 pub fn pixels(&self) -> Option<IpcBytes> {
472 if self.is_loaded() { Some(self.data.pixels.clone()) } else { None }
473 }
474
475 pub fn partial_pixels(&self) -> Option<IpcBytes> {
482 if self.is_loading() && self.data.partial.is_some() {
483 Some(self.data.pixels.clone())
484 } else {
485 None
486 }
487 }
488
489 fn actual_pixels_and_size(&self) -> Option<(PxSize, IpcBytes)> {
490 match (self.partial_pixels(), self.partial_size()) {
491 (Some(b), Some(s)) => Some((s, b)),
492 _ => Some((self.size(), self.pixels()?)),
493 }
494 }
495
496 pub fn copy_pixels(&self, rect: PxRect) -> Option<(PxRect, IpcBytesMut)> {
504 self.actual_pixels_and_size().and_then(|(size, pixels)| {
505 let area = PxRect::from_size(size).intersection(&rect).unwrap_or_default();
506 if area.size.width.0 == 0 || area.size.height.0 == 0 {
507 Some((area, IpcBytes::new_mut_blocking(0).unwrap()))
508 } else {
509 let x = area.origin.x.0 as usize;
510 let y = area.origin.y.0 as usize;
511 let width = area.size.width.0 as usize;
512 let height = area.size.height.0 as usize;
513 let pixel = if self.is_mask() { 1 } else { 4 };
514 let mut bytes = IpcBytes::new_mut_blocking(width * height * pixel).ok()?;
515 let mut write = &mut bytes[..];
516 let row_stride = self.size().width.0 as usize * pixel;
517 for l in y..y + height {
518 let line_start = l * row_stride + x * pixel;
519 let line_end = line_start + width * pixel;
520 let line = &pixels[line_start..line_end];
521 write[..line.len()].copy_from_slice(line);
522 write = &mut write[line.len()..];
523 }
524 Some((area, bytes))
525 }
526 })
527 }
528
529 pub async fn encode(&self, format: Txt) -> std::result::Result<IpcBytes, EncodeError> {
537 self.encode_with_entries(&[], format).await
538 }
539
540 pub async fn encode_with_entries(
544 &self,
545 entries: &[(ImageEntry, ImageEntryKind)],
546 format: Txt,
547 ) -> std::result::Result<IpcBytes, EncodeError> {
548 if self.is_loading() {
549 return Err(EncodeError::Loading);
550 } else if let Some(e) = self.error() {
551 return Err(e.into());
552 } else if self.handle.is_dummy() {
553 return Err(EncodeError::Dummy);
554 }
555
556 let mut r = ImageEncodeRequest::new(self.handle.image_id(), format);
557 r.entries = entries.iter().map(|(img, kind)| (img.handle.image_id(), kind.clone())).collect();
558
559 match VIEW_PROCESS.encode_image(r).recv().await {
560 Ok(r) => r,
561 Err(_) => Err(EncodeError::Disconnected),
562 }
563 }
564
565 pub async fn save(&self, path: impl Into<PathBuf>) -> io::Result<()> {
576 let path = path.into();
577 if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
578 self.save_impl(&[], Txt::from_str(ext), path).await
579 } else {
580 Err(io::Error::new(
581 io::ErrorKind::InvalidInput,
582 "could not determinate image format from path extension",
583 ))
584 }
585 }
586
587 pub async fn save_with_format(&self, format: impl Into<Txt>, path: impl Into<PathBuf>) -> io::Result<()> {
597 self.save_impl(&[], format.into(), path.into()).await
598 }
599
600 pub async fn save_with_entries(
606 &self,
607 entries: &[(ImageEntry, ImageEntryKind)],
608 format: impl Into<Txt>,
609 path: impl Into<PathBuf>,
610 ) -> io::Result<()> {
611 self.save_impl(entries, format.into(), path.into()).await
612 }
613
614 async fn save_impl(&self, entries: &[(ImageEntry, ImageEntryKind)], format: Txt, path: PathBuf) -> io::Result<()> {
615 let data = self.encode_with_entries(entries, format).await.map_err(io::Error::other)?;
616 task::wait(move || fs::write(path, &data[..])).await
617 }
618
619 pub fn insert_entry(&mut self, entry: ImageVar) {
629 let id = self.handle.image_id();
630 let (i, p) = entry.with(|i| (i.entry_index(), i.data.meta.parent.clone()));
631 let i = self
632 .entries
633 .iter()
634 .position(|v| {
635 let entry_i = v.with(|i| i.entry_index());
636 entry_i > i
637 })
638 .unwrap_or(self.entries.len());
639
640 if let Some(p) = &p {
641 if p.parent != id {
642 tracing::warn!("replacing entry parent from {:?} tp {:?}", p.parent, id);
643 entry.modify(move |e| {
644 if let Some(p) = &mut e.data.meta.parent {
645 p.parent = id;
646 } else {
647 e.data.meta.parent = Some(ImageEntryMetadata::new(id, i, ImageEntryKind::Page));
648 }
649 });
650 }
651 } else {
652 entry.modify(move |e| e.data.meta.parent = Some(ImageEntryMetadata::new(id, i, ImageEntryKind::Page)));
653 }
654
655 self.entries.insert(i, VarEq(entry));
656 }
657
658 pub fn extensions(&self) -> &[(ApiExtensionId, ApiExtensionPayload)] {
660 &self.data.meta.extensions
661 }
662
663 pub fn cur_hotspot(&self) -> Option<PxPoint> {
668 let ext_id = VIEW_PROCESS.extension_id("image_cur").ok()??;
669 for (id, data) in self.extensions() {
670 if *id == ext_id {
671 return data.deserialize::<PxPoint>().ok();
672 }
673 }
674 None
675 }
676
677 pub fn exif(&self) -> Option<&[u8]> {
685 let ext_id = VIEW_PROCESS.extension_id("image_meta_exif").ok()??;
686 for (id, data) in self.extensions() {
687 if *id == ext_id {
688 return Some(&data.0[..]);
689 }
690 }
691 None
692 }
693
694 pub fn icc_profile(&self) -> Option<&[u8]> {
702 let ext_id = VIEW_PROCESS.extension_id("image_meta_icc").ok()??;
703 for (id, data) in self.extensions() {
704 if *id == ext_id {
705 return Some(&data.0[..]);
706 }
707 }
708 None
709 }
710}
711impl zng_app::render::Img for ImageEntry {
712 fn renderer_id(&self, renderer: &ViewRenderer) -> ImageTextureId {
713 if self.is_loaded() {
714 let mut img = self.img_mut.lock();
715 let rms = &mut img.render_ids;
716 if let Some(rm) = rms.iter().find(|k| &k.renderer == renderer) {
717 return rm.image_id;
718 }
719
720 let key = match renderer.use_image(&self.handle) {
721 Ok(k) => {
722 if k == ImageTextureId::INVALID {
723 tracing::error!("received INVALID from `use_image`");
724 return k;
725 }
726 k
727 }
728 Err(_) => {
729 tracing::debug!("respawned `add_image`, will return INVALID");
730 return ImageTextureId::INVALID;
731 }
732 };
733
734 rms.push(RenderImage {
735 image_id: key,
736 renderer: renderer.clone(),
737 });
738 key
739 } else {
740 ImageTextureId::INVALID
741 }
742 }
743
744 fn size(&self) -> PxSize {
745 self.size()
746 }
747}
748
749struct RenderImage {
750 image_id: ImageTextureId,
751 renderer: ViewRenderer,
752}
753impl Drop for RenderImage {
754 fn drop(&mut self) {
755 let _ = self.renderer.delete_image_use(self.image_id);
757 }
758}
759impl fmt::Debug for RenderImage {
760 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
761 fmt::Debug::fmt(&self.image_id, f)
762 }
763}
764
765#[derive(Clone, Copy)]
773pub struct ImageHash([u8; 32]);
774impl ImageHash {
775 pub fn compute(data: &[u8]) -> Self {
777 let mut h = Self::hasher();
778 h.update(data);
779 h.finish()
780 }
781
782 pub fn hasher() -> ImageHasher {
784 ImageHasher::default()
785 }
786}
787impl fmt::Debug for ImageHash {
788 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
789 if f.alternate() {
790 f.debug_tuple("ImageHash").field(&self.0).finish()
791 } else {
792 use base64::*;
793 write!(f, "{}", base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(self.0))
794 }
795 }
796}
797impl fmt::Display for ImageHash {
798 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
799 write!(f, "{self:?}")
800 }
801}
802impl std::hash::Hash for ImageHash {
803 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
804 let h64 = [
805 self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], self.0[6], self.0[7],
806 ];
807 state.write_u64(u64::from_ne_bytes(h64))
808 }
809}
810impl PartialEq for ImageHash {
811 fn eq(&self, other: &Self) -> bool {
812 self.0 == other.0
813 }
814}
815impl Eq for ImageHash {}
816
817pub struct ImageHasher(sha2::Sha512_256);
819impl Default for ImageHasher {
820 fn default() -> Self {
821 use sha2::Digest;
822 Self(sha2::Sha512_256::new())
823 }
824}
825impl ImageHasher {
826 pub fn new() -> Self {
828 Self::default()
829 }
830
831 pub fn update(&mut self, data: &[u8]) {
833 use sha2::Digest;
834
835 const NUM_SAMPLES: usize = 1000;
838 const SAMPLE_CHUNK_SIZE: usize = 1024;
839
840 let total_size = data.len();
841 if total_size == 0 {
842 return;
843 }
844 if total_size < 1000 * 1000 * 4 {
845 return self.0.update(data);
846 }
847
848 let step_size = total_size.checked_div(NUM_SAMPLES).unwrap_or(total_size);
849 for n in 0..NUM_SAMPLES {
850 let start_index = n * step_size;
851 if start_index >= total_size {
852 break;
853 }
854 let end_index = (start_index + SAMPLE_CHUNK_SIZE).min(total_size);
855 let s = &data[start_index..end_index];
856 self.0.update(s);
857 }
858 }
859
860 pub fn finish(self) -> ImageHash {
862 use sha2::Digest;
863 ImageHash(self.0.finalize().as_slice().try_into().unwrap())
867 }
868}
869impl std::hash::Hasher for ImageHasher {
870 fn finish(&self) -> u64 {
871 tracing::warn!("Hasher::finish called for ImageHasher");
872
873 use sha2::Digest;
874 let hash = self.0.clone().finalize();
875 u64::from_le_bytes(hash[..8].try_into().unwrap())
876 }
877
878 fn write(&mut self, bytes: &[u8]) {
879 self.update(bytes);
880 }
881}
882
883pub(crate) type RenderFn = Arc<Box<dyn Fn(&ImageRenderArgs) -> Box<dyn ImageRenderWindowRoot> + Send + Sync>>;
885
886#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
890#[non_exhaustive]
891pub struct ImageRenderArgs {
892 pub parent: Option<WindowId>,
894}
895impl ImageRenderArgs {
896 pub fn new(parent: WindowId) -> Self {
898 Self { parent: Some(parent) }
899 }
900}
901
902#[derive(Clone)]
904#[non_exhaustive]
905pub enum ImageSource {
906 Read(PathBuf),
910 #[cfg(feature = "http")]
916 Download(zng_task::http::Uri, Option<Txt>),
917 Data(ImageHash, IpcBytes, ImageDataFormat),
925
926 Render(RenderFn, Option<ImageRenderArgs>),
932
933 Image(ImageVar),
937
938 Entries {
942 primary: Box<ImageSource>,
944 entries: Vec<(ImageEntryKind, ImageSource)>,
946 },
947}
948impl ImageSource {
949 pub fn from_data(data: IpcBytes, format: ImageDataFormat) -> Self {
951 let mut hasher = ImageHasher::default();
952 hasher.update(&data[..]);
953 let hash = hasher.finish();
954 Self::Data(hash, data, format)
955 }
956
957 pub fn hash128(&self, options: &ImageOptions) -> Option<ImageHash> {
959 match self {
960 ImageSource::Read(p) => Some(Self::hash128_read(p, options)),
961 #[cfg(feature = "http")]
962 ImageSource::Download(u, a) => Some(Self::hash128_download(u, a, options)),
963 ImageSource::Data(h, _, _) => Some(Self::hash128_data(*h, options)),
964 ImageSource::Render(rfn, args) => Some(Self::hash128_render(rfn, args, options)),
965 ImageSource::Image(_) => None,
966 ImageSource::Entries { .. } => None,
967 }
968 }
969
970 pub fn hash128_data(data_hash: ImageHash, options: &ImageOptions) -> ImageHash {
974 if options.downscale.is_some() || options.mask.is_some() || !options.entries.is_empty() {
975 use std::hash::Hash;
976 let mut h = ImageHash::hasher();
977 data_hash.0.hash(&mut h);
978 options.downscale.hash(&mut h);
979 options.mask.hash(&mut h);
980 options.entries.hash(&mut h);
981 h.finish()
982 } else {
983 data_hash
984 }
985 }
986
987 pub fn hash128_read(path: &Path, options: &ImageOptions) -> ImageHash {
991 use std::hash::Hash;
992 let mut h = ImageHash::hasher();
993 0u8.hash(&mut h);
994 path.hash(&mut h);
995 options.downscale.hash(&mut h);
996 options.mask.hash(&mut h);
997 options.entries.hash(&mut h);
998 h.finish()
999 }
1000
1001 #[cfg(feature = "http")]
1005 pub fn hash128_download(uri: &zng_task::http::Uri, accept: &Option<Txt>, options: &ImageOptions) -> ImageHash {
1006 use std::hash::Hash;
1007 let mut h = ImageHash::hasher();
1008 1u8.hash(&mut h);
1009 uri.hash(&mut h);
1010 accept.hash(&mut h);
1011 options.downscale.hash(&mut h);
1012 options.mask.hash(&mut h);
1013 options.entries.hash(&mut h);
1014 h.finish()
1015 }
1016
1017 pub fn hash128_render(rfn: &RenderFn, args: &Option<ImageRenderArgs>, options: &ImageOptions) -> ImageHash {
1023 use std::hash::Hash;
1024 let mut h = ImageHash::hasher();
1025 2u8.hash(&mut h);
1026 (Arc::as_ptr(rfn) as usize).hash(&mut h);
1027 args.hash(&mut h);
1028 options.downscale.hash(&mut h);
1029 options.mask.hash(&mut h);
1030 options.entries.hash(&mut h);
1031 h.finish()
1032 }
1033}
1034
1035impl ImageSource {
1036 pub fn render<F, R>(new_img: F) -> Self
1064 where
1065 F: Fn(&ImageRenderArgs) -> R + Send + Sync + 'static,
1066 R: ImageRenderWindowRoot,
1067 {
1068 let window = IMAGES_SV.read().render_windows();
1069 Self::Render(
1070 Arc::new(Box::new(move |args| {
1071 if let Some(parent) = args.parent {
1072 window.set_parent_in_window_context(parent);
1073 }
1074 let r = new_img(args);
1075 window.enable_frame_capture_in_window_context(None);
1076 Box::new(r)
1077 })),
1078 None,
1079 )
1080 }
1081
1082 pub fn render_node(render_mode: RenderMode, render: impl Fn(&ImageRenderArgs) -> UiNode + Send + Sync + 'static) -> Self {
1114 let window = IMAGES_SV.read().render_windows();
1115 Self::Render(
1116 Arc::new(Box::new(move |args| {
1117 if let Some(parent) = args.parent {
1118 window.set_parent_in_window_context(parent);
1119 }
1120 let node = render(args);
1121 window.enable_frame_capture_in_window_context(None);
1122 window.new_window_root(node, render_mode)
1123 })),
1124 None,
1125 )
1126 }
1127}
1128
1129impl ImageSource {
1130 pub fn flood(size: impl Into<PxSize>, color: impl Into<Rgba>, density: Option<PxDensity2d>) -> Self {
1132 Self::flood_impl(size.into(), color.into(), density)
1133 }
1134 fn flood_impl(size: PxSize, color: Rgba, density: Option<PxDensity2d>) -> Self {
1135 let pixels = size.width.0 as usize * size.height.0 as usize;
1136 let bgra = color.to_bgra_bytes();
1137 let mut b = IpcBytes::new_mut_blocking(pixels * 4).expect("cannot allocate IpcBytes");
1138 for b in b.chunks_exact_mut(4) {
1139 b.copy_from_slice(&bgra);
1140 }
1141 Self::from_data(
1142 b.finish_blocking().expect("cannot allocate IpcBytes"),
1143 ImageDataFormat::Bgra8 {
1144 size,
1145 density,
1146 original_color_type: ColorType::RGBA8,
1147 },
1148 )
1149 }
1150
1151 pub fn linear_vertical(
1153 size: impl Into<PxSize>,
1154 stops: impl Into<GradientStops>,
1155 density: Option<PxDensity2d>,
1156 mask: Option<ImageMaskMode>,
1157 ) -> Self {
1158 Self::linear_vertical_impl(size.into(), stops.into(), density, mask)
1159 }
1160 fn linear_vertical_impl(size: PxSize, stops: GradientStops, density: Option<PxDensity2d>, mask: Option<ImageMaskMode>) -> Self {
1161 assert!(size.width > Px(0));
1162 assert!(size.height > Px(0));
1163
1164 let mut line = PxLine::new(PxPoint::splat(Px(0)), PxPoint::new(Px(0), size.height));
1165 let mut render_stops = vec![];
1166
1167 LAYOUT.with_root_context(LayoutPassId::new(), LayoutMetrics::new(1.fct(), size, Px(14)), || {
1168 stops.layout_linear(LayoutAxis::Y, ExtendMode::Clamp, &mut line, &mut render_stops);
1169 });
1170 let line_a = line.start.y.0 as f32;
1171 let line_b = line.end.y.0 as f32;
1172
1173 let mut bgra = Vec::with_capacity(size.height.0 as usize);
1174 let mut render_stops = render_stops.into_iter();
1175 let mut stop_a = render_stops.next().unwrap();
1176 let mut stop_b = render_stops.next().unwrap();
1177 'outer: for y in 0..size.height.0 {
1178 let yf = y as f32;
1179 let yf = (yf - line_a) / (line_b - line_a);
1180 if yf < stop_a.offset {
1181 bgra.push(stop_a.color.to_bgra_bytes());
1183 continue;
1184 }
1185 while yf > stop_b.offset {
1186 if let Some(next_b) = render_stops.next() {
1187 stop_a = stop_b;
1189 stop_b = next_b;
1190 } else {
1191 for _ in y..size.height.0 {
1193 bgra.push(stop_b.color.to_bgra_bytes());
1194 }
1195 break 'outer;
1196 }
1197 }
1198
1199 let yf = (yf - stop_a.offset) / (stop_b.offset - stop_a.offset);
1201 let sample = stop_a.color.lerp(&stop_b.color, yf.fct());
1202 bgra.push(sample.to_bgra_bytes());
1203 }
1204
1205 match mask {
1206 Some(m) => {
1207 let len = size.width.0 as usize * size.height.0 as usize;
1208 let mut data = Vec::with_capacity(len);
1209
1210 for y in 0..size.height.0 {
1211 let c = bgra[y as usize];
1212 let c = match m {
1213 ImageMaskMode::A => c[3],
1214 ImageMaskMode::B => c[0],
1215 ImageMaskMode::G => c[1],
1216 ImageMaskMode::R => c[2],
1217 ImageMaskMode::Luminance => {
1218 let hsla = Hsla::from(Rgba::new(c[2], c[1], c[0], c[3]));
1219 (hsla.lightness * 255.0).round().clamp(0.0, 255.0) as u8
1220 }
1221 _ => unreachable!(),
1222 };
1223 for _x in 0..size.width.0 {
1224 data.push(c);
1225 }
1226 }
1227
1228 Self::from_data(
1229 IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"),
1230 ImageDataFormat::A8 { size },
1231 )
1232 }
1233 None => {
1234 let len = size.width.0 as usize * size.height.0 as usize * 4;
1235 let mut data = Vec::with_capacity(len);
1236
1237 for y in 0..size.height.0 {
1238 let c = bgra[y as usize];
1239 for _x in 0..size.width.0 {
1240 data.extend_from_slice(&c);
1241 }
1242 }
1243
1244 Self::from_data(
1245 IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"),
1246 ImageDataFormat::Bgra8 {
1247 size,
1248 density,
1249 original_color_type: ColorType::RGBA8,
1250 },
1251 )
1252 }
1253 }
1254 }
1255
1256 pub fn linear_horizontal(
1258 size: impl Into<PxSize>,
1259 stops: impl Into<GradientStops>,
1260 density: Option<PxDensity2d>,
1261 mask: Option<ImageMaskMode>,
1262 ) -> Self {
1263 Self::linear_horizontal_impl(size.into(), stops.into(), density, mask)
1264 }
1265 fn linear_horizontal_impl(size: PxSize, stops: GradientStops, density: Option<PxDensity2d>, mask: Option<ImageMaskMode>) -> Self {
1266 assert!(size.width > Px(0));
1267 assert!(size.height > Px(0));
1268
1269 let mut line = PxLine::new(PxPoint::splat(Px(0)), PxPoint::new(size.width, Px(0)));
1270 let mut render_stops = vec![];
1271 LAYOUT.with_root_context(LayoutPassId::new(), LayoutMetrics::new(1.fct(), size, Px(14)), || {
1272 stops.layout_linear(LayoutAxis::Y, ExtendMode::Clamp, &mut line, &mut render_stops);
1273 });
1274 let line_a = line.start.x.0 as f32;
1275 let line_b = line.end.x.0 as f32;
1276
1277 let mut bgra = Vec::with_capacity(size.width.0 as usize);
1278 let mut render_stops = render_stops.into_iter();
1279 let mut stop_a = render_stops.next().unwrap();
1280 let mut stop_b = render_stops.next().unwrap();
1281 'outer: for x in 0..size.width.0 {
1282 let xf = x as f32;
1283 let xf = (xf - line_a) / (line_b - line_a);
1284 if xf < stop_a.offset {
1285 bgra.push(stop_a.color.to_bgra_bytes());
1287 continue;
1288 }
1289 while xf > stop_b.offset {
1290 if let Some(next_b) = render_stops.next() {
1291 stop_a = stop_b;
1293 stop_b = next_b;
1294 } else {
1295 for _ in x..size.width.0 {
1297 bgra.push(stop_b.color.to_bgra_bytes());
1298 }
1299 break 'outer;
1300 }
1301 }
1302
1303 let xf = (xf - stop_a.offset) / (stop_b.offset - stop_a.offset);
1305 let sample = stop_a.color.lerp(&stop_b.color, xf.fct());
1306 bgra.push(sample.to_bgra_bytes());
1307 }
1308
1309 match mask {
1310 Some(m) => {
1311 let len = size.width.0 as usize * size.height.0 as usize;
1312 let mut data = Vec::with_capacity(len);
1313
1314 for _y in 0..size.height.0 {
1315 for c in &bgra {
1316 let c = match m {
1317 ImageMaskMode::A => c[3],
1318 ImageMaskMode::B => c[0],
1319 ImageMaskMode::G => c[1],
1320 ImageMaskMode::R => c[2],
1321 ImageMaskMode::Luminance => {
1322 let hsla = Hsla::from(Rgba::new(c[2], c[1], c[0], c[3]));
1323 (hsla.lightness * 255.0).round().clamp(0.0, 255.0) as u8
1324 }
1325 _ => unreachable!(),
1326 };
1327 data.push(c);
1328 }
1329 }
1330
1331 Self::from_data(
1332 IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"),
1333 ImageDataFormat::A8 { size },
1334 )
1335 }
1336 None => {
1337 let len = size.width.0 as usize * size.height.0 as usize * 4;
1338 let mut data = Vec::with_capacity(len);
1339
1340 for _y in 0..size.height.0 {
1341 for c in &bgra {
1342 data.extend_from_slice(c);
1343 }
1344 }
1345
1346 Self::from_data(
1347 IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"),
1348 ImageDataFormat::Bgra8 {
1349 size,
1350 density,
1351 original_color_type: ColorType::RGBA8,
1352 },
1353 )
1354 }
1355 }
1356 }
1357}
1358
1359impl PartialEq for ImageSource {
1360 fn eq(&self, other: &Self) -> bool {
1361 match (self, other) {
1362 (Self::Read(l), Self::Read(r)) => l == r,
1363 #[cfg(feature = "http")]
1364 (Self::Download(lu, la), Self::Download(ru, ra)) => lu == ru && la == ra,
1365 (Self::Render(lf, la), Self::Render(rf, ra)) => Arc::ptr_eq(lf, rf) && la == ra,
1366 (Self::Image(l), Self::Image(r)) => l.var_eq(r),
1367 (l, r) => {
1368 let l_hash = match l {
1369 ImageSource::Data(h, _, _) => h,
1370 _ => return false,
1371 };
1372 let r_hash = match r {
1373 ImageSource::Data(h, _, _) => h,
1374 _ => return false,
1375 };
1376
1377 l_hash == r_hash
1378 }
1379 }
1380 }
1381}
1382impl fmt::Debug for ImageSource {
1383 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1384 if f.alternate() {
1385 write!(f, "ImageSource::")?;
1386 }
1387 match self {
1388 ImageSource::Read(p) => f.debug_tuple("Read").field(p).finish(),
1389 #[cfg(feature = "http")]
1390 ImageSource::Download(u, a) => f.debug_tuple("Download").field(u).field(a).finish(),
1391 ImageSource::Data(key, bytes, fmt) => f.debug_tuple("Data").field(key).field(bytes).field(fmt).finish(),
1392
1393 ImageSource::Render(_, _) => write!(f, "Render(_, _)"),
1394 ImageSource::Image(_) => write!(f, "Image(_)"),
1395 ImageSource::Entries { primary, entries } => f
1396 .debug_struct("Entries")
1397 .field("primary", primary)
1398 .field("entries", entries)
1399 .finish(),
1400 }
1401 }
1402}
1403
1404#[cfg(feature = "http")]
1405impl_from_and_into_var! {
1406 fn from(uri: zng_task::http::Uri) -> ImageSource {
1407 ImageSource::Download(uri, None)
1408 }
1409 fn from((uri, accept): (zng_task::http::Uri, &'static str)) -> ImageSource {
1411 ImageSource::Download(uri, Some(accept.into()))
1412 }
1413
1414 fn from(s: &str) -> ImageSource {
1420 use zng_task::http::*;
1421 if let Ok(uri) = Uri::try_from(s)
1422 && let Some(scheme) = uri.scheme()
1423 {
1424 if scheme == &uri::Scheme::HTTPS || scheme == &uri::Scheme::HTTP {
1425 return ImageSource::Download(uri, None);
1426 } else if scheme.as_str() == "file" {
1427 return PathBuf::from(uri.path()).into();
1428 }
1429 }
1430 PathBuf::from(s).into()
1431 }
1432}
1433
1434#[cfg(not(feature = "http"))]
1435impl_from_and_into_var! {
1436 fn from(s: &str) -> ImageSource {
1440 PathBuf::from(s).into()
1441 }
1442}
1443
1444impl_from_and_into_var! {
1445 fn from(image: ImageVar) -> ImageSource {
1446 ImageSource::Image(image)
1447 }
1448 fn from(path: PathBuf) -> ImageSource {
1449 ImageSource::Read(path)
1450 }
1451 fn from(path: &Path) -> ImageSource {
1452 path.to_owned().into()
1453 }
1454
1455 fn from(s: String) -> ImageSource {
1457 s.as_str().into()
1458 }
1459 fn from(s: Txt) -> ImageSource {
1461 s.as_str().into()
1462 }
1463 fn from(data: &[u8]) -> ImageSource {
1467 ImageSource::Data(
1468 ImageHash::compute(data),
1469 IpcBytes::from_slice_blocking(data).expect("cannot allocate IpcBytes"),
1470 ImageDataFormat::Unknown,
1471 )
1472 }
1473 fn from<const N: usize>(data: &[u8; N]) -> ImageSource {
1477 (&data[..]).into()
1478 }
1479 fn from(data: IpcBytes) -> ImageSource {
1483 ImageSource::Data(ImageHash::compute(&data[..]), data, ImageDataFormat::Unknown)
1484 }
1485 fn from(data: Vec<u8>) -> ImageSource {
1489 IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes").into()
1490 }
1491 fn from<F: Into<ImageDataFormat>>((data, format): (&[u8], F)) -> ImageSource {
1493 ImageSource::Data(
1494 ImageHash::compute(data),
1495 IpcBytes::from_slice_blocking(data).expect("cannot allocate IpcBytes"),
1496 format.into(),
1497 )
1498 }
1499 fn from<F: Into<ImageDataFormat>, const N: usize>((data, format): (&[u8; N], F)) -> ImageSource {
1501 (&data[..], format).into()
1502 }
1503 fn from<F: Into<ImageDataFormat>>((data, format): (Vec<u8>, F)) -> ImageSource {
1505 (IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"), format).into()
1506 }
1507 fn from<F: Into<ImageDataFormat>>((data, format): (IpcBytes, F)) -> ImageSource {
1509 ImageSource::Data(ImageHash::compute(&data[..]), data, format.into())
1510 }
1511}
1512
1513#[derive(Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1517pub enum ImageCacheMode {
1518 Ignore,
1520 Cache,
1522 Retry,
1524 Reload,
1528}
1529impl fmt::Debug for ImageCacheMode {
1530 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1531 if f.alternate() {
1532 write!(f, "CacheMode::")?;
1533 }
1534 match self {
1535 Self::Ignore => write!(f, "Ignore"),
1536 Self::Cache => write!(f, "Cache"),
1537 Self::Retry => write!(f, "Retry"),
1538 Self::Reload => write!(f, "Reload"),
1539 }
1540 }
1541}
1542impl_from_and_into_var! {
1543 fn from(cache: bool) -> ImageCacheMode {
1544 if cache { ImageCacheMode::Cache } else { ImageCacheMode::Ignore }
1545 }
1546}
1547
1548#[derive(Clone)]
1550pub enum ImageSourceFilter<U> {
1551 BlockAll,
1553 AllowAll,
1555 Custom(Arc<dyn Fn(&U) -> bool + Send + Sync>),
1557}
1558impl<U> ImageSourceFilter<U> {
1559 pub fn custom(allow: impl Fn(&U) -> bool + Send + Sync + 'static) -> Self {
1563 Self::Custom(Arc::new(allow))
1564 }
1565
1566 pub fn and(self, other: Self) -> Self
1575 where
1576 U: 'static,
1577 {
1578 use ImageSourceFilter::*;
1579 match (self, other) {
1580 (BlockAll, _) | (_, BlockAll) => BlockAll,
1581 (AllowAll, _) | (_, AllowAll) => AllowAll,
1582 (Custom(c0), Custom(c1)) => Custom(Arc::new(move |u| c0(u) && c1(u))),
1583 }
1584 }
1585
1586 pub fn or(self, other: Self) -> Self
1595 where
1596 U: 'static,
1597 {
1598 use ImageSourceFilter::*;
1599 match (self, other) {
1600 (AllowAll, _) | (_, AllowAll) => AllowAll,
1601 (BlockAll, _) | (_, BlockAll) => BlockAll,
1602 (Custom(c0), Custom(c1)) => Custom(Arc::new(move |u| c0(u) || c1(u))),
1603 }
1604 }
1605
1606 pub fn allows(&self, item: &U) -> bool {
1608 match self {
1609 ImageSourceFilter::BlockAll => false,
1610 ImageSourceFilter::AllowAll => true,
1611 ImageSourceFilter::Custom(f) => f(item),
1612 }
1613 }
1614}
1615impl<U> fmt::Debug for ImageSourceFilter<U> {
1616 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1617 match self {
1618 Self::BlockAll => write!(f, "BlockAll"),
1619 Self::AllowAll => write!(f, "AllowAll"),
1620 Self::Custom(_) => write!(f, "Custom(_)"),
1621 }
1622 }
1623}
1624impl<U: 'static> ops::BitAnd for ImageSourceFilter<U> {
1625 type Output = Self;
1626
1627 fn bitand(self, rhs: Self) -> Self::Output {
1628 self.and(rhs)
1629 }
1630}
1631impl<U: 'static> ops::BitOr for ImageSourceFilter<U> {
1632 type Output = Self;
1633
1634 fn bitor(self, rhs: Self) -> Self::Output {
1635 self.or(rhs)
1636 }
1637}
1638impl<U: 'static> ops::BitAndAssign for ImageSourceFilter<U> {
1639 fn bitand_assign(&mut self, rhs: Self) {
1640 *self = mem::replace(self, Self::BlockAll).and(rhs);
1641 }
1642}
1643impl<U: 'static> ops::BitOrAssign for ImageSourceFilter<U> {
1644 fn bitor_assign(&mut self, rhs: Self) {
1645 *self = mem::replace(self, Self::BlockAll).or(rhs);
1646 }
1647}
1648impl<U> PartialEq for ImageSourceFilter<U> {
1649 fn eq(&self, other: &Self) -> bool {
1650 match (self, other) {
1651 (Self::Custom(l0), Self::Custom(r0)) => Arc::ptr_eq(l0, r0),
1652 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
1653 }
1654 }
1655}
1656
1657pub type PathFilter = ImageSourceFilter<PathBuf>;
1667impl PathFilter {
1668 pub fn allow_dir(dir: impl AsRef<Path>) -> Self {
1670 let dir = absolute_path(dir.as_ref(), || env::current_dir().expect("could not access current dir"), true);
1671 PathFilter::custom(move |r| r.starts_with(&dir))
1672 }
1673
1674 pub fn allow_ext(ext: impl Into<std::ffi::OsString>) -> Self {
1676 let ext = ext.into();
1677 PathFilter::custom(move |r| r.extension().map(|e| e == ext).unwrap_or(false))
1678 }
1679
1680 pub fn allow_current_dir() -> Self {
1688 PathFilter::custom(|r| env::current_dir().map(|d| r.starts_with(d)).unwrap_or(false))
1689 }
1690
1691 pub fn allow_exe_dir() -> Self {
1693 if let Ok(mut p) = env::current_exe().and_then(dunce::canonicalize)
1694 && p.pop()
1695 {
1696 return Self::allow_dir(p);
1697 }
1698
1699 Self::custom(|_| false)
1701 }
1702
1703 pub fn allow_res() -> Self {
1707 Self::allow_dir(zng_env::res(""))
1708 }
1709}
1710
1711#[cfg(feature = "http")]
1715pub type UriFilter = ImageSourceFilter<zng_task::http::Uri>;
1716#[cfg(feature = "http")]
1717impl UriFilter {
1718 pub fn allow_host(host: impl Into<Txt>) -> Self {
1720 let host = host.into();
1721 UriFilter::custom(move |u| u.authority().map(|a| a.host() == host).unwrap_or(false))
1722 }
1723}
1724
1725impl<F: Fn(&PathBuf) -> bool + Send + Sync + 'static> From<F> for PathFilter {
1726 fn from(custom: F) -> Self {
1727 PathFilter::custom(custom)
1728 }
1729}
1730
1731#[cfg(feature = "http")]
1732impl<F: Fn(&task::http::Uri) -> bool + Send + Sync + 'static> From<F> for UriFilter {
1733 fn from(custom: F) -> Self {
1734 UriFilter::custom(custom)
1735 }
1736}
1737
1738#[derive(Clone, Debug, PartialEq)]
1740#[non_exhaustive]
1741pub struct ImageLimits {
1742 pub max_encoded_len: ByteLength,
1750 pub max_decoded_len: ByteLength,
1754
1755 pub allow_path: PathFilter,
1757
1758 #[cfg(feature = "http")]
1760 pub allow_uri: UriFilter,
1761}
1762impl ImageLimits {
1763 pub fn none() -> Self {
1765 ImageLimits {
1766 max_encoded_len: ByteLength::MAX,
1767 max_decoded_len: ByteLength::MAX,
1768 allow_path: PathFilter::AllowAll,
1769 #[cfg(feature = "http")]
1770 allow_uri: UriFilter::AllowAll,
1771 }
1772 }
1773
1774 pub fn with_max_encoded_len(mut self, max_encoded_len: impl Into<ByteLength>) -> Self {
1778 self.max_encoded_len = max_encoded_len.into();
1779 self
1780 }
1781
1782 pub fn with_max_decoded_len(mut self, max_decoded_len: impl Into<ByteLength>) -> Self {
1786 self.max_decoded_len = max_decoded_len.into();
1787 self
1788 }
1789
1790 pub fn with_allow_path(mut self, allow_path: impl Into<PathFilter>) -> Self {
1794 self.allow_path = allow_path.into();
1795 self
1796 }
1797
1798 #[cfg(feature = "http")]
1802 pub fn with_allow_uri(mut self, allow_url: impl Into<UriFilter>) -> Self {
1803 self.allow_uri = allow_url.into();
1804 self
1805 }
1806}
1807impl Default for ImageLimits {
1808 fn default() -> Self {
1812 Self {
1813 max_encoded_len: 100.megabytes(),
1814 max_decoded_len: 4096.megabytes(),
1815 allow_path: PathFilter::allow_res(),
1816 #[cfg(feature = "http")]
1817 allow_uri: UriFilter::BlockAll,
1818 }
1819 }
1820}
1821impl_from_and_into_var! {
1822 fn from(some: ImageLimits) -> Option<ImageLimits>;
1823}
1824
1825#[derive(Debug, Clone, PartialEq)]
1829#[non_exhaustive]
1830pub struct ImageOptions {
1831 pub cache_mode: ImageCacheMode,
1833 pub downscale: Option<ImageDownscaleMode>,
1835 pub mask: Option<ImageMaskMode>,
1837 pub entries: ImageEntriesMode,
1839}
1840
1841impl ImageOptions {
1842 pub fn new(
1844 cache_mode: ImageCacheMode,
1845 downscale: Option<ImageDownscaleMode>,
1846 mask: Option<ImageMaskMode>,
1847 entries: ImageEntriesMode,
1848 ) -> Self {
1849 Self {
1850 cache_mode,
1851 downscale,
1852 mask,
1853 entries,
1854 }
1855 }
1856
1857 pub fn cache() -> Self {
1859 Self::new(ImageCacheMode::Cache, None, None, ImageEntriesMode::empty())
1860 }
1861
1862 pub fn none() -> Self {
1864 Self::new(ImageCacheMode::Ignore, None, None, ImageEntriesMode::empty())
1865 }
1866}
1867
1868fn absolute_path(path: &Path, base: impl FnOnce() -> PathBuf, allow_escape: bool) -> PathBuf {
1869 if path.is_absolute() {
1870 normalize_path(path)
1871 } else {
1872 let mut dir = base();
1873 if allow_escape {
1874 dir.push(path);
1875 normalize_path(&dir)
1876 } else {
1877 dir.push(normalize_path(path));
1878 dir
1879 }
1880 }
1881}
1882fn normalize_path(path: &Path) -> PathBuf {
1886 use std::path::Component;
1887
1888 let mut components = path.components().peekable();
1889 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
1890 components.next();
1891 PathBuf::from(c.as_os_str())
1892 } else {
1893 PathBuf::new()
1894 };
1895
1896 for component in components {
1897 match component {
1898 Component::Prefix(..) => unreachable!(),
1899 Component::RootDir => {
1900 ret.push(component.as_os_str());
1901 }
1902 Component::CurDir => {}
1903 Component::ParentDir => {
1904 ret.pop();
1905 }
1906 Component::Normal(c) => {
1907 ret.push(c);
1908 }
1909 }
1910 }
1911 ret
1912}