1use std::{
2 any::Any,
3 env, fmt, fs, io, mem, ops,
4 path::{Path, PathBuf},
5 sync::Arc,
6};
7
8use zng_app::{
9 view_process::{EncodeError, VIEW_PROCESS, ViewImageHandle, ViewRenderer},
10 widget::node::UiNode,
11 window::WindowId,
12};
13use zng_color::{
14 Hsla, Rgba,
15 gradient::{ExtendMode, GradientStops},
16};
17use zng_layout::{
18 context::{LAYOUT, LayoutMetrics, LayoutPassId},
19 unit::{ByteLength, ByteUnits, FactorUnits as _, LayoutAxis, Px, PxDensity2d, PxLine, PxPoint, PxRect, PxSize, about_eq},
20};
21use zng_task::parking_lot::Mutex;
22use zng_task::{
23 self as task,
24 channel::{IpcBytes, IpcBytesMut, IpcReadHandle},
25};
26use zng_txt::Txt;
27use zng_var::{Var, VarEq, animation::Transitionable, impl_from_and_into_var};
28use zng_view_api::{
29 api_extension::{ApiExtensionId, ApiExtensionPayload},
30 image::{ImageDecoded, ImageEncodeRequest, ImageEntryMetadata, ImageTextureId},
31 window::RenderMode,
32};
33
34use crate::{IMAGES_SV, ImageRenderWindowRoot};
35
36pub use zng_view_api::image::{
37 ColorType, ImageDataFormat, ImageDownscaleMode, ImageEntriesMode, ImageEntryKind, ImageFormat, ImageFormatCapability, ImageMaskMode,
38 PartialImageKind,
39};
40
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: &IpcReadHandle,
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 pub fn view_handle(&self) -> &ViewImageHandle {
452 &self.handle
453 }
454
455 pub fn layout_size(&self, ctx: &LayoutMetrics) -> PxSize {
463 self.calc_size(ctx, PxDensity2d::splat(ctx.screen_density()), false)
464 }
465
466 pub fn calc_size(&self, ctx: &LayoutMetrics, fallback_density: PxDensity2d, ignore_image_density: bool) -> PxSize {
476 let dpi = if ignore_image_density {
477 fallback_density
478 } else {
479 self.density().unwrap_or(fallback_density)
480 };
481
482 let s_density = ctx.screen_density();
483 let mut size = self.size();
484
485 let fct = ctx.scale_factor().0;
486 size.width *= (s_density.ppcm() / dpi.width.ppcm()) * fct;
487 size.height *= (s_density.ppcm() / dpi.height.ppcm()) * fct;
488
489 size
490 }
491
492 pub fn pixels(&self) -> Option<IpcBytes> {
496 if self.is_loaded() { Some(self.data.pixels.clone()) } else { None }
497 }
498
499 pub fn partial_pixels(&self) -> Option<IpcBytes> {
506 if self.is_loading() && self.data.partial.is_some() {
507 Some(self.data.pixels.clone())
508 } else {
509 None
510 }
511 }
512
513 fn actual_pixels_and_size(&self) -> Option<(PxSize, IpcBytes)> {
514 match (self.partial_pixels(), self.partial_size()) {
515 (Some(b), Some(s)) => Some((s, b)),
516 _ => Some((self.size(), self.pixels()?)),
517 }
518 }
519
520 pub fn copy_pixels(&self, rect: PxRect) -> Option<(PxRect, IpcBytesMut)> {
528 self.actual_pixels_and_size().and_then(|(size, pixels)| {
529 let area = PxRect::from_size(size).intersection(&rect).unwrap_or_default();
530 if area.size.width.0 == 0 || area.size.height.0 == 0 {
531 Some((area, IpcBytesMut::new_blocking(0).unwrap()))
532 } else {
533 let x = area.origin.x.0 as usize;
534 let y = area.origin.y.0 as usize;
535 let width = area.size.width.0 as usize;
536 let height = area.size.height.0 as usize;
537 let pixel = if self.is_mask() { 1 } else { 4 };
538 let mut bytes = IpcBytesMut::new_blocking(width * height * pixel).ok()?;
539 let mut write = &mut bytes[..];
540 let row_stride = self.size().width.0 as usize * pixel;
541 for l in y..y + height {
542 let line_start = l * row_stride + x * pixel;
543 let line_end = line_start + width * pixel;
544 let line = &pixels[line_start..line_end];
545 write[..line.len()].copy_from_slice(line);
546 write = &mut write[line.len()..];
547 }
548 Some((area, bytes))
549 }
550 })
551 }
552
553 pub async fn encode(&self, format: Txt) -> std::result::Result<IpcBytes, EncodeError> {
561 self.encode_with_entries(&[], format).await
562 }
563
564 pub async fn encode_with_entries(
568 &self,
569 entries: &[(ImageEntry, ImageEntryKind)],
570 format: Txt,
571 ) -> std::result::Result<IpcBytes, EncodeError> {
572 if self.is_loading() {
573 return Err(EncodeError::Loading);
574 } else if let Some(e) = self.error() {
575 return Err(e.into());
576 } else if self.handle.is_dummy() {
577 return Err(EncodeError::Dummy);
578 }
579
580 let mut r = ImageEncodeRequest::new(self.handle.image_id(), format);
581 r.entries = entries.iter().map(|(img, kind)| (img.handle.image_id(), kind.clone())).collect();
582
583 match VIEW_PROCESS.encode_image(r).recv().await {
584 Ok(r) => r,
585 Err(_) => Err(EncodeError::Disconnected),
586 }
587 }
588
589 pub async fn save(&self, path: impl Into<PathBuf>) -> io::Result<()> {
600 let path = path.into();
601 if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
602 self.save_impl(&[], Txt::from_str(ext), path).await
603 } else {
604 Err(io::Error::new(
605 io::ErrorKind::InvalidInput,
606 "could not determinate image format from path extension",
607 ))
608 }
609 }
610
611 pub async fn save_with_format(&self, format: impl Into<Txt>, path: impl Into<PathBuf>) -> io::Result<()> {
621 self.save_impl(&[], format.into(), path.into()).await
622 }
623
624 pub async fn save_with_entries(
630 &self,
631 entries: &[(ImageEntry, ImageEntryKind)],
632 format: impl Into<Txt>,
633 path: impl Into<PathBuf>,
634 ) -> io::Result<()> {
635 self.save_impl(entries, format.into(), path.into()).await
636 }
637
638 async fn save_impl(&self, entries: &[(ImageEntry, ImageEntryKind)], format: Txt, path: PathBuf) -> io::Result<()> {
639 let data = self.encode_with_entries(entries, format).await.map_err(io::Error::other)?;
640 task::wait(move || fs::write(path, &data[..])).await
641 }
642
643 pub fn insert_entry(&mut self, entry: ImageVar) {
653 let id = self.handle.image_id();
654 let (i, p) = entry.with(|i| (i.entry_index(), i.data.meta.parent.clone()));
655 let i = self
656 .entries
657 .iter()
658 .position(|v| {
659 let entry_i = v.with(|i| i.entry_index());
660 entry_i > i
661 })
662 .unwrap_or(self.entries.len());
663
664 if let Some(p) = &p {
665 if p.parent != id {
666 tracing::warn!("replacing entry parent from {:?} tp {:?}", p.parent, id);
667 entry.modify(move |e| {
668 if let Some(p) = &mut e.data.meta.parent {
669 p.parent = id;
670 } else {
671 e.data.meta.parent = Some(ImageEntryMetadata::new(id, i, ImageEntryKind::Page));
672 }
673 });
674 }
675 } else {
676 entry.modify(move |e| e.data.meta.parent = Some(ImageEntryMetadata::new(id, i, ImageEntryKind::Page)));
677 }
678
679 self.entries.insert(i, VarEq(entry));
680 }
681
682 pub fn extensions(&self) -> &[(ApiExtensionId, ApiExtensionPayload)] {
684 &self.data.meta.extensions
685 }
686
687 pub fn cur_hotspot(&self) -> Option<PxPoint> {
692 let ext_id = VIEW_PROCESS.extension_id("image_cur").ok()??;
693 for (id, data) in self.extensions() {
694 if *id == ext_id {
695 return data.deserialize::<PxPoint>().ok();
696 }
697 }
698 None
699 }
700
701 pub fn exif(&self) -> Option<&[u8]> {
709 let ext_id = VIEW_PROCESS.extension_id("image_meta_exif").ok()??;
710 for (id, data) in self.extensions() {
711 if *id == ext_id {
712 return Some(&data.0[..]);
713 }
714 }
715 None
716 }
717
718 pub fn icc_profile(&self) -> Option<&[u8]> {
726 let ext_id = VIEW_PROCESS.extension_id("image_meta_icc").ok()??;
727 for (id, data) in self.extensions() {
728 if *id == ext_id {
729 return Some(&data.0[..]);
730 }
731 }
732 None
733 }
734}
735impl zng_app::render::Img for ImageEntry {
736 fn renderer_id(&self, renderer: &ViewRenderer) -> ImageTextureId {
737 if self.is_loaded() {
738 let mut img = self.img_mut.lock();
739 let rms = &mut img.render_ids;
740 if let Some(rm) = rms.iter().find(|k| &k.renderer == renderer) {
741 return rm.image_id;
742 }
743
744 let key = match renderer.use_image(&self.handle) {
745 Ok(k) => {
746 if k == ImageTextureId::INVALID {
747 tracing::error!("received INVALID from `use_image`");
748 return k;
749 }
750 k
751 }
752 Err(_) => {
753 tracing::debug!("respawned `add_image`, will return INVALID");
754 return ImageTextureId::INVALID;
755 }
756 };
757
758 rms.push(RenderImage {
759 image_id: key,
760 renderer: renderer.clone(),
761 });
762 key
763 } else {
764 ImageTextureId::INVALID
765 }
766 }
767
768 fn size(&self) -> PxSize {
769 self.size()
770 }
771}
772
773struct RenderImage {
774 image_id: ImageTextureId,
775 renderer: ViewRenderer,
776}
777impl Drop for RenderImage {
778 fn drop(&mut self) {
779 let _ = self.renderer.delete_image_use(self.image_id);
781 }
782}
783impl fmt::Debug for RenderImage {
784 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
785 fmt::Debug::fmt(&self.image_id, f)
786 }
787}
788
789#[derive(Clone, Copy)]
797pub struct ImageHash([u8; 32]);
798impl ImageHash {
799 pub fn compute(data: &[u8]) -> Self {
801 let mut h = Self::hasher();
802 h.update(data);
803 h.finish()
804 }
805
806 pub fn hasher() -> ImageHasher {
808 ImageHasher::default()
809 }
810}
811impl fmt::Debug for ImageHash {
812 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
813 if f.alternate() {
814 f.debug_tuple("ImageHash").field(&self.0).finish()
815 } else {
816 use base64::*;
817 write!(f, "{}", base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(self.0))
818 }
819 }
820}
821impl fmt::Display for ImageHash {
822 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
823 write!(f, "{self:?}")
824 }
825}
826impl std::hash::Hash for ImageHash {
827 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
828 let h64 = [
829 self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], self.0[6], self.0[7],
830 ];
831 state.write_u64(u64::from_ne_bytes(h64))
832 }
833}
834impl PartialEq for ImageHash {
835 fn eq(&self, other: &Self) -> bool {
836 self.0 == other.0
837 }
838}
839impl Eq for ImageHash {}
840
841pub struct ImageHasher(sha2::Sha512_256);
843impl Default for ImageHasher {
844 fn default() -> Self {
845 use sha2::Digest;
846 Self(sha2::Sha512_256::new())
847 }
848}
849impl ImageHasher {
850 pub fn new() -> Self {
852 Self::default()
853 }
854
855 pub fn update(&mut self, data: &[u8]) {
857 use sha2::Digest;
858
859 const NUM_SAMPLES: usize = 1000;
862 const SAMPLE_CHUNK_SIZE: usize = 1024;
863
864 let total_size = data.len();
865 if total_size == 0 {
866 return;
867 }
868 if total_size < 1000 * 1000 * 4 {
869 return self.0.update(data);
870 }
871
872 let step_size = total_size.checked_div(NUM_SAMPLES).unwrap_or(total_size);
873 for n in 0..NUM_SAMPLES {
874 let start_index = n * step_size;
875 if start_index >= total_size {
876 break;
877 }
878 let end_index = (start_index + SAMPLE_CHUNK_SIZE).min(total_size);
879 let s = &data[start_index..end_index];
880 self.0.update(s);
881 }
882 }
883
884 pub fn finish(self) -> ImageHash {
886 use sha2::Digest;
887 ImageHash(self.0.finalize().as_slice().try_into().unwrap())
891 }
892}
893impl std::hash::Hasher for ImageHasher {
894 fn finish(&self) -> u64 {
895 tracing::warn!("Hasher::finish called for ImageHasher");
896
897 use sha2::Digest;
898 let hash = self.0.clone().finalize();
899 u64::from_le_bytes(hash[..8].try_into().unwrap())
900 }
901
902 fn write(&mut self, bytes: &[u8]) {
903 self.update(bytes);
904 }
905}
906
907pub(crate) type RenderFn = Arc<Box<dyn Fn(&ImageRenderArgs) -> Box<dyn ImageRenderWindowRoot> + Send + Sync>>;
909
910#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
914#[non_exhaustive]
915pub struct ImageRenderArgs {
916 pub parent: Option<WindowId>,
918}
919impl ImageRenderArgs {
920 pub fn new(parent: WindowId) -> Self {
922 Self { parent: Some(parent) }
923 }
924}
925
926#[derive(Clone)]
928#[non_exhaustive]
929pub enum ImageSource {
930 Read(PathBuf),
934 #[cfg(feature = "http")]
940 Download(zng_task::http::Uri, Option<Txt>),
941 Data(ImageHash, IpcBytes, ImageDataFormat),
949
950 Render(RenderFn, Option<ImageRenderArgs>),
956
957 Image(ImageVar),
961
962 Entries {
966 primary: Box<ImageSource>,
968 entries: Vec<(ImageEntryKind, ImageSource)>,
970 },
971}
972impl ImageSource {
973 pub fn from_data(data: IpcBytes, format: ImageDataFormat) -> Self {
975 let mut hasher = ImageHasher::default();
976 hasher.update(&data[..]);
977 let hash = hasher.finish();
978 Self::Data(hash, data, format)
979 }
980
981 pub fn hash128(&self, options: &ImageOptions) -> Option<ImageHash> {
983 match self {
984 ImageSource::Read(p) => Some(Self::hash128_read(p, options)),
985 #[cfg(feature = "http")]
986 ImageSource::Download(u, a) => Some(Self::hash128_download(u, a, options)),
987 ImageSource::Data(h, _, _) => Some(Self::hash128_data(*h, options)),
988 ImageSource::Render(rfn, args) => Some(Self::hash128_render(rfn, args, options)),
989 ImageSource::Image(_) => None,
990 ImageSource::Entries { .. } => None,
991 }
992 }
993
994 pub fn hash128_data(data_hash: ImageHash, options: &ImageOptions) -> ImageHash {
998 if options.downscale.is_some() || options.mask.is_some() || !options.entries.is_empty() {
999 use std::hash::Hash;
1000 let mut h = ImageHash::hasher();
1001 data_hash.0.hash(&mut h);
1002 options.downscale.hash(&mut h);
1003 options.mask.hash(&mut h);
1004 options.entries.hash(&mut h);
1005 h.finish()
1006 } else {
1007 data_hash
1008 }
1009 }
1010
1011 pub fn hash128_read(path: &Path, options: &ImageOptions) -> ImageHash {
1015 use std::hash::Hash;
1016 let mut h = ImageHash::hasher();
1017 0u8.hash(&mut h);
1018 path.hash(&mut h);
1019 options.downscale.hash(&mut h);
1020 options.mask.hash(&mut h);
1021 options.entries.hash(&mut h);
1022 h.finish()
1023 }
1024
1025 #[cfg(feature = "http")]
1029 pub fn hash128_download(uri: &zng_task::http::Uri, accept: &Option<Txt>, options: &ImageOptions) -> ImageHash {
1030 use std::hash::Hash;
1031 let mut h = ImageHash::hasher();
1032 1u8.hash(&mut h);
1033 uri.hash(&mut h);
1034 accept.hash(&mut h);
1035 options.downscale.hash(&mut h);
1036 options.mask.hash(&mut h);
1037 options.entries.hash(&mut h);
1038 h.finish()
1039 }
1040
1041 pub fn hash128_render(rfn: &RenderFn, args: &Option<ImageRenderArgs>, options: &ImageOptions) -> ImageHash {
1047 use std::hash::Hash;
1048 let mut h = ImageHash::hasher();
1049 2u8.hash(&mut h);
1050 (Arc::as_ptr(rfn) as usize).hash(&mut h);
1051 args.hash(&mut h);
1052 options.downscale.hash(&mut h);
1053 options.mask.hash(&mut h);
1054 options.entries.hash(&mut h);
1055 h.finish()
1056 }
1057}
1058
1059impl ImageSource {
1060 pub fn render<F, R>(new_img: F) -> Self
1088 where
1089 F: Fn(&ImageRenderArgs) -> R + Send + Sync + 'static,
1090 R: ImageRenderWindowRoot,
1091 {
1092 let window = IMAGES_SV.read().render_windows();
1093 Self::Render(
1094 Arc::new(Box::new(move |args| {
1095 if let Some(parent) = args.parent {
1096 window.set_parent_in_window_context(parent);
1097 }
1098 let r = new_img(args);
1099 window.enable_frame_capture_in_window_context(None);
1100 Box::new(r)
1101 })),
1102 None,
1103 )
1104 }
1105
1106 pub fn render_node(render_mode: RenderMode, render: impl Fn(&ImageRenderArgs) -> UiNode + Send + Sync + 'static) -> Self {
1138 let window = IMAGES_SV.read().render_windows();
1139 Self::Render(
1140 Arc::new(Box::new(move |args| {
1141 if let Some(parent) = args.parent {
1142 window.set_parent_in_window_context(parent);
1143 }
1144 let node = render(args);
1145 window.enable_frame_capture_in_window_context(None);
1146 window.new_window_root(node, render_mode)
1147 })),
1148 None,
1149 )
1150 }
1151}
1152
1153impl ImageSource {
1154 pub fn flood(size: impl Into<PxSize>, color: impl Into<Rgba>, density: Option<PxDensity2d>) -> Self {
1156 Self::flood_impl(size.into(), color.into(), density)
1157 }
1158 fn flood_impl(size: PxSize, color: Rgba, density: Option<PxDensity2d>) -> Self {
1159 let pixels = size.width.0 as usize * size.height.0 as usize;
1160 let bgra = color.to_bgra_bytes();
1161 let mut b = IpcBytesMut::new_blocking(pixels * 4).expect("cannot allocate IpcBytes");
1162 for b in b.chunks_exact_mut(4) {
1163 b.copy_from_slice(&bgra);
1164 }
1165 Self::from_data(
1166 b.finish_blocking().expect("cannot allocate IpcBytes"),
1167 ImageDataFormat::Bgra8 {
1168 size,
1169 density,
1170 original_color_type: ColorType::RGBA8,
1171 },
1172 )
1173 }
1174
1175 pub fn linear_vertical(
1177 size: impl Into<PxSize>,
1178 stops: impl Into<GradientStops>,
1179 density: Option<PxDensity2d>,
1180 mask: Option<ImageMaskMode>,
1181 ) -> Self {
1182 Self::linear_vertical_impl(size.into(), stops.into(), density, mask)
1183 }
1184 fn linear_vertical_impl(size: PxSize, stops: GradientStops, density: Option<PxDensity2d>, mask: Option<ImageMaskMode>) -> Self {
1185 assert!(size.width > Px(0));
1186 assert!(size.height > Px(0));
1187
1188 let mut line = PxLine::new(PxPoint::splat(Px(0)), PxPoint::new(Px(0), size.height));
1189 let mut render_stops = vec![];
1190
1191 LAYOUT.with_root_context(LayoutPassId::new(), LayoutMetrics::new(1.fct(), size, Px(14)), || {
1192 stops.layout_linear(LayoutAxis::Y, ExtendMode::Clamp, &mut line, &mut render_stops);
1193 });
1194 let line_a = line.start.y.0 as f32;
1195 let line_b = line.end.y.0 as f32;
1196
1197 let mut bgra = Vec::with_capacity(size.height.0 as usize);
1198 let mut render_stops = render_stops.into_iter();
1199 let mut stop_a = render_stops.next().unwrap();
1200 let mut stop_b = render_stops.next().unwrap();
1201 'outer: for y in 0..size.height.0 {
1202 let yf = y as f32;
1203 let yf = (yf - line_a) / (line_b - line_a);
1204 if yf < stop_a.offset {
1205 bgra.push(stop_a.color.to_bgra_bytes());
1207 continue;
1208 }
1209 while yf > stop_b.offset {
1210 if let Some(next_b) = render_stops.next() {
1211 stop_a = stop_b;
1213 stop_b = next_b;
1214 } else {
1215 for _ in y..size.height.0 {
1217 bgra.push(stop_b.color.to_bgra_bytes());
1218 }
1219 break 'outer;
1220 }
1221 }
1222
1223 let yf = (yf - stop_a.offset) / (stop_b.offset - stop_a.offset);
1225 let sample = stop_a.color.lerp(&stop_b.color, yf.fct());
1226 bgra.push(sample.to_bgra_bytes());
1227 }
1228
1229 match mask {
1230 Some(m) => {
1231 let len = size.width.0 as usize * size.height.0 as usize;
1232 let mut data = Vec::with_capacity(len);
1233
1234 for y in 0..size.height.0 {
1235 let c = bgra[y as usize];
1236 let c = match m {
1237 ImageMaskMode::A => c[3],
1238 ImageMaskMode::B => c[0],
1239 ImageMaskMode::G => c[1],
1240 ImageMaskMode::R => c[2],
1241 ImageMaskMode::Luminance => {
1242 let hsla = Hsla::from(Rgba::new(c[2], c[1], c[0], c[3]));
1243 (hsla.lightness * 255.0).round().clamp(0.0, 255.0) as u8
1244 }
1245 _ => unreachable!(),
1246 };
1247 for _x in 0..size.width.0 {
1248 data.push(c);
1249 }
1250 }
1251
1252 Self::from_data(
1253 IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"),
1254 ImageDataFormat::A8 { size },
1255 )
1256 }
1257 None => {
1258 let len = size.width.0 as usize * size.height.0 as usize * 4;
1259 let mut data = Vec::with_capacity(len);
1260
1261 for y in 0..size.height.0 {
1262 let c = bgra[y as usize];
1263 for _x in 0..size.width.0 {
1264 data.extend_from_slice(&c);
1265 }
1266 }
1267
1268 Self::from_data(
1269 IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"),
1270 ImageDataFormat::Bgra8 {
1271 size,
1272 density,
1273 original_color_type: ColorType::RGBA8,
1274 },
1275 )
1276 }
1277 }
1278 }
1279
1280 pub fn linear_horizontal(
1282 size: impl Into<PxSize>,
1283 stops: impl Into<GradientStops>,
1284 density: Option<PxDensity2d>,
1285 mask: Option<ImageMaskMode>,
1286 ) -> Self {
1287 Self::linear_horizontal_impl(size.into(), stops.into(), density, mask)
1288 }
1289 fn linear_horizontal_impl(size: PxSize, stops: GradientStops, density: Option<PxDensity2d>, mask: Option<ImageMaskMode>) -> Self {
1290 assert!(size.width > Px(0));
1291 assert!(size.height > Px(0));
1292
1293 let mut line = PxLine::new(PxPoint::splat(Px(0)), PxPoint::new(size.width, Px(0)));
1294 let mut render_stops = vec![];
1295 LAYOUT.with_root_context(LayoutPassId::new(), LayoutMetrics::new(1.fct(), size, Px(14)), || {
1296 stops.layout_linear(LayoutAxis::Y, ExtendMode::Clamp, &mut line, &mut render_stops);
1297 });
1298 let line_a = line.start.x.0 as f32;
1299 let line_b = line.end.x.0 as f32;
1300
1301 let mut bgra = Vec::with_capacity(size.width.0 as usize);
1302 let mut render_stops = render_stops.into_iter();
1303 let mut stop_a = render_stops.next().unwrap();
1304 let mut stop_b = render_stops.next().unwrap();
1305 'outer: for x in 0..size.width.0 {
1306 let xf = x as f32;
1307 let xf = (xf - line_a) / (line_b - line_a);
1308 if xf < stop_a.offset {
1309 bgra.push(stop_a.color.to_bgra_bytes());
1311 continue;
1312 }
1313 while xf > stop_b.offset {
1314 if let Some(next_b) = render_stops.next() {
1315 stop_a = stop_b;
1317 stop_b = next_b;
1318 } else {
1319 for _ in x..size.width.0 {
1321 bgra.push(stop_b.color.to_bgra_bytes());
1322 }
1323 break 'outer;
1324 }
1325 }
1326
1327 let xf = (xf - stop_a.offset) / (stop_b.offset - stop_a.offset);
1329 let sample = stop_a.color.lerp(&stop_b.color, xf.fct());
1330 bgra.push(sample.to_bgra_bytes());
1331 }
1332
1333 match mask {
1334 Some(m) => {
1335 let len = size.width.0 as usize * size.height.0 as usize;
1336 let mut data = Vec::with_capacity(len);
1337
1338 for _y in 0..size.height.0 {
1339 for c in &bgra {
1340 let c = match m {
1341 ImageMaskMode::A => c[3],
1342 ImageMaskMode::B => c[0],
1343 ImageMaskMode::G => c[1],
1344 ImageMaskMode::R => c[2],
1345 ImageMaskMode::Luminance => {
1346 let hsla = Hsla::from(Rgba::new(c[2], c[1], c[0], c[3]));
1347 (hsla.lightness * 255.0).round().clamp(0.0, 255.0) as u8
1348 }
1349 _ => unreachable!(),
1350 };
1351 data.push(c);
1352 }
1353 }
1354
1355 Self::from_data(
1356 IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"),
1357 ImageDataFormat::A8 { size },
1358 )
1359 }
1360 None => {
1361 let len = size.width.0 as usize * size.height.0 as usize * 4;
1362 let mut data = Vec::with_capacity(len);
1363
1364 for _y in 0..size.height.0 {
1365 for c in &bgra {
1366 data.extend_from_slice(c);
1367 }
1368 }
1369
1370 Self::from_data(
1371 IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"),
1372 ImageDataFormat::Bgra8 {
1373 size,
1374 density,
1375 original_color_type: ColorType::RGBA8,
1376 },
1377 )
1378 }
1379 }
1380 }
1381}
1382
1383impl PartialEq for ImageSource {
1384 fn eq(&self, other: &Self) -> bool {
1385 match (self, other) {
1386 (Self::Read(l), Self::Read(r)) => l == r,
1387 #[cfg(feature = "http")]
1388 (Self::Download(lu, la), Self::Download(ru, ra)) => lu == ru && la == ra,
1389 (Self::Render(lf, la), Self::Render(rf, ra)) => Arc::ptr_eq(lf, rf) && la == ra,
1390 (Self::Image(l), Self::Image(r)) => l.var_eq(r),
1391 (l, r) => {
1392 let l_hash = match l {
1393 ImageSource::Data(h, _, _) => h,
1394 _ => return false,
1395 };
1396 let r_hash = match r {
1397 ImageSource::Data(h, _, _) => h,
1398 _ => return false,
1399 };
1400
1401 l_hash == r_hash
1402 }
1403 }
1404 }
1405}
1406impl fmt::Debug for ImageSource {
1407 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1408 if f.alternate() {
1409 write!(f, "ImageSource::")?;
1410 }
1411 match self {
1412 ImageSource::Read(p) => f.debug_tuple("Read").field(p).finish(),
1413 #[cfg(feature = "http")]
1414 ImageSource::Download(u, a) => f.debug_tuple("Download").field(u).field(a).finish(),
1415 ImageSource::Data(key, bytes, fmt) => f.debug_tuple("Data").field(key).field(bytes).field(fmt).finish(),
1416
1417 ImageSource::Render(_, _) => write!(f, "Render(_, _)"),
1418 ImageSource::Image(_) => write!(f, "Image(_)"),
1419 ImageSource::Entries { primary, entries } => f
1420 .debug_struct("Entries")
1421 .field("primary", primary)
1422 .field("entries", entries)
1423 .finish(),
1424 }
1425 }
1426}
1427
1428#[cfg(feature = "http")]
1429impl_from_and_into_var! {
1430 fn from(uri: zng_task::http::Uri) -> ImageSource {
1431 ImageSource::Download(uri, None)
1432 }
1433 fn from((uri, accept): (zng_task::http::Uri, &'static str)) -> ImageSource {
1435 ImageSource::Download(uri, Some(accept.into()))
1436 }
1437
1438 fn from(s: &str) -> ImageSource {
1444 use zng_task::http::*;
1445 if let Ok(uri) = Uri::try_from(s)
1446 && let Some(scheme) = uri.scheme()
1447 {
1448 if scheme == &uri::Scheme::HTTPS || scheme == &uri::Scheme::HTTP {
1449 return ImageSource::Download(uri, None);
1450 } else if scheme.as_str() == "file" {
1451 return PathBuf::from(uri.path()).into();
1452 }
1453 }
1454 PathBuf::from(s).into()
1455 }
1456}
1457
1458#[cfg(not(feature = "http"))]
1459impl_from_and_into_var! {
1460 fn from(s: &str) -> ImageSource {
1464 PathBuf::from(s).into()
1465 }
1466}
1467
1468impl_from_and_into_var! {
1469 fn from(image: ImageVar) -> ImageSource {
1470 ImageSource::Image(image)
1471 }
1472 fn from(path: PathBuf) -> ImageSource {
1473 ImageSource::Read(path)
1474 }
1475 fn from(path: &Path) -> ImageSource {
1476 path.to_owned().into()
1477 }
1478
1479 fn from(s: String) -> ImageSource {
1481 s.as_str().into()
1482 }
1483 fn from(s: Txt) -> ImageSource {
1485 s.as_str().into()
1486 }
1487 fn from(data: &[u8]) -> ImageSource {
1491 ImageSource::Data(
1492 ImageHash::compute(data),
1493 IpcBytes::from_slice_blocking(data).expect("cannot allocate IpcBytes"),
1494 ImageDataFormat::Unknown,
1495 )
1496 }
1497 fn from<const N: usize>(data: &[u8; N]) -> ImageSource {
1501 (&data[..]).into()
1502 }
1503 fn from(data: IpcBytes) -> ImageSource {
1507 ImageSource::Data(ImageHash::compute(&data[..]), data, ImageDataFormat::Unknown)
1508 }
1509 fn from(data: Vec<u8>) -> ImageSource {
1513 IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes").into()
1514 }
1515 fn from<F: Into<ImageDataFormat>>((data, format): (&[u8], F)) -> ImageSource {
1517 ImageSource::Data(
1518 ImageHash::compute(data),
1519 IpcBytes::from_slice_blocking(data).expect("cannot allocate IpcBytes"),
1520 format.into(),
1521 )
1522 }
1523 fn from<F: Into<ImageDataFormat>, const N: usize>((data, format): (&[u8; N], F)) -> ImageSource {
1525 (&data[..], format).into()
1526 }
1527 fn from<F: Into<ImageDataFormat>>((data, format): (Vec<u8>, F)) -> ImageSource {
1529 (IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"), format).into()
1530 }
1531 fn from<F: Into<ImageDataFormat>>((data, format): (IpcBytes, F)) -> ImageSource {
1533 ImageSource::Data(ImageHash::compute(&data[..]), data, format.into())
1534 }
1535}
1536
1537#[derive(Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1541pub enum ImageCacheMode {
1542 Ignore,
1544 Cache,
1546 Retry,
1548 Reload,
1552}
1553impl fmt::Debug for ImageCacheMode {
1554 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1555 if f.alternate() {
1556 write!(f, "CacheMode::")?;
1557 }
1558 match self {
1559 Self::Ignore => write!(f, "Ignore"),
1560 Self::Cache => write!(f, "Cache"),
1561 Self::Retry => write!(f, "Retry"),
1562 Self::Reload => write!(f, "Reload"),
1563 }
1564 }
1565}
1566impl_from_and_into_var! {
1567 fn from(cache: bool) -> ImageCacheMode {
1568 if cache { ImageCacheMode::Cache } else { ImageCacheMode::Ignore }
1569 }
1570}
1571
1572#[derive(Clone)]
1574pub enum ImageSourceFilter<U> {
1575 BlockAll,
1577 AllowAll,
1579 Custom(Arc<dyn Fn(&U) -> bool + Send + Sync>),
1581}
1582impl<U> ImageSourceFilter<U> {
1583 pub fn custom(allow: impl Fn(&U) -> bool + Send + Sync + 'static) -> Self {
1587 Self::Custom(Arc::new(allow))
1588 }
1589
1590 pub fn and(self, other: Self) -> Self
1599 where
1600 U: 'static,
1601 {
1602 use ImageSourceFilter::*;
1603 match (self, other) {
1604 (BlockAll, _) | (_, BlockAll) => BlockAll,
1605 (AllowAll, _) | (_, AllowAll) => AllowAll,
1606 (Custom(c0), Custom(c1)) => Custom(Arc::new(move |u| c0(u) && c1(u))),
1607 }
1608 }
1609
1610 pub fn or(self, other: Self) -> Self
1619 where
1620 U: 'static,
1621 {
1622 use ImageSourceFilter::*;
1623 match (self, other) {
1624 (AllowAll, _) | (_, AllowAll) => AllowAll,
1625 (BlockAll, _) | (_, BlockAll) => BlockAll,
1626 (Custom(c0), Custom(c1)) => Custom(Arc::new(move |u| c0(u) || c1(u))),
1627 }
1628 }
1629
1630 pub fn allows(&self, item: &U) -> bool {
1632 match self {
1633 ImageSourceFilter::BlockAll => false,
1634 ImageSourceFilter::AllowAll => true,
1635 ImageSourceFilter::Custom(f) => f(item),
1636 }
1637 }
1638}
1639impl<U> fmt::Debug for ImageSourceFilter<U> {
1640 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1641 match self {
1642 Self::BlockAll => write!(f, "BlockAll"),
1643 Self::AllowAll => write!(f, "AllowAll"),
1644 Self::Custom(_) => write!(f, "Custom(_)"),
1645 }
1646 }
1647}
1648impl<U: 'static> ops::BitAnd for ImageSourceFilter<U> {
1649 type Output = Self;
1650
1651 fn bitand(self, rhs: Self) -> Self::Output {
1652 self.and(rhs)
1653 }
1654}
1655impl<U: 'static> ops::BitOr for ImageSourceFilter<U> {
1656 type Output = Self;
1657
1658 fn bitor(self, rhs: Self) -> Self::Output {
1659 self.or(rhs)
1660 }
1661}
1662impl<U: 'static> ops::BitAndAssign for ImageSourceFilter<U> {
1663 fn bitand_assign(&mut self, rhs: Self) {
1664 *self = mem::replace(self, Self::BlockAll).and(rhs);
1665 }
1666}
1667impl<U: 'static> ops::BitOrAssign for ImageSourceFilter<U> {
1668 fn bitor_assign(&mut self, rhs: Self) {
1669 *self = mem::replace(self, Self::BlockAll).or(rhs);
1670 }
1671}
1672impl<U> PartialEq for ImageSourceFilter<U> {
1673 fn eq(&self, other: &Self) -> bool {
1674 match (self, other) {
1675 (Self::Custom(l0), Self::Custom(r0)) => Arc::ptr_eq(l0, r0),
1676 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
1677 }
1678 }
1679}
1680
1681pub type PathFilter = ImageSourceFilter<PathBuf>;
1691impl PathFilter {
1692 pub fn allow_dir(dir: impl AsRef<Path>) -> Self {
1694 let dir = absolute_path(dir.as_ref(), || env::current_dir().expect("could not access current dir"), true);
1695 PathFilter::custom(move |r| r.starts_with(&dir))
1696 }
1697
1698 pub fn allow_ext(ext: impl Into<std::ffi::OsString>) -> Self {
1700 let ext = ext.into();
1701 PathFilter::custom(move |r| r.extension().map(|e| e == ext).unwrap_or(false))
1702 }
1703
1704 pub fn allow_current_dir() -> Self {
1712 PathFilter::custom(|r| env::current_dir().map(|d| r.starts_with(d)).unwrap_or(false))
1713 }
1714
1715 pub fn allow_exe_dir() -> Self {
1717 if let Ok(mut p) = env::current_exe().and_then(dunce::canonicalize)
1718 && p.pop()
1719 {
1720 return Self::allow_dir(p);
1721 }
1722
1723 Self::custom(|_| false)
1725 }
1726
1727 pub fn allow_res() -> Self {
1731 Self::allow_dir(zng_env::res(""))
1732 }
1733}
1734
1735#[cfg(feature = "http")]
1739pub type UriFilter = ImageSourceFilter<zng_task::http::Uri>;
1740#[cfg(feature = "http")]
1741impl UriFilter {
1742 pub fn allow_host(host: impl Into<Txt>) -> Self {
1744 let host = host.into();
1745 UriFilter::custom(move |u| u.authority().map(|a| a.host() == host).unwrap_or(false))
1746 }
1747}
1748
1749impl<F: Fn(&PathBuf) -> bool + Send + Sync + 'static> From<F> for PathFilter {
1750 fn from(custom: F) -> Self {
1751 PathFilter::custom(custom)
1752 }
1753}
1754
1755#[cfg(feature = "http")]
1756impl<F: Fn(&task::http::Uri) -> bool + Send + Sync + 'static> From<F> for UriFilter {
1757 fn from(custom: F) -> Self {
1758 UriFilter::custom(custom)
1759 }
1760}
1761
1762#[derive(Clone, Debug, PartialEq)]
1764#[non_exhaustive]
1765pub struct ImageLimits {
1766 pub max_encoded_len: ByteLength,
1774 pub max_decoded_len: ByteLength,
1778
1779 pub allow_path: PathFilter,
1781
1782 #[cfg(feature = "http")]
1784 pub allow_uri: UriFilter,
1785}
1786impl ImageLimits {
1787 pub fn none() -> Self {
1789 ImageLimits {
1790 max_encoded_len: ByteLength::MAX,
1791 max_decoded_len: ByteLength::MAX,
1792 allow_path: PathFilter::AllowAll,
1793 #[cfg(feature = "http")]
1794 allow_uri: UriFilter::AllowAll,
1795 }
1796 }
1797
1798 pub fn with_max_encoded_len(mut self, max_encoded_len: impl Into<ByteLength>) -> Self {
1802 self.max_encoded_len = max_encoded_len.into();
1803 self
1804 }
1805
1806 pub fn with_max_decoded_len(mut self, max_decoded_len: impl Into<ByteLength>) -> Self {
1810 self.max_decoded_len = max_decoded_len.into();
1811 self
1812 }
1813
1814 pub fn with_allow_path(mut self, allow_path: impl Into<PathFilter>) -> Self {
1818 self.allow_path = allow_path.into();
1819 self
1820 }
1821
1822 #[cfg(feature = "http")]
1826 pub fn with_allow_uri(mut self, allow_url: impl Into<UriFilter>) -> Self {
1827 self.allow_uri = allow_url.into();
1828 self
1829 }
1830}
1831impl Default for ImageLimits {
1832 fn default() -> Self {
1836 Self {
1837 max_encoded_len: 100.megabytes(),
1838 max_decoded_len: 4096.megabytes(),
1839 allow_path: PathFilter::allow_res(),
1840 #[cfg(feature = "http")]
1841 allow_uri: UriFilter::BlockAll,
1842 }
1843 }
1844}
1845impl_from_and_into_var! {
1846 fn from(some: ImageLimits) -> Option<ImageLimits>;
1847}
1848
1849#[derive(Debug, Clone, PartialEq)]
1853#[non_exhaustive]
1854pub struct ImageOptions {
1855 pub cache_mode: ImageCacheMode,
1857 pub downscale: Option<ImageDownscaleMode>,
1859 pub mask: Option<ImageMaskMode>,
1861 pub entries: ImageEntriesMode,
1863}
1864
1865impl ImageOptions {
1866 pub fn new(
1868 cache_mode: ImageCacheMode,
1869 downscale: Option<ImageDownscaleMode>,
1870 mask: Option<ImageMaskMode>,
1871 entries: ImageEntriesMode,
1872 ) -> Self {
1873 Self {
1874 cache_mode,
1875 downscale,
1876 mask,
1877 entries,
1878 }
1879 }
1880
1881 pub fn cache() -> Self {
1883 Self::new(ImageCacheMode::Cache, None, None, ImageEntriesMode::empty())
1884 }
1885
1886 pub fn none() -> Self {
1888 Self::new(ImageCacheMode::Ignore, None, None, ImageEntriesMode::empty())
1889 }
1890}
1891
1892fn absolute_path(path: &Path, base: impl FnOnce() -> PathBuf, allow_escape: bool) -> PathBuf {
1893 if path.is_absolute() {
1894 normalize_path(path)
1895 } else {
1896 let mut dir = base();
1897 if allow_escape {
1898 dir.push(path);
1899 normalize_path(&dir)
1900 } else {
1901 dir.push(normalize_path(path));
1902 dir
1903 }
1904 }
1905}
1906fn normalize_path(path: &Path) -> PathBuf {
1910 use std::path::Component;
1911
1912 let mut components = path.components().peekable();
1913 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
1914 components.next();
1915 PathBuf::from(c.as_os_str())
1916 } else {
1917 PathBuf::new()
1918 };
1919
1920 for component in components {
1921 match component {
1922 Component::Prefix(..) => unreachable!(),
1923 Component::RootDir => {
1924 ret.push(component.as_os_str());
1925 }
1926 Component::CurDir => {}
1927 Component::ParentDir => {
1928 ret.pop();
1929 }
1930 Component::Normal(c) => {
1931 ret.push(c);
1932 }
1933 }
1934 }
1935 ret
1936}