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.handle.is_dummy() || 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 format_name(&self) -> Txt {
249 self.data.meta.format_name.clone()
250 }
251
252 pub fn original_color_type(&self) -> ColorType {
254 self.data.meta.original_color_type.clone()
255 }
256
257 pub fn color_type(&self) -> ColorType {
259 if self.is_mask() { ColorType::A8 } else { ColorType::BGRA8 }
260 }
261
262 pub fn is_opaque(&self) -> bool {
264 self.data.is_opaque
265 }
266
267 pub fn is_mask(&self) -> bool {
269 self.data.meta.is_mask
270 }
271
272 pub fn has_entries(&self) -> bool {
276 !self.entries.is_empty()
277 }
278
279 pub fn entries(&self) -> Vec<ImageVar> {
281 self.entries.iter().map(|e| e.read_only()).collect()
282 }
283
284 pub fn flat_entries(&self) -> Var<Vec<(VarEq<ImageEntry>, usize)>> {
292 let update_signal = zng_var::var(());
298
299 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 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 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 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 pub fn entry_index(&self) -> usize {
358 match &self.data.meta.parent {
359 Some(p) => p.index,
360 None => 0,
361 }
362 }
363
364 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 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 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_dist.width.abs().cmp(&b_dist.width.abs())
437 } else {
438 std::cmp::Ordering::Greater
440 }
441 } else if b_dist.width < Px(0) || b_dist.height < Px(0) {
442 std::cmp::Ordering::Less
444 } else {
445 a_dist.width.cmp(&b_dist.width)
447 }
448 }
449
450 #[deprecated = "use `best_reduce`"]
454 pub fn with_best_reduce<R>(&self, size: PxSize, visit: impl FnOnce(&ImageEntry) -> R) -> Option<R> {
455 Some(self.best_reduce(size).with(visit))
456 }
457
458 pub fn view_handle(&self) -> &ViewImageHandle {
460 &self.handle
461 }
462
463 pub fn layout_size(&self, ctx: &LayoutMetrics) -> PxSize {
471 self.calc_size(ctx, PxDensity2d::splat(ctx.screen_density()), false)
472 }
473
474 pub fn calc_size(&self, ctx: &LayoutMetrics, fallback_density: PxDensity2d, ignore_image_density: bool) -> PxSize {
484 let dpi = if ignore_image_density {
485 fallback_density
486 } else {
487 self.density().unwrap_or(fallback_density)
488 };
489
490 let s_density = ctx.screen_density();
491 let mut size = self.size();
492
493 let fct = ctx.scale_factor().0;
494 size.width *= (s_density.ppcm() / dpi.width.ppcm()) * fct;
495 size.height *= (s_density.ppcm() / dpi.height.ppcm()) * fct;
496
497 size
498 }
499
500 pub fn pixels(&self) -> Option<IpcBytes> {
504 if self.is_loaded() { Some(self.data.pixels.clone()) } else { None }
505 }
506
507 pub fn partial_pixels(&self) -> Option<IpcBytes> {
514 if self.is_loading() && self.data.partial.is_some() {
515 Some(self.data.pixels.clone())
516 } else {
517 None
518 }
519 }
520
521 fn actual_pixels_and_size(&self) -> Option<(PxSize, IpcBytes)> {
522 match (self.partial_pixels(), self.partial_size()) {
523 (Some(b), Some(s)) => Some((s, b)),
524 _ => Some((self.size(), self.pixels()?)),
525 }
526 }
527
528 pub fn copy_pixels(&self, rect: PxRect) -> Option<(PxRect, IpcBytesMut)> {
536 self.actual_pixels_and_size().and_then(|(size, pixels)| {
537 let area = PxRect::from_size(size).intersection(&rect).unwrap_or_default();
538 if area.size.width.0 == 0 || area.size.height.0 == 0 {
539 Some((area, IpcBytes::new_mut_blocking(0).unwrap()))
540 } else {
541 let x = area.origin.x.0 as usize;
542 let y = area.origin.y.0 as usize;
543 let width = area.size.width.0 as usize;
544 let height = area.size.height.0 as usize;
545 let pixel = if self.is_mask() { 1 } else { 4 };
546 let mut bytes = IpcBytes::new_mut_blocking(width * height * pixel).ok()?;
547 let mut write = &mut bytes[..];
548 let row_stride = self.size().width.0 as usize * pixel;
549 for l in y..y + height {
550 let line_start = l * row_stride + x * pixel;
551 let line_end = line_start + width * pixel;
552 let line = &pixels[line_start..line_end];
553 write[..line.len()].copy_from_slice(line);
554 write = &mut write[line.len()..];
555 }
556 Some((area, bytes))
557 }
558 })
559 }
560
561 pub async fn encode(&self, format: Txt) -> std::result::Result<IpcBytes, EncodeError> {
569 self.encode_with_entries(&[], format).await
570 }
571
572 pub async fn encode_with_entries(
576 &self,
577 entries: &[(ImageEntry, ImageEntryKind)],
578 format: Txt,
579 ) -> std::result::Result<IpcBytes, EncodeError> {
580 if self.is_loading() {
581 return Err(EncodeError::Loading);
582 } else if let Some(e) = self.error() {
583 return Err(e.into());
584 } else if self.handle.is_dummy() {
585 return Err(EncodeError::Dummy);
586 }
587
588 let mut r = ImageEncodeRequest::new(self.handle.image_id(), format);
589 r.entries = entries.iter().map(|(img, kind)| (img.handle.image_id(), kind.clone())).collect();
590
591 match VIEW_PROCESS.encode_image(r).recv().await {
592 Ok(r) => r,
593 Err(_) => Err(EncodeError::Disconnected),
594 }
595 }
596
597 pub async fn save(&self, path: impl Into<PathBuf>) -> io::Result<()> {
608 let path = path.into();
609 if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
610 self.save_impl(&[], Txt::from_str(ext), path).await
611 } else {
612 Err(io::Error::new(
613 io::ErrorKind::InvalidInput,
614 "could not determinate image format from path extension",
615 ))
616 }
617 }
618
619 pub async fn save_with_format(&self, format: impl Into<Txt>, path: impl Into<PathBuf>) -> io::Result<()> {
629 self.save_impl(&[], format.into(), path.into()).await
630 }
631
632 pub async fn save_with_entries(
638 &self,
639 entries: &[(ImageEntry, ImageEntryKind)],
640 format: impl Into<Txt>,
641 path: impl Into<PathBuf>,
642 ) -> io::Result<()> {
643 self.save_impl(entries, format.into(), path.into()).await
644 }
645
646 async fn save_impl(&self, entries: &[(ImageEntry, ImageEntryKind)], format: Txt, path: PathBuf) -> io::Result<()> {
647 let data = self.encode_with_entries(entries, format).await.map_err(io::Error::other)?;
648 task::wait(move || fs::write(path, &data[..])).await
649 }
650
651 pub fn insert_entry(&mut self, entry: ImageVar) {
661 let id = self.handle.image_id();
662 let (i, p) = entry.with(|i| (i.entry_index(), i.data.meta.parent.clone()));
663 let i = self
664 .entries
665 .iter()
666 .position(|v| {
667 let entry_i = v.with(|i| i.entry_index());
668 entry_i > i
669 })
670 .unwrap_or(self.entries.len());
671
672 if let Some(p) = &p {
673 if p.parent != id {
674 tracing::warn!("replacing entry parent from {:?} tp {:?}", p.parent, id);
675 entry.modify(move |e| {
676 if let Some(p) = &mut e.data.meta.parent {
677 p.parent = id;
678 } else {
679 e.data.meta.parent = Some(ImageEntryMetadata::new(id, i, ImageEntryKind::Page));
680 }
681 });
682 }
683 } else {
684 entry.modify(move |e| e.data.meta.parent = Some(ImageEntryMetadata::new(id, i, ImageEntryKind::Page)));
685 }
686
687 self.entries.insert(i, VarEq(entry));
688 }
689
690 pub fn extensions(&self) -> &[(ApiExtensionId, ApiExtensionPayload)] {
692 &self.data.meta.extensions
693 }
694
695 pub fn cur_hotspot(&self) -> Option<PxPoint> {
700 let ext_id = VIEW_PROCESS.extension_id("image_cur").ok()??;
701 for (id, data) in self.extensions() {
702 if *id == ext_id {
703 return data.deserialize::<PxPoint>().ok();
704 }
705 }
706 None
707 }
708
709 pub fn exif(&self) -> Option<&[u8]> {
717 let ext_id = VIEW_PROCESS.extension_id("image_meta_exif").ok()??;
718 for (id, data) in self.extensions() {
719 if *id == ext_id {
720 return Some(&data.0[..]);
721 }
722 }
723 None
724 }
725
726 pub fn icc_profile(&self) -> Option<&[u8]> {
734 let ext_id = VIEW_PROCESS.extension_id("image_meta_icc").ok()??;
735 for (id, data) in self.extensions() {
736 if *id == ext_id {
737 return Some(&data.0[..]);
738 }
739 }
740 None
741 }
742}
743impl zng_app::render::Img for ImageEntry {
744 fn renderer_id(&self, renderer: &ViewRenderer) -> ImageTextureId {
745 if self.is_loaded() {
746 let mut img = self.img_mut.lock();
747 let rms = &mut img.render_ids;
748 if let Some(rm) = rms.iter().find(|k| &k.renderer == renderer) {
749 return rm.image_id;
750 }
751
752 let key = match renderer.use_image(&self.handle) {
753 Ok(k) => {
754 if k == ImageTextureId::INVALID {
755 tracing::error!("received INVALID from `use_image`");
756 return k;
757 }
758 k
759 }
760 Err(_) => {
761 tracing::debug!("respawned `add_image`, will return INVALID");
762 return ImageTextureId::INVALID;
763 }
764 };
765
766 rms.push(RenderImage {
767 image_id: key,
768 renderer: renderer.clone(),
769 });
770 key
771 } else {
772 ImageTextureId::INVALID
773 }
774 }
775
776 fn size(&self) -> PxSize {
777 self.size()
778 }
779}
780
781struct RenderImage {
782 image_id: ImageTextureId,
783 renderer: ViewRenderer,
784}
785impl Drop for RenderImage {
786 fn drop(&mut self) {
787 let _ = self.renderer.delete_image_use(self.image_id);
789 }
790}
791impl fmt::Debug for RenderImage {
792 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
793 fmt::Debug::fmt(&self.image_id, f)
794 }
795}
796
797#[derive(Clone, Copy)]
805pub struct ImageHash([u8; 32]);
806impl ImageHash {
807 pub fn compute(data: &[u8]) -> Self {
809 let mut h = Self::hasher();
810 h.update(data);
811 h.finish()
812 }
813
814 pub fn hasher() -> ImageHasher {
816 ImageHasher::default()
817 }
818}
819impl fmt::Debug for ImageHash {
820 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
821 if f.alternate() {
822 f.debug_tuple("ImageHash").field(&self.0).finish()
823 } else {
824 use base64::*;
825 write!(f, "{}", base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(self.0))
826 }
827 }
828}
829impl fmt::Display for ImageHash {
830 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
831 write!(f, "{self:?}")
832 }
833}
834impl std::hash::Hash for ImageHash {
835 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
836 let h64 = [
837 self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], self.0[6], self.0[7],
838 ];
839 state.write_u64(u64::from_ne_bytes(h64))
840 }
841}
842impl PartialEq for ImageHash {
843 fn eq(&self, other: &Self) -> bool {
844 self.0 == other.0
845 }
846}
847impl Eq for ImageHash {}
848
849pub struct ImageHasher(sha2::Sha512_256);
851impl Default for ImageHasher {
852 fn default() -> Self {
853 use sha2::Digest;
854 Self(sha2::Sha512_256::new())
855 }
856}
857impl ImageHasher {
858 pub fn new() -> Self {
860 Self::default()
861 }
862
863 pub fn update(&mut self, data: &[u8]) {
865 use sha2::Digest;
866
867 const NUM_SAMPLES: usize = 1000;
870 const SAMPLE_CHUNK_SIZE: usize = 1024;
871
872 let total_size = data.len();
873 if total_size == 0 {
874 return;
875 }
876 if total_size < 1000 * 1000 * 4 {
877 return self.0.update(data);
878 }
879
880 let step_size = total_size.checked_div(NUM_SAMPLES).unwrap_or(total_size);
881 for n in 0..NUM_SAMPLES {
882 let start_index = n * step_size;
883 if start_index >= total_size {
884 break;
885 }
886 let end_index = (start_index + SAMPLE_CHUNK_SIZE).min(total_size);
887 let s = &data[start_index..end_index];
888 self.0.update(s);
889 }
890 }
891
892 pub fn finish(self) -> ImageHash {
894 use sha2::Digest;
895 ImageHash(self.0.finalize().as_slice().try_into().unwrap())
899 }
900}
901impl std::hash::Hasher for ImageHasher {
902 fn finish(&self) -> u64 {
903 tracing::warn!("Hasher::finish called for ImageHasher");
904
905 use sha2::Digest;
906 let hash = self.0.clone().finalize();
907 u64::from_le_bytes(hash[..8].try_into().unwrap())
908 }
909
910 fn write(&mut self, bytes: &[u8]) {
911 self.update(bytes);
912 }
913}
914
915pub(crate) type RenderFn = Arc<Box<dyn Fn(&ImageRenderArgs) -> Box<dyn ImageRenderWindowRoot> + Send + Sync>>;
917
918#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
922#[non_exhaustive]
923pub struct ImageRenderArgs {
924 pub parent: Option<WindowId>,
926}
927impl ImageRenderArgs {
928 pub fn new(parent: WindowId) -> Self {
930 Self { parent: Some(parent) }
931 }
932}
933
934#[derive(Clone)]
936#[non_exhaustive]
937pub enum ImageSource {
938 Read(PathBuf),
942 #[cfg(feature = "http")]
948 Download(zng_task::http::Uri, Option<Txt>),
949 Data(ImageHash, IpcBytes, ImageDataFormat),
957
958 Render(RenderFn, Option<ImageRenderArgs>),
964
965 Image(ImageVar),
969
970 Entries {
974 primary: Box<ImageSource>,
976 entries: Vec<(ImageEntryKind, ImageSource)>,
978 },
979}
980impl ImageSource {
981 pub fn from_data(data: IpcBytes, format: ImageDataFormat) -> Self {
983 let mut hasher = ImageHasher::default();
984 hasher.update(&data[..]);
985 let hash = hasher.finish();
986 Self::Data(hash, data, format)
987 }
988
989 pub fn hash128(&self, options: &ImageOptions) -> Option<ImageHash> {
991 match self {
992 ImageSource::Read(p) => Some(Self::hash128_read(p, options)),
993 #[cfg(feature = "http")]
994 ImageSource::Download(u, a) => Some(Self::hash128_download(u, a, options)),
995 ImageSource::Data(h, _, _) => Some(Self::hash128_data(*h, options)),
996 ImageSource::Render(rfn, args) => Some(Self::hash128_render(rfn, args, options)),
997 ImageSource::Image(_) => None,
998 ImageSource::Entries { .. } => None,
999 }
1000 }
1001
1002 pub fn hash128_data(data_hash: ImageHash, options: &ImageOptions) -> ImageHash {
1006 if options.downscale.is_some() || options.mask.is_some() || !options.entries.is_empty() {
1007 use std::hash::Hash;
1008 let mut h = ImageHash::hasher();
1009 data_hash.0.hash(&mut h);
1010 options.downscale.hash(&mut h);
1011 options.mask.hash(&mut h);
1012 options.entries.hash(&mut h);
1013 h.finish()
1014 } else {
1015 data_hash
1016 }
1017 }
1018
1019 pub fn hash128_read(path: &Path, options: &ImageOptions) -> ImageHash {
1023 use std::hash::Hash;
1024 let mut h = ImageHash::hasher();
1025 0u8.hash(&mut h);
1026 path.hash(&mut h);
1027 options.downscale.hash(&mut h);
1028 options.mask.hash(&mut h);
1029 options.entries.hash(&mut h);
1030 h.finish()
1031 }
1032
1033 #[cfg(feature = "http")]
1037 pub fn hash128_download(uri: &zng_task::http::Uri, accept: &Option<Txt>, options: &ImageOptions) -> ImageHash {
1038 use std::hash::Hash;
1039 let mut h = ImageHash::hasher();
1040 1u8.hash(&mut h);
1041 uri.hash(&mut h);
1042 accept.hash(&mut h);
1043 options.downscale.hash(&mut h);
1044 options.mask.hash(&mut h);
1045 options.entries.hash(&mut h);
1046 h.finish()
1047 }
1048
1049 pub fn hash128_render(rfn: &RenderFn, args: &Option<ImageRenderArgs>, options: &ImageOptions) -> ImageHash {
1055 use std::hash::Hash;
1056 let mut h = ImageHash::hasher();
1057 2u8.hash(&mut h);
1058 (Arc::as_ptr(rfn) as usize).hash(&mut h);
1059 args.hash(&mut h);
1060 options.downscale.hash(&mut h);
1061 options.mask.hash(&mut h);
1062 options.entries.hash(&mut h);
1063 h.finish()
1064 }
1065}
1066
1067impl ImageSource {
1068 pub fn render<F, R>(new_img: F) -> Self
1096 where
1097 F: Fn(&ImageRenderArgs) -> R + Send + Sync + 'static,
1098 R: ImageRenderWindowRoot,
1099 {
1100 let window = IMAGES_SV.read().render_windows();
1101 Self::Render(
1102 Arc::new(Box::new(move |args| {
1103 if let Some(parent) = args.parent {
1104 window.set_parent_in_window_context(parent);
1105 }
1106 let r = new_img(args);
1107 window.enable_frame_capture_in_window_context(None);
1108 Box::new(r)
1109 })),
1110 None,
1111 )
1112 }
1113
1114 pub fn render_node(render_mode: RenderMode, render: impl Fn(&ImageRenderArgs) -> UiNode + Send + Sync + 'static) -> Self {
1146 let window = IMAGES_SV.read().render_windows();
1147 Self::Render(
1148 Arc::new(Box::new(move |args| {
1149 if let Some(parent) = args.parent {
1150 window.set_parent_in_window_context(parent);
1151 }
1152 let node = render(args);
1153 window.enable_frame_capture_in_window_context(None);
1154 window.new_window_root(node, render_mode)
1155 })),
1156 None,
1157 )
1158 }
1159}
1160
1161impl ImageSource {
1162 pub fn flood(size: impl Into<PxSize>, color: impl Into<Rgba>, density: Option<PxDensity2d>) -> Self {
1164 Self::flood_impl(size.into(), color.into(), density)
1165 }
1166 fn flood_impl(size: PxSize, color: Rgba, density: Option<PxDensity2d>) -> Self {
1167 let pixels = size.width.0 as usize * size.height.0 as usize;
1168 let bgra = color.to_bgra_bytes();
1169 let mut b = IpcBytes::new_mut_blocking(pixels * 4).expect("cannot allocate IpcBytes");
1170 for b in b.chunks_exact_mut(4) {
1171 b.copy_from_slice(&bgra);
1172 }
1173 Self::from_data(
1174 b.finish_blocking().expect("cannot allocate IpcBytes"),
1175 ImageDataFormat::Bgra8 {
1176 size,
1177 density,
1178 original_color_type: ColorType::RGBA8,
1179 },
1180 )
1181 }
1182
1183 pub fn linear_vertical(
1185 size: impl Into<PxSize>,
1186 stops: impl Into<GradientStops>,
1187 density: Option<PxDensity2d>,
1188 mask: Option<ImageMaskMode>,
1189 ) -> Self {
1190 Self::linear_vertical_impl(size.into(), stops.into(), density, mask)
1191 }
1192 fn linear_vertical_impl(size: PxSize, stops: GradientStops, density: Option<PxDensity2d>, mask: Option<ImageMaskMode>) -> Self {
1193 assert!(size.width > Px(0));
1194 assert!(size.height > Px(0));
1195
1196 let mut line = PxLine::new(PxPoint::splat(Px(0)), PxPoint::new(Px(0), size.height));
1197 let mut render_stops = vec![];
1198
1199 LAYOUT.with_root_context(LayoutPassId::new(), LayoutMetrics::new(1.fct(), size, Px(14)), || {
1200 stops.layout_linear(LayoutAxis::Y, ExtendMode::Clamp, &mut line, &mut render_stops);
1201 });
1202 let line_a = line.start.y.0 as f32;
1203 let line_b = line.end.y.0 as f32;
1204
1205 let mut bgra = Vec::with_capacity(size.height.0 as usize);
1206 let mut render_stops = render_stops.into_iter();
1207 let mut stop_a = render_stops.next().unwrap();
1208 let mut stop_b = render_stops.next().unwrap();
1209 'outer: for y in 0..size.height.0 {
1210 let yf = y as f32;
1211 let yf = (yf - line_a) / (line_b - line_a);
1212 if yf < stop_a.offset {
1213 bgra.push(stop_a.color.to_bgra_bytes());
1215 continue;
1216 }
1217 while yf > stop_b.offset {
1218 if let Some(next_b) = render_stops.next() {
1219 stop_a = stop_b;
1221 stop_b = next_b;
1222 } else {
1223 for _ in y..size.height.0 {
1225 bgra.push(stop_b.color.to_bgra_bytes());
1226 }
1227 break 'outer;
1228 }
1229 }
1230
1231 let yf = (yf - stop_a.offset) / (stop_b.offset - stop_a.offset);
1233 let sample = stop_a.color.lerp(&stop_b.color, yf.fct());
1234 bgra.push(sample.to_bgra_bytes());
1235 }
1236
1237 match mask {
1238 Some(m) => {
1239 let len = size.width.0 as usize * size.height.0 as usize;
1240 let mut data = Vec::with_capacity(len);
1241
1242 for y in 0..size.height.0 {
1243 let c = bgra[y as usize];
1244 let c = match m {
1245 ImageMaskMode::A => c[3],
1246 ImageMaskMode::B => c[0],
1247 ImageMaskMode::G => c[1],
1248 ImageMaskMode::R => c[2],
1249 ImageMaskMode::Luminance => {
1250 let hsla = Hsla::from(Rgba::new(c[2], c[1], c[0], c[3]));
1251 (hsla.lightness * 255.0).round().clamp(0.0, 255.0) as u8
1252 }
1253 _ => unreachable!(),
1254 };
1255 for _x in 0..size.width.0 {
1256 data.push(c);
1257 }
1258 }
1259
1260 Self::from_data(
1261 IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"),
1262 ImageDataFormat::A8 { size },
1263 )
1264 }
1265 None => {
1266 let len = size.width.0 as usize * size.height.0 as usize * 4;
1267 let mut data = Vec::with_capacity(len);
1268
1269 for y in 0..size.height.0 {
1270 let c = bgra[y as usize];
1271 for _x in 0..size.width.0 {
1272 data.extend_from_slice(&c);
1273 }
1274 }
1275
1276 Self::from_data(
1277 IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"),
1278 ImageDataFormat::Bgra8 {
1279 size,
1280 density,
1281 original_color_type: ColorType::RGBA8,
1282 },
1283 )
1284 }
1285 }
1286 }
1287
1288 pub fn linear_horizontal(
1290 size: impl Into<PxSize>,
1291 stops: impl Into<GradientStops>,
1292 density: Option<PxDensity2d>,
1293 mask: Option<ImageMaskMode>,
1294 ) -> Self {
1295 Self::linear_horizontal_impl(size.into(), stops.into(), density, mask)
1296 }
1297 fn linear_horizontal_impl(size: PxSize, stops: GradientStops, density: Option<PxDensity2d>, mask: Option<ImageMaskMode>) -> Self {
1298 assert!(size.width > Px(0));
1299 assert!(size.height > Px(0));
1300
1301 let mut line = PxLine::new(PxPoint::splat(Px(0)), PxPoint::new(size.width, Px(0)));
1302 let mut render_stops = vec![];
1303 LAYOUT.with_root_context(LayoutPassId::new(), LayoutMetrics::new(1.fct(), size, Px(14)), || {
1304 stops.layout_linear(LayoutAxis::Y, ExtendMode::Clamp, &mut line, &mut render_stops);
1305 });
1306 let line_a = line.start.x.0 as f32;
1307 let line_b = line.end.x.0 as f32;
1308
1309 let mut bgra = Vec::with_capacity(size.width.0 as usize);
1310 let mut render_stops = render_stops.into_iter();
1311 let mut stop_a = render_stops.next().unwrap();
1312 let mut stop_b = render_stops.next().unwrap();
1313 'outer: for x in 0..size.width.0 {
1314 let xf = x as f32;
1315 let xf = (xf - line_a) / (line_b - line_a);
1316 if xf < stop_a.offset {
1317 bgra.push(stop_a.color.to_bgra_bytes());
1319 continue;
1320 }
1321 while xf > stop_b.offset {
1322 if let Some(next_b) = render_stops.next() {
1323 stop_a = stop_b;
1325 stop_b = next_b;
1326 } else {
1327 for _ in x..size.width.0 {
1329 bgra.push(stop_b.color.to_bgra_bytes());
1330 }
1331 break 'outer;
1332 }
1333 }
1334
1335 let xf = (xf - stop_a.offset) / (stop_b.offset - stop_a.offset);
1337 let sample = stop_a.color.lerp(&stop_b.color, xf.fct());
1338 bgra.push(sample.to_bgra_bytes());
1339 }
1340
1341 match mask {
1342 Some(m) => {
1343 let len = size.width.0 as usize * size.height.0 as usize;
1344 let mut data = Vec::with_capacity(len);
1345
1346 for _y in 0..size.height.0 {
1347 for c in &bgra {
1348 let c = match m {
1349 ImageMaskMode::A => c[3],
1350 ImageMaskMode::B => c[0],
1351 ImageMaskMode::G => c[1],
1352 ImageMaskMode::R => c[2],
1353 ImageMaskMode::Luminance => {
1354 let hsla = Hsla::from(Rgba::new(c[2], c[1], c[0], c[3]));
1355 (hsla.lightness * 255.0).round().clamp(0.0, 255.0) as u8
1356 }
1357 _ => unreachable!(),
1358 };
1359 data.push(c);
1360 }
1361 }
1362
1363 Self::from_data(
1364 IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"),
1365 ImageDataFormat::A8 { size },
1366 )
1367 }
1368 None => {
1369 let len = size.width.0 as usize * size.height.0 as usize * 4;
1370 let mut data = Vec::with_capacity(len);
1371
1372 for _y in 0..size.height.0 {
1373 for c in &bgra {
1374 data.extend_from_slice(c);
1375 }
1376 }
1377
1378 Self::from_data(
1379 IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"),
1380 ImageDataFormat::Bgra8 {
1381 size,
1382 density,
1383 original_color_type: ColorType::RGBA8,
1384 },
1385 )
1386 }
1387 }
1388 }
1389}
1390
1391impl PartialEq for ImageSource {
1392 fn eq(&self, other: &Self) -> bool {
1393 match (self, other) {
1394 (Self::Read(l), Self::Read(r)) => l == r,
1395 #[cfg(feature = "http")]
1396 (Self::Download(lu, la), Self::Download(ru, ra)) => lu == ru && la == ra,
1397 (Self::Render(lf, la), Self::Render(rf, ra)) => Arc::ptr_eq(lf, rf) && la == ra,
1398 (Self::Image(l), Self::Image(r)) => l.var_eq(r),
1399 (l, r) => {
1400 let l_hash = match l {
1401 ImageSource::Data(h, _, _) => h,
1402 _ => return false,
1403 };
1404 let r_hash = match r {
1405 ImageSource::Data(h, _, _) => h,
1406 _ => return false,
1407 };
1408
1409 l_hash == r_hash
1410 }
1411 }
1412 }
1413}
1414impl fmt::Debug for ImageSource {
1415 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1416 if f.alternate() {
1417 write!(f, "ImageSource::")?;
1418 }
1419 match self {
1420 ImageSource::Read(p) => f.debug_tuple("Read").field(p).finish(),
1421 #[cfg(feature = "http")]
1422 ImageSource::Download(u, a) => f.debug_tuple("Download").field(u).field(a).finish(),
1423 ImageSource::Data(key, bytes, fmt) => f.debug_tuple("Data").field(key).field(bytes).field(fmt).finish(),
1424
1425 ImageSource::Render(_, _) => write!(f, "Render(_, _)"),
1426 ImageSource::Image(_) => write!(f, "Image(_)"),
1427 ImageSource::Entries { primary, entries } => f
1428 .debug_struct("Entries")
1429 .field("primary", primary)
1430 .field("entries", entries)
1431 .finish(),
1432 }
1433 }
1434}
1435
1436#[cfg(feature = "http")]
1437impl_from_and_into_var! {
1438 fn from(uri: zng_task::http::Uri) -> ImageSource {
1439 ImageSource::Download(uri, None)
1440 }
1441 fn from((uri, accept): (zng_task::http::Uri, &'static str)) -> ImageSource {
1443 ImageSource::Download(uri, Some(accept.into()))
1444 }
1445
1446 fn from(s: &str) -> ImageSource {
1452 use zng_task::http::*;
1453 if let Ok(uri) = Uri::try_from(s)
1454 && let Some(scheme) = uri.scheme()
1455 {
1456 if scheme == &uri::Scheme::HTTPS || scheme == &uri::Scheme::HTTP {
1457 return ImageSource::Download(uri, None);
1458 } else if scheme.as_str() == "file" {
1459 return PathBuf::from(uri.path()).into();
1460 }
1461 }
1462 PathBuf::from(s).into()
1463 }
1464}
1465
1466#[cfg(not(feature = "http"))]
1467impl_from_and_into_var! {
1468 fn from(s: &str) -> ImageSource {
1472 PathBuf::from(s).into()
1473 }
1474}
1475
1476impl_from_and_into_var! {
1477 fn from(image: ImageVar) -> ImageSource {
1478 ImageSource::Image(image)
1479 }
1480 fn from(path: PathBuf) -> ImageSource {
1481 ImageSource::Read(path)
1482 }
1483 fn from(path: &Path) -> ImageSource {
1484 path.to_owned().into()
1485 }
1486
1487 fn from(s: String) -> ImageSource {
1489 s.as_str().into()
1490 }
1491 fn from(s: Txt) -> ImageSource {
1493 s.as_str().into()
1494 }
1495 fn from(data: &[u8]) -> ImageSource {
1499 ImageSource::Data(
1500 ImageHash::compute(data),
1501 IpcBytes::from_slice_blocking(data).expect("cannot allocate IpcBytes"),
1502 ImageDataFormat::Unknown,
1503 )
1504 }
1505 fn from<const N: usize>(data: &[u8; N]) -> ImageSource {
1509 (&data[..]).into()
1510 }
1511 fn from(data: IpcBytes) -> ImageSource {
1515 ImageSource::Data(ImageHash::compute(&data[..]), data, ImageDataFormat::Unknown)
1516 }
1517 fn from(data: Vec<u8>) -> ImageSource {
1521 IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes").into()
1522 }
1523 fn from<F: Into<ImageDataFormat>>((data, format): (&[u8], F)) -> ImageSource {
1525 ImageSource::Data(
1526 ImageHash::compute(data),
1527 IpcBytes::from_slice_blocking(data).expect("cannot allocate IpcBytes"),
1528 format.into(),
1529 )
1530 }
1531 fn from<F: Into<ImageDataFormat>, const N: usize>((data, format): (&[u8; N], F)) -> ImageSource {
1533 (&data[..], format).into()
1534 }
1535 fn from<F: Into<ImageDataFormat>>((data, format): (Vec<u8>, F)) -> ImageSource {
1537 (IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"), format).into()
1538 }
1539 fn from<F: Into<ImageDataFormat>>((data, format): (IpcBytes, F)) -> ImageSource {
1541 ImageSource::Data(ImageHash::compute(&data[..]), data, format.into())
1542 }
1543}
1544
1545#[derive(Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1549pub enum ImageCacheMode {
1550 Ignore,
1552 Cache,
1554 Retry,
1556 Reload,
1560}
1561impl fmt::Debug for ImageCacheMode {
1562 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1563 if f.alternate() {
1564 write!(f, "CacheMode::")?;
1565 }
1566 match self {
1567 Self::Ignore => write!(f, "Ignore"),
1568 Self::Cache => write!(f, "Cache"),
1569 Self::Retry => write!(f, "Retry"),
1570 Self::Reload => write!(f, "Reload"),
1571 }
1572 }
1573}
1574impl_from_and_into_var! {
1575 fn from(cache: bool) -> ImageCacheMode {
1576 if cache { ImageCacheMode::Cache } else { ImageCacheMode::Ignore }
1577 }
1578}
1579
1580#[derive(Clone)]
1582pub enum ImageSourceFilter<U> {
1583 BlockAll,
1585 AllowAll,
1587 Custom(Arc<dyn Fn(&U) -> bool + Send + Sync>),
1589}
1590impl<U> ImageSourceFilter<U> {
1591 pub fn custom(allow: impl Fn(&U) -> bool + Send + Sync + 'static) -> Self {
1595 Self::Custom(Arc::new(allow))
1596 }
1597
1598 pub fn and(self, other: Self) -> Self
1607 where
1608 U: 'static,
1609 {
1610 use ImageSourceFilter::*;
1611 match (self, other) {
1612 (BlockAll, _) | (_, BlockAll) => BlockAll,
1613 (AllowAll, _) | (_, AllowAll) => AllowAll,
1614 (Custom(c0), Custom(c1)) => Custom(Arc::new(move |u| c0(u) && c1(u))),
1615 }
1616 }
1617
1618 pub fn or(self, other: Self) -> Self
1627 where
1628 U: 'static,
1629 {
1630 use ImageSourceFilter::*;
1631 match (self, other) {
1632 (AllowAll, _) | (_, AllowAll) => AllowAll,
1633 (BlockAll, _) | (_, BlockAll) => BlockAll,
1634 (Custom(c0), Custom(c1)) => Custom(Arc::new(move |u| c0(u) || c1(u))),
1635 }
1636 }
1637
1638 pub fn allows(&self, item: &U) -> bool {
1640 match self {
1641 ImageSourceFilter::BlockAll => false,
1642 ImageSourceFilter::AllowAll => true,
1643 ImageSourceFilter::Custom(f) => f(item),
1644 }
1645 }
1646}
1647impl<U> fmt::Debug for ImageSourceFilter<U> {
1648 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1649 match self {
1650 Self::BlockAll => write!(f, "BlockAll"),
1651 Self::AllowAll => write!(f, "AllowAll"),
1652 Self::Custom(_) => write!(f, "Custom(_)"),
1653 }
1654 }
1655}
1656impl<U: 'static> ops::BitAnd for ImageSourceFilter<U> {
1657 type Output = Self;
1658
1659 fn bitand(self, rhs: Self) -> Self::Output {
1660 self.and(rhs)
1661 }
1662}
1663impl<U: 'static> ops::BitOr for ImageSourceFilter<U> {
1664 type Output = Self;
1665
1666 fn bitor(self, rhs: Self) -> Self::Output {
1667 self.or(rhs)
1668 }
1669}
1670impl<U: 'static> ops::BitAndAssign for ImageSourceFilter<U> {
1671 fn bitand_assign(&mut self, rhs: Self) {
1672 *self = mem::replace(self, Self::BlockAll).and(rhs);
1673 }
1674}
1675impl<U: 'static> ops::BitOrAssign for ImageSourceFilter<U> {
1676 fn bitor_assign(&mut self, rhs: Self) {
1677 *self = mem::replace(self, Self::BlockAll).or(rhs);
1678 }
1679}
1680impl<U> PartialEq for ImageSourceFilter<U> {
1681 fn eq(&self, other: &Self) -> bool {
1682 match (self, other) {
1683 (Self::Custom(l0), Self::Custom(r0)) => Arc::ptr_eq(l0, r0),
1684 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
1685 }
1686 }
1687}
1688
1689pub type PathFilter = ImageSourceFilter<PathBuf>;
1699impl PathFilter {
1700 pub fn allow_dir(dir: impl AsRef<Path>) -> Self {
1702 let dir = absolute_path(dir.as_ref(), || env::current_dir().expect("could not access current dir"), true);
1703 PathFilter::custom(move |r| r.starts_with(&dir))
1704 }
1705
1706 pub fn allow_ext(ext: impl Into<std::ffi::OsString>) -> Self {
1708 let ext = ext.into();
1709 PathFilter::custom(move |r| r.extension().map(|e| e == ext).unwrap_or(false))
1710 }
1711
1712 pub fn allow_current_dir() -> Self {
1720 PathFilter::custom(|r| env::current_dir().map(|d| r.starts_with(d)).unwrap_or(false))
1721 }
1722
1723 pub fn allow_exe_dir() -> Self {
1725 if let Ok(mut p) = env::current_exe().and_then(dunce::canonicalize)
1726 && p.pop()
1727 {
1728 return Self::allow_dir(p);
1729 }
1730
1731 Self::custom(|_| false)
1733 }
1734
1735 pub fn allow_res() -> Self {
1739 Self::allow_dir(zng_env::res(""))
1740 }
1741}
1742
1743#[cfg(feature = "http")]
1747pub type UriFilter = ImageSourceFilter<zng_task::http::Uri>;
1748#[cfg(feature = "http")]
1749impl UriFilter {
1750 pub fn allow_host(host: impl Into<Txt>) -> Self {
1752 let host = host.into();
1753 UriFilter::custom(move |u| u.authority().map(|a| a.host() == host).unwrap_or(false))
1754 }
1755}
1756
1757impl<F: Fn(&PathBuf) -> bool + Send + Sync + 'static> From<F> for PathFilter {
1758 fn from(custom: F) -> Self {
1759 PathFilter::custom(custom)
1760 }
1761}
1762
1763#[cfg(feature = "http")]
1764impl<F: Fn(&task::http::Uri) -> bool + Send + Sync + 'static> From<F> for UriFilter {
1765 fn from(custom: F) -> Self {
1766 UriFilter::custom(custom)
1767 }
1768}
1769
1770#[derive(Clone, Debug, PartialEq)]
1772#[non_exhaustive]
1773pub struct ImageLimits {
1774 pub max_encoded_len: ByteLength,
1782 pub max_decoded_len: ByteLength,
1786
1787 pub allow_path: PathFilter,
1789
1790 #[cfg(feature = "http")]
1792 pub allow_uri: UriFilter,
1793}
1794impl ImageLimits {
1795 pub fn none() -> Self {
1797 ImageLimits {
1798 max_encoded_len: ByteLength::MAX,
1799 max_decoded_len: ByteLength::MAX,
1800 allow_path: PathFilter::AllowAll,
1801 #[cfg(feature = "http")]
1802 allow_uri: UriFilter::AllowAll,
1803 }
1804 }
1805
1806 pub fn with_max_encoded_len(mut self, max_encoded_len: impl Into<ByteLength>) -> Self {
1810 self.max_encoded_len = max_encoded_len.into();
1811 self
1812 }
1813
1814 pub fn with_max_decoded_len(mut self, max_decoded_len: impl Into<ByteLength>) -> Self {
1818 self.max_decoded_len = max_decoded_len.into();
1819 self
1820 }
1821
1822 pub fn with_allow_path(mut self, allow_path: impl Into<PathFilter>) -> Self {
1826 self.allow_path = allow_path.into();
1827 self
1828 }
1829
1830 #[cfg(feature = "http")]
1834 pub fn with_allow_uri(mut self, allow_url: impl Into<UriFilter>) -> Self {
1835 self.allow_uri = allow_url.into();
1836 self
1837 }
1838}
1839impl Default for ImageLimits {
1840 fn default() -> Self {
1844 Self {
1845 max_encoded_len: 100.megabytes(),
1846 max_decoded_len: 4096.megabytes(),
1847 allow_path: PathFilter::allow_res(),
1848 #[cfg(feature = "http")]
1849 allow_uri: UriFilter::BlockAll,
1850 }
1851 }
1852}
1853impl_from_and_into_var! {
1854 fn from(some: ImageLimits) -> Option<ImageLimits>;
1855}
1856
1857#[derive(Debug, Clone, PartialEq)]
1861#[non_exhaustive]
1862pub struct ImageOptions {
1863 pub cache_mode: ImageCacheMode,
1865 pub downscale: Option<ImageDownscaleMode>,
1867 pub mask: Option<ImageMaskMode>,
1869 pub entries: ImageEntriesMode,
1871}
1872
1873impl ImageOptions {
1874 pub fn new(
1876 cache_mode: ImageCacheMode,
1877 downscale: Option<ImageDownscaleMode>,
1878 mask: Option<ImageMaskMode>,
1879 entries: ImageEntriesMode,
1880 ) -> Self {
1881 Self {
1882 cache_mode,
1883 downscale,
1884 mask,
1885 entries,
1886 }
1887 }
1888
1889 pub fn cache() -> Self {
1891 Self::new(ImageCacheMode::Cache, None, None, ImageEntriesMode::empty())
1892 }
1893
1894 pub fn none() -> Self {
1896 Self::new(ImageCacheMode::Ignore, None, None, ImageEntriesMode::empty())
1897 }
1898}
1899
1900fn absolute_path(path: &Path, base: impl FnOnce() -> PathBuf, allow_escape: bool) -> PathBuf {
1901 if path.is_absolute() {
1902 normalize_path(path)
1903 } else {
1904 let mut dir = base();
1905 if allow_escape {
1906 dir.push(path);
1907 normalize_path(&dir)
1908 } else {
1909 dir.push(normalize_path(path));
1910 dir
1911 }
1912 }
1913}
1914fn normalize_path(path: &Path) -> PathBuf {
1918 use std::path::Component;
1919
1920 let mut components = path.components().peekable();
1921 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
1922 components.next();
1923 PathBuf::from(c.as_os_str())
1924 } else {
1925 PathBuf::new()
1926 };
1927
1928 for component in components {
1929 match component {
1930 Component::Prefix(..) => unreachable!(),
1931 Component::RootDir => {
1932 ret.push(component.as_os_str());
1933 }
1934 Component::CurDir => {}
1935 Component::ParentDir => {
1936 ret.pop();
1937 }
1938 Component::Normal(c) => {
1939 ret.push(c);
1940 }
1941 }
1942 }
1943 ret
1944}