1use std::{
2 env, fmt, fs, io, mem, ops,
3 path::{Path, PathBuf},
4 sync::Arc,
5};
6
7use once_cell::sync::OnceCell;
8use parking_lot::Mutex;
9use zng_app::{
10 view_process::{EncodeError, ViewImage, ViewRenderer},
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},
20};
21use zng_task::{self as task, SignalOnce};
22use zng_txt::Txt;
23use zng_var::{Var, animation::Transitionable, impl_from_and_into_var};
24use zng_view_api::image::ImageTextureId;
25
26use crate::render::ImageRenderWindowRoot;
27
28pub use zng_view_api::image::{ImageDataFormat, ImageDownscale, ImageMaskMode};
29
30pub trait ImageCacheProxy: Send + Sync {
39 fn get(
41 &mut self,
42 key: &ImageHash,
43 source: &ImageSource,
44 mode: ImageCacheMode,
45 downscale: Option<ImageDownscale>,
46 mask: Option<ImageMaskMode>,
47 ) -> ProxyGetResult {
48 let r = match source {
49 ImageSource::Static(_, data, image_format) => self.data(key, data, image_format, mode, downscale, mask, false),
50 ImageSource::Data(_, data, image_format) => self.data(key, data, image_format, mode, downscale, mask, false),
51 _ => return ProxyGetResult::None,
52 };
53 match r {
54 Some(img) => ProxyGetResult::Image(img),
55 None => ProxyGetResult::None,
56 }
57 }
58
59 #[allow(clippy::too_many_arguments)]
73 fn data(
74 &mut self,
75 key: &ImageHash,
76 data: &[u8],
77 image_format: &ImageDataFormat,
78 mode: ImageCacheMode,
79 downscale: Option<ImageDownscale>,
80 mask: Option<ImageMaskMode>,
81 is_loaded: bool,
82 ) -> Option<ImageVar> {
83 let _ = (key, data, image_format, mode, downscale, mask, is_loaded);
84 None
85 }
86
87 fn remove(&mut self, key: &ImageHash, purge: bool) -> ProxyRemoveResult {
89 let _ = (key, purge);
90 ProxyRemoveResult::None
91 }
92
93 fn clear(&mut self, purge: bool) {
95 let _ = purge;
96 }
97
98 fn is_data_proxy(&self) -> bool {
113 false
114 }
115}
116
117pub enum ProxyGetResult {
119 None,
123 Cache(ImageSource, ImageCacheMode, Option<ImageDownscale>, Option<ImageMaskMode>),
125 Image(ImageVar),
127}
128
129pub enum ProxyRemoveResult {
131 None,
135 Remove(ImageHash, bool),
139 Removed,
141}
142
143pub type ImageVar = Var<Img>;
149
150#[derive(Debug, Clone)]
154pub struct Img {
155 pub(super) view: OnceCell<ViewImage>,
157 render_ids: Arc<Mutex<Vec<RenderImage>>>,
158 pub(super) done_signal: SignalOnce,
159 pub(super) cache_key: Option<ImageHash>,
160}
161impl PartialEq for Img {
162 fn eq(&self, other: &Self) -> bool {
163 self.view == other.view
164 }
165}
166impl Img {
167 pub(super) fn new_none(cache_key: Option<ImageHash>) -> Self {
168 Img {
169 view: OnceCell::new(),
170 render_ids: Arc::default(),
171 done_signal: SignalOnce::new(),
172 cache_key,
173 }
174 }
175
176 pub fn new(view: ViewImage) -> Self {
178 let sig = view.awaiter();
179 let v = OnceCell::new();
180 let _ = v.set(view);
181 Img {
182 view: v,
183 render_ids: Arc::default(),
184 done_signal: sig,
185 cache_key: None,
186 }
187 }
188
189 pub fn dummy(error: Option<Txt>) -> Self {
191 Self::new(ViewImage::dummy(error))
192 }
193
194 pub fn is_loading(&self) -> bool {
196 match self.view.get() {
197 Some(v) => !v.is_loaded() && !v.is_error(),
198 None => true,
199 }
200 }
201
202 pub fn is_loaded(&self) -> bool {
204 match self.view.get() {
205 Some(v) => v.is_loaded(),
206 None => false,
207 }
208 }
209
210 pub fn is_error(&self) -> bool {
212 match self.view.get() {
213 Some(v) => v.is_error(),
214 None => false,
215 }
216 }
217
218 pub fn error(&self) -> Option<Txt> {
220 match self.view.get() {
221 Some(v) => v.error(),
222 None => None,
223 }
224 }
225
226 pub fn wait_done(&self) -> impl Future<Output = ()> + Send + Sync + 'static {
228 self.done_signal.clone()
229 }
230
231 pub fn size(&self) -> PxSize {
233 self.view.get().map(|v| v.size()).unwrap_or_else(PxSize::zero)
234 }
235
236 pub fn density(&self) -> Option<PxDensity2d> {
239 self.view.get().and_then(|v| v.density())
240 }
241
242 pub fn is_opaque(&self) -> bool {
244 self.view.get().map(|v| v.is_opaque()).unwrap_or(true)
245 }
246
247 pub fn is_mask(&self) -> bool {
249 self.view.get().map(|v| v.is_mask()).unwrap_or(false)
250 }
251
252 pub fn view(&self) -> Option<&ViewImage> {
254 self.view.get().filter(|&v| v.is_loaded())
255 }
256
257 pub fn layout_size(&self, ctx: &LayoutMetrics) -> PxSize {
265 self.calc_size(ctx, PxDensity2d::splat(ctx.screen_density()), false)
266 }
267
268 pub fn calc_size(&self, ctx: &LayoutMetrics, fallback_density: PxDensity2d, ignore_image_density: bool) -> PxSize {
278 let dpi = if ignore_image_density {
279 fallback_density
280 } else {
281 self.density().unwrap_or(fallback_density)
282 };
283
284 let s_density = ctx.screen_density();
285 let mut size = self.size();
286
287 let fct = ctx.scale_factor().0;
288 size.width *= (s_density.ppcm() / dpi.width.ppcm()) * fct;
289 size.height *= (s_density.ppcm() / dpi.height.ppcm()) * fct;
290
291 size
292 }
293
294 pub fn pixels(&self) -> Option<zng_view_api::ipc::IpcBytes> {
298 self.view.get().and_then(|v| v.pixels())
299 }
300
301 pub fn copy_pixels(&self, rect: PxRect) -> Option<(PxRect, Vec<u8>)> {
309 self.pixels().map(|pixels| {
310 let area = PxRect::from_size(self.size()).intersection(&rect).unwrap_or_default();
311 if area.size.width.0 == 0 || area.size.height.0 == 0 {
312 (area, vec![])
313 } else {
314 let x = area.origin.x.0 as usize;
315 let y = area.origin.y.0 as usize;
316 let width = area.size.width.0 as usize;
317 let height = area.size.height.0 as usize;
318 let pixel = if self.is_mask() { 1 } else { 4 };
319 let mut bytes = Vec::with_capacity(width * height * pixel);
320 let row_stride = self.size().width.0 as usize * pixel;
321 for l in y..y + height {
322 let line_start = l * row_stride + x * pixel;
323 let line_end = line_start + width * pixel;
324 let line = &pixels[line_start..line_end];
325 bytes.extend_from_slice(line);
326 }
327 (area, bytes)
328 }
329 })
330 }
331
332 pub async fn encode(&self, format: Txt) -> std::result::Result<zng_view_api::ipc::IpcBytes, EncodeError> {
334 self.done_signal.clone().await;
335 if let Some(e) = self.error() {
336 Err(EncodeError::Encode(e))
337 } else {
338 self.view.get().unwrap().encode(format).await
339 }
340 }
341
342 pub async fn save(&self, path: impl Into<PathBuf>) -> io::Result<()> {
346 let path = path.into();
347 if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
348 self.save_impl(Txt::from_str(ext), path).await
349 } else {
350 Err(io::Error::new(
351 io::ErrorKind::InvalidInput,
352 "could not determinate image format from path extension",
353 ))
354 }
355 }
356
357 pub async fn save_with_format(&self, format: Txt, path: impl Into<PathBuf>) -> io::Result<()> {
361 self.save_impl(format, path.into()).await
362 }
363
364 async fn save_impl(&self, format: Txt, path: PathBuf) -> io::Result<()> {
365 let view = self.view.get().unwrap();
366 let data = view
367 .encode(format)
368 .await
369 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
370 task::wait(move || fs::write(path, &data[..])).await
371 }
372
373 pub(crate) fn inner_set_or_replace(&mut self, img: ViewImage, done: bool) {
374 match self.view.set(img) {
375 Ok(()) => {
376 if done {
377 self.done_signal.set();
378 }
379 }
380 Err(img) => {
381 let cache_key = self.cache_key;
383 *self = Self {
384 view: OnceCell::with_value(img),
385 render_ids: Arc::default(),
386 done_signal: if done { SignalOnce::new_set() } else { SignalOnce::new() },
387 cache_key,
388 };
389 }
390 }
391 }
392}
393impl zng_app::render::Img for Img {
394 fn renderer_id(&self, renderer: &ViewRenderer) -> ImageTextureId {
395 if self.is_loaded() {
396 let mut rms = self.render_ids.lock();
397 if let Some(rm) = rms.iter().find(|k| &k.renderer == renderer) {
398 return rm.image_id;
399 }
400
401 let key = match renderer.use_image(self.view.get().unwrap()) {
402 Ok(k) => {
403 if k == ImageTextureId::INVALID {
404 tracing::error!("received INVALID from `use_image`");
405 return k;
406 }
407 k
408 }
409 Err(_) => {
410 tracing::debug!("respawned `add_image`, will return INVALID");
411 return ImageTextureId::INVALID;
412 }
413 };
414
415 rms.push(RenderImage {
416 image_id: key,
417 renderer: renderer.clone(),
418 });
419 key
420 } else {
421 ImageTextureId::INVALID
422 }
423 }
424
425 fn size(&self) -> PxSize {
426 self.size()
427 }
428}
429
430struct RenderImage {
431 image_id: ImageTextureId,
432 renderer: ViewRenderer,
433}
434impl Drop for RenderImage {
435 fn drop(&mut self) {
436 let _ = self.renderer.delete_image_use(self.image_id);
438 }
439}
440impl fmt::Debug for RenderImage {
441 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
442 fmt::Debug::fmt(&self.image_id, f)
443 }
444}
445
446#[derive(Clone, Copy)]
454pub struct ImageHash([u8; 32]);
455impl ImageHash {
456 pub fn compute(data: &[u8]) -> Self {
458 let mut h = Self::hasher();
459 h.update(data);
460 h.finish()
461 }
462
463 pub fn hasher() -> ImageHasher {
465 ImageHasher::default()
466 }
467}
468impl fmt::Debug for ImageHash {
469 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
470 if f.alternate() {
471 f.debug_tuple("ImageHash").field(&self.0).finish()
472 } else {
473 use base64::*;
474 write!(f, "{}", base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(self.0))
475 }
476 }
477}
478impl fmt::Display for ImageHash {
479 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
480 write!(f, "{self:?}")
481 }
482}
483impl std::hash::Hash for ImageHash {
484 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
485 let h64 = [
486 self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], self.0[6], self.0[7],
487 ];
488 state.write_u64(u64::from_ne_bytes(h64))
489 }
490}
491impl PartialEq for ImageHash {
492 fn eq(&self, other: &Self) -> bool {
493 self.0 == other.0
494 }
495}
496impl Eq for ImageHash {}
497
498pub struct ImageHasher(sha2::Sha512_256);
500impl Default for ImageHasher {
501 fn default() -> Self {
502 use sha2::Digest;
503 Self(sha2::Sha512_256::new())
504 }
505}
506impl ImageHasher {
507 pub fn new() -> Self {
509 Self::default()
510 }
511
512 pub fn update(&mut self, data: impl AsRef<[u8]>) {
514 use sha2::Digest;
515 self.0.update(data);
516 }
517
518 pub fn finish(self) -> ImageHash {
520 use sha2::Digest;
521 #[allow(deprecated)]
525 ImageHash(self.0.finalize().as_slice().try_into().unwrap())
526 }
527}
528impl std::hash::Hasher for ImageHasher {
529 fn finish(&self) -> u64 {
530 tracing::warn!("Hasher::finish called for ImageHasher");
531
532 use sha2::Digest;
533 let hash = self.0.clone().finalize();
534 u64::from_le_bytes(hash[..8].try_into().unwrap())
535 }
536
537 fn write(&mut self, bytes: &[u8]) {
538 self.update(bytes);
539 }
540}
541
542type RenderFn = Arc<Box<dyn Fn(&ImageRenderArgs) -> Box<dyn ImageRenderWindowRoot> + Send + Sync>>;
544
545#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
549#[non_exhaustive]
550pub struct ImageRenderArgs {
551 pub parent: Option<WindowId>,
553}
554impl ImageRenderArgs {
555 pub fn new(parent: WindowId) -> Self {
557 Self { parent: Some(parent) }
558 }
559}
560
561#[derive(Clone)]
563#[non_exhaustive]
564pub enum ImageSource {
565 Read(PathBuf),
569 #[cfg(feature = "http")]
575 Download(crate::task::http::Uri, Option<Txt>),
576 Static(ImageHash, &'static [u8], ImageDataFormat),
580 Data(ImageHash, Arc<Vec<u8>>, ImageDataFormat),
588
589 Render(RenderFn, Option<ImageRenderArgs>),
595
596 Image(ImageVar),
600}
601impl ImageSource {
602 pub fn from_data(data: Arc<Vec<u8>>, format: ImageDataFormat) -> Self {
604 let mut hasher = ImageHasher::default();
605 hasher.update(&data[..]);
606 let hash = hasher.finish();
607 Self::Data(hash, data, format)
608 }
609
610 pub fn from_static(data: &'static [u8], format: ImageDataFormat) -> Self {
612 let mut hasher = ImageHasher::default();
613 hasher.update(data);
614 let hash = hasher.finish();
615 Self::Static(hash, data, format)
616 }
617
618 pub fn hash128(&self, downscale: Option<ImageDownscale>, mask: Option<ImageMaskMode>) -> Option<ImageHash> {
620 match self {
621 ImageSource::Read(p) => Some(Self::hash128_read(p, downscale, mask)),
622 #[cfg(feature = "http")]
623 ImageSource::Download(u, a) => Some(Self::hash128_download(u, a, downscale, mask)),
624 ImageSource::Static(h, _, _) => Some(Self::hash128_data(*h, downscale, mask)),
625 ImageSource::Data(h, _, _) => Some(Self::hash128_data(*h, downscale, mask)),
626 ImageSource::Render(rfn, args) => Some(Self::hash128_render(rfn, args, downscale, mask)),
627 ImageSource::Image(_) => None,
628 }
629 }
630
631 pub fn hash128_data(data_hash: ImageHash, downscale: Option<ImageDownscale>, mask: Option<ImageMaskMode>) -> ImageHash {
636 if downscale.is_some() || mask.is_some() {
637 use std::hash::Hash;
638 let mut h = ImageHash::hasher();
639 data_hash.0.hash(&mut h);
640 downscale.hash(&mut h);
641 mask.hash(&mut h);
642 h.finish()
643 } else {
644 data_hash
645 }
646 }
647
648 pub fn hash128_read(path: &Path, downscale: Option<ImageDownscale>, mask: Option<ImageMaskMode>) -> ImageHash {
652 use std::hash::Hash;
653 let mut h = ImageHash::hasher();
654 0u8.hash(&mut h);
655 path.hash(&mut h);
656 downscale.hash(&mut h);
657 mask.hash(&mut h);
658 h.finish()
659 }
660
661 #[cfg(feature = "http")]
665 pub fn hash128_download(
666 uri: &crate::task::http::Uri,
667 accept: &Option<Txt>,
668 downscale: Option<ImageDownscale>,
669 mask: Option<ImageMaskMode>,
670 ) -> ImageHash {
671 use std::hash::Hash;
672 let mut h = ImageHash::hasher();
673 1u8.hash(&mut h);
674 uri.hash(&mut h);
675 accept.hash(&mut h);
676 downscale.hash(&mut h);
677 mask.hash(&mut h);
678 h.finish()
679 }
680
681 pub fn hash128_render(
687 rfn: &RenderFn,
688 args: &Option<ImageRenderArgs>,
689 downscale: Option<ImageDownscale>,
690 mask: Option<ImageMaskMode>,
691 ) -> ImageHash {
692 use std::hash::Hash;
693 let mut h = ImageHash::hasher();
694 2u8.hash(&mut h);
695 (Arc::as_ptr(rfn) as usize).hash(&mut h);
696 args.hash(&mut h);
697 downscale.hash(&mut h);
698 mask.hash(&mut h);
699 h.finish()
700 }
701}
702
703impl ImageSource {
704 pub fn flood(size: impl Into<PxSize>, color: impl Into<Rgba>, density: Option<PxDensity2d>) -> Self {
706 Self::flood_impl(size.into(), color.into(), density)
707 }
708 fn flood_impl(size: PxSize, color: Rgba, density: Option<PxDensity2d>) -> Self {
709 let pixels = size.width.0 as usize * size.height.0 as usize;
710 let bgra = color.to_bgra_bytes();
711 let mut data = Vec::with_capacity(pixels * 4);
712 for _ in 0..pixels {
713 data.extend_from_slice(&bgra);
714 }
715 Self::from_data(Arc::new(data), ImageDataFormat::Bgra8 { size, density })
716 }
717
718 pub fn linear_vertical(
720 size: impl Into<PxSize>,
721 stops: impl Into<GradientStops>,
722 density: Option<PxDensity2d>,
723 mask: Option<ImageMaskMode>,
724 ) -> Self {
725 Self::linear_vertical_impl(size.into(), stops.into(), density, mask)
726 }
727 fn linear_vertical_impl(size: PxSize, stops: GradientStops, density: Option<PxDensity2d>, mask: Option<ImageMaskMode>) -> Self {
728 assert!(size.width > Px(0));
729 assert!(size.height > Px(0));
730
731 let mut line = PxLine::new(PxPoint::splat(Px(0)), PxPoint::new(Px(0), size.height));
732 let mut render_stops = vec![];
733
734 LAYOUT.with_root_context(LayoutPassId::new(), LayoutMetrics::new(1.fct(), size, Px(14)), || {
735 stops.layout_linear(LayoutAxis::Y, ExtendMode::Clamp, &mut line, &mut render_stops);
736 });
737 let line_a = line.start.y.0 as f32;
738 let line_b = line.end.y.0 as f32;
739
740 let mut bgra = Vec::with_capacity(size.height.0 as usize);
741 let mut render_stops = render_stops.into_iter();
742 let mut stop_a = render_stops.next().unwrap();
743 let mut stop_b = render_stops.next().unwrap();
744 'outer: for y in 0..size.height.0 {
745 let yf = y as f32;
746 let yf = (yf - line_a) / (line_b - line_a);
747 if yf < stop_a.offset {
748 bgra.push(stop_a.color.to_bgra_bytes());
750 continue;
751 }
752 while yf > stop_b.offset {
753 if let Some(next_b) = render_stops.next() {
754 stop_a = stop_b;
756 stop_b = next_b;
757 } else {
758 for _ in y..size.height.0 {
760 bgra.push(stop_b.color.to_bgra_bytes());
761 }
762 break 'outer;
763 }
764 }
765
766 let yf = (yf - stop_a.offset) / (stop_b.offset - stop_a.offset);
768 let sample = stop_a.color.lerp(&stop_b.color, yf.fct());
769 bgra.push(sample.to_bgra_bytes());
770 }
771
772 match mask {
773 Some(m) => {
774 let len = size.width.0 as usize * size.height.0 as usize;
775 let mut data = Vec::with_capacity(len);
776
777 for y in 0..size.height.0 {
778 let c = bgra[y as usize];
779 let c = match m {
780 ImageMaskMode::A => c[3],
781 ImageMaskMode::B => c[0],
782 ImageMaskMode::G => c[1],
783 ImageMaskMode::R => c[2],
784 ImageMaskMode::Luminance => {
785 let hsla = Hsla::from(Rgba::new(c[2], c[1], c[0], c[3]));
786 (hsla.lightness * 255.0).round().clamp(0.0, 255.0) as u8
787 }
788 _ => unreachable!(),
789 };
790 for _x in 0..size.width.0 {
791 data.push(c);
792 }
793 }
794
795 Self::from_data(Arc::new(data), ImageDataFormat::A8 { size })
796 }
797 None => {
798 let len = size.width.0 as usize * size.height.0 as usize * 4;
799 let mut data = Vec::with_capacity(len);
800
801 for y in 0..size.height.0 {
802 let c = bgra[y as usize];
803 for _x in 0..size.width.0 {
804 data.extend_from_slice(&c);
805 }
806 }
807
808 Self::from_data(Arc::new(data), ImageDataFormat::Bgra8 { size, density })
809 }
810 }
811 }
812
813 pub fn linear_horizontal(
815 size: impl Into<PxSize>,
816 stops: impl Into<GradientStops>,
817 density: Option<PxDensity2d>,
818 mask: Option<ImageMaskMode>,
819 ) -> Self {
820 Self::linear_horizontal_impl(size.into(), stops.into(), density, mask)
821 }
822 fn linear_horizontal_impl(size: PxSize, stops: GradientStops, density: Option<PxDensity2d>, mask: Option<ImageMaskMode>) -> Self {
823 assert!(size.width > Px(0));
824 assert!(size.height > Px(0));
825
826 let mut line = PxLine::new(PxPoint::splat(Px(0)), PxPoint::new(size.width, Px(0)));
827 let mut render_stops = vec![];
828 LAYOUT.with_root_context(LayoutPassId::new(), LayoutMetrics::new(1.fct(), size, Px(14)), || {
829 stops.layout_linear(LayoutAxis::Y, ExtendMode::Clamp, &mut line, &mut render_stops);
830 });
831 let line_a = line.start.x.0 as f32;
832 let line_b = line.end.x.0 as f32;
833
834 let mut bgra = Vec::with_capacity(size.width.0 as usize);
835 let mut render_stops = render_stops.into_iter();
836 let mut stop_a = render_stops.next().unwrap();
837 let mut stop_b = render_stops.next().unwrap();
838 'outer: for x in 0..size.width.0 {
839 let xf = x as f32;
840 let xf = (xf - line_a) / (line_b - line_a);
841 if xf < stop_a.offset {
842 bgra.push(stop_a.color.to_bgra_bytes());
844 continue;
845 }
846 while xf > stop_b.offset {
847 if let Some(next_b) = render_stops.next() {
848 stop_a = stop_b;
850 stop_b = next_b;
851 } else {
852 for _ in x..size.width.0 {
854 bgra.push(stop_b.color.to_bgra_bytes());
855 }
856 break 'outer;
857 }
858 }
859
860 let xf = (xf - stop_a.offset) / (stop_b.offset - stop_a.offset);
862 let sample = stop_a.color.lerp(&stop_b.color, xf.fct());
863 bgra.push(sample.to_bgra_bytes());
864 }
865
866 match mask {
867 Some(m) => {
868 let len = size.width.0 as usize * size.height.0 as usize;
869 let mut data = Vec::with_capacity(len);
870
871 for _y in 0..size.height.0 {
872 for c in &bgra {
873 let c = match m {
874 ImageMaskMode::A => c[3],
875 ImageMaskMode::B => c[0],
876 ImageMaskMode::G => c[1],
877 ImageMaskMode::R => c[2],
878 ImageMaskMode::Luminance => {
879 let hsla = Hsla::from(Rgba::new(c[2], c[1], c[0], c[3]));
880 (hsla.lightness * 255.0).round().clamp(0.0, 255.0) as u8
881 }
882 _ => unreachable!(),
883 };
884 data.push(c);
885 }
886 }
887
888 Self::from_data(Arc::new(data), ImageDataFormat::A8 { size })
889 }
890 None => {
891 let len = size.width.0 as usize * size.height.0 as usize * 4;
892 let mut data = Vec::with_capacity(len);
893
894 for _y in 0..size.height.0 {
895 for c in &bgra {
896 data.extend_from_slice(c);
897 }
898 }
899
900 Self::from_data(Arc::new(data), ImageDataFormat::Bgra8 { size, density })
901 }
902 }
903 }
904}
905
906impl PartialEq for ImageSource {
907 fn eq(&self, other: &Self) -> bool {
908 match (self, other) {
909 (Self::Read(l), Self::Read(r)) => l == r,
910 #[cfg(feature = "http")]
911 (Self::Download(lu, la), Self::Download(ru, ra)) => lu == ru && la == ra,
912 (Self::Render(lf, la), Self::Render(rf, ra)) => Arc::ptr_eq(lf, rf) && la == ra,
913 (Self::Image(l), Self::Image(r)) => l.var_eq(r),
914 (l, r) => {
915 let l_hash = match l {
916 ImageSource::Static(h, _, _) => h,
917 ImageSource::Data(h, _, _) => h,
918 _ => return false,
919 };
920 let r_hash = match r {
921 ImageSource::Static(h, _, _) => h,
922 ImageSource::Data(h, _, _) => h,
923 _ => return false,
924 };
925
926 l_hash == r_hash
927 }
928 }
929 }
930}
931impl fmt::Debug for ImageSource {
932 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
933 if f.alternate() {
934 write!(f, "ImageSource::")?;
935 }
936 match self {
937 ImageSource::Read(p) => f.debug_tuple("Read").field(p).finish(),
938 #[cfg(feature = "http")]
939 ImageSource::Download(u, a) => f.debug_tuple("Download").field(u).field(a).finish(),
940 ImageSource::Static(key, _, fmt) => f.debug_tuple("Static").field(key).field(fmt).finish(),
941 ImageSource::Data(key, _, fmt) => f.debug_tuple("Data").field(key).field(fmt).finish(),
942 ImageSource::Render(_, _) => write!(f, "Render(_, _)"),
943 ImageSource::Image(_) => write!(f, "Image(_)"),
944 }
945 }
946}
947
948#[cfg(feature = "http")]
949impl_from_and_into_var! {
950 fn from(uri: crate::task::http::Uri) -> ImageSource {
951 ImageSource::Download(uri, None)
952 }
953 fn from((uri, accept): (crate::task::http::Uri, &'static str)) -> ImageSource {
955 ImageSource::Download(uri, Some(accept.into()))
956 }
957
958 fn from(s: &str) -> ImageSource {
964 use crate::task::http::uri::*;
965 if let Ok(uri) = Uri::try_from(s)
966 && let Some(scheme) = uri.scheme()
967 {
968 if scheme == &Scheme::HTTPS || scheme == &Scheme::HTTP {
969 return ImageSource::Download(uri, None);
970 } else if scheme.as_str() == "file" {
971 return PathBuf::from(uri.path()).into();
972 }
973 }
974 PathBuf::from(s).into()
975 }
976}
977
978#[cfg(not(feature = "http"))]
979impl_from_and_into_var! {
980 fn from(s: &str) -> ImageSource {
984 PathBuf::from(s).into()
985 }
986}
987
988impl_from_and_into_var! {
989 fn from(image: ImageVar) -> ImageSource {
990 ImageSource::Image(image)
991 }
992 fn from(path: PathBuf) -> ImageSource {
993 ImageSource::Read(path)
994 }
995 fn from(path: &Path) -> ImageSource {
996 path.to_owned().into()
997 }
998
999 fn from(s: String) -> ImageSource {
1001 s.as_str().into()
1002 }
1003 fn from(s: Txt) -> ImageSource {
1005 s.as_str().into()
1006 }
1007 fn from(data: &'static [u8]) -> ImageSource {
1011 ImageSource::Static(ImageHash::compute(data), data, ImageDataFormat::Unknown)
1012 }
1013 fn from<const N: usize>(data: &'static [u8; N]) -> ImageSource {
1017 (&data[..]).into()
1018 }
1019 fn from(data: Arc<Vec<u8>>) -> ImageSource {
1023 ImageSource::Data(ImageHash::compute(&data[..]), data, ImageDataFormat::Unknown)
1024 }
1025 fn from(data: Vec<u8>) -> ImageSource {
1029 Arc::new(data).into()
1030 }
1031 fn from<F: Into<ImageDataFormat>>((data, format): (&'static [u8], F)) -> ImageSource {
1033 ImageSource::Static(ImageHash::compute(data), data, format.into())
1034 }
1035 fn from<F: Into<ImageDataFormat>, const N: usize>((data, format): (&'static [u8; N], F)) -> ImageSource {
1037 (&data[..], format).into()
1038 }
1039 fn from<F: Into<ImageDataFormat>>((data, format): (Vec<u8>, F)) -> ImageSource {
1041 (Arc::new(data), format).into()
1042 }
1043 fn from<F: Into<ImageDataFormat>>((data, format): (Arc<Vec<u8>>, F)) -> ImageSource {
1045 ImageSource::Data(ImageHash::compute(&data[..]), data, format.into())
1046 }
1047}
1048
1049#[derive(Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1053pub enum ImageCacheMode {
1054 Ignore,
1056 Cache,
1058 Retry,
1060 Reload,
1064}
1065impl fmt::Debug for ImageCacheMode {
1066 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1067 if f.alternate() {
1068 write!(f, "CacheMode::")?;
1069 }
1070 match self {
1071 Self::Ignore => write!(f, "Ignore"),
1072 Self::Cache => write!(f, "Cache"),
1073 Self::Retry => write!(f, "Retry"),
1074 Self::Reload => write!(f, "Reload"),
1075 }
1076 }
1077}
1078
1079#[derive(Clone)]
1081pub enum ImageSourceFilter<U> {
1082 BlockAll,
1084 AllowAll,
1086 Custom(Arc<dyn Fn(&U) -> bool + Send + Sync>),
1088}
1089impl<U> ImageSourceFilter<U> {
1090 pub fn custom(allow: impl Fn(&U) -> bool + Send + Sync + 'static) -> Self {
1094 Self::Custom(Arc::new(allow))
1095 }
1096
1097 pub fn and(self, other: Self) -> Self
1106 where
1107 U: 'static,
1108 {
1109 use ImageSourceFilter::*;
1110 match (self, other) {
1111 (BlockAll, _) | (_, BlockAll) => BlockAll,
1112 (AllowAll, _) | (_, AllowAll) => AllowAll,
1113 (Custom(c0), Custom(c1)) => Custom(Arc::new(move |u| c0(u) && c1(u))),
1114 }
1115 }
1116
1117 pub fn or(self, other: Self) -> Self
1126 where
1127 U: 'static,
1128 {
1129 use ImageSourceFilter::*;
1130 match (self, other) {
1131 (AllowAll, _) | (_, AllowAll) => AllowAll,
1132 (BlockAll, _) | (_, BlockAll) => BlockAll,
1133 (Custom(c0), Custom(c1)) => Custom(Arc::new(move |u| c0(u) || c1(u))),
1134 }
1135 }
1136
1137 pub fn allows(&self, item: &U) -> bool {
1139 match self {
1140 ImageSourceFilter::BlockAll => false,
1141 ImageSourceFilter::AllowAll => true,
1142 ImageSourceFilter::Custom(f) => f(item),
1143 }
1144 }
1145}
1146impl<U> fmt::Debug for ImageSourceFilter<U> {
1147 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1148 match self {
1149 Self::BlockAll => write!(f, "BlockAll"),
1150 Self::AllowAll => write!(f, "AllowAll"),
1151 Self::Custom(_) => write!(f, "Custom(_)"),
1152 }
1153 }
1154}
1155impl<U: 'static> ops::BitAnd for ImageSourceFilter<U> {
1156 type Output = Self;
1157
1158 fn bitand(self, rhs: Self) -> Self::Output {
1159 self.and(rhs)
1160 }
1161}
1162impl<U: 'static> ops::BitOr for ImageSourceFilter<U> {
1163 type Output = Self;
1164
1165 fn bitor(self, rhs: Self) -> Self::Output {
1166 self.or(rhs)
1167 }
1168}
1169impl<U: 'static> ops::BitAndAssign for ImageSourceFilter<U> {
1170 fn bitand_assign(&mut self, rhs: Self) {
1171 *self = mem::replace(self, Self::BlockAll).and(rhs);
1172 }
1173}
1174impl<U: 'static> ops::BitOrAssign for ImageSourceFilter<U> {
1175 fn bitor_assign(&mut self, rhs: Self) {
1176 *self = mem::replace(self, Self::BlockAll).or(rhs);
1177 }
1178}
1179impl<U> PartialEq for ImageSourceFilter<U> {
1180 fn eq(&self, other: &Self) -> bool {
1181 match (self, other) {
1182 (Self::Custom(l0), Self::Custom(r0)) => Arc::ptr_eq(l0, r0),
1183 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
1184 }
1185 }
1186}
1187
1188pub type PathFilter = ImageSourceFilter<PathBuf>;
1198impl PathFilter {
1199 pub fn allow_dir(dir: impl AsRef<Path>) -> Self {
1201 let dir = crate::absolute_path(dir.as_ref(), || env::current_dir().expect("could not access current dir"), true);
1202 PathFilter::custom(move |r| r.starts_with(&dir))
1203 }
1204
1205 pub fn allow_ext(ext: impl Into<std::ffi::OsString>) -> Self {
1207 let ext = ext.into();
1208 PathFilter::custom(move |r| r.extension().map(|e| e == ext).unwrap_or(false))
1209 }
1210
1211 pub fn allow_current_dir() -> Self {
1219 PathFilter::custom(|r| env::current_dir().map(|d| r.starts_with(d)).unwrap_or(false))
1220 }
1221
1222 pub fn allow_exe_dir() -> Self {
1224 if let Ok(mut p) = env::current_exe().and_then(dunce::canonicalize)
1225 && p.pop()
1226 {
1227 return Self::allow_dir(p);
1228 }
1229
1230 Self::custom(|_| false)
1232 }
1233
1234 pub fn allow_res() -> Self {
1238 Self::allow_dir(zng_env::res(""))
1239 }
1240}
1241
1242#[cfg(feature = "http")]
1246pub type UriFilter = ImageSourceFilter<crate::task::http::Uri>;
1247#[cfg(feature = "http")]
1248impl UriFilter {
1249 pub fn allow_host(host: impl Into<Txt>) -> Self {
1251 let host = host.into();
1252 UriFilter::custom(move |u| u.authority().map(|a| a.host() == host).unwrap_or(false))
1253 }
1254}
1255
1256impl<F: Fn(&PathBuf) -> bool + Send + Sync + 'static> From<F> for PathFilter {
1257 fn from(custom: F) -> Self {
1258 PathFilter::custom(custom)
1259 }
1260}
1261
1262#[cfg(feature = "http")]
1263impl<F: Fn(&task::http::Uri) -> bool + Send + Sync + 'static> From<F> for UriFilter {
1264 fn from(custom: F) -> Self {
1265 UriFilter::custom(custom)
1266 }
1267}
1268
1269#[derive(Clone, Debug, PartialEq)]
1271#[non_exhaustive]
1272pub struct ImageLimits {
1273 pub max_encoded_len: ByteLength,
1281 pub max_decoded_len: ByteLength,
1285
1286 pub allow_path: PathFilter,
1290
1291 #[cfg(feature = "http")]
1293 pub allow_uri: UriFilter,
1294}
1295impl ImageLimits {
1296 pub fn none() -> Self {
1298 ImageLimits {
1299 max_encoded_len: ByteLength::MAX,
1300 max_decoded_len: ByteLength::MAX,
1301 allow_path: PathFilter::AllowAll,
1302 #[cfg(feature = "http")]
1303 allow_uri: UriFilter::AllowAll,
1304 }
1305 }
1306
1307 pub fn with_max_encoded_len(mut self, max_encoded_size: impl Into<ByteLength>) -> Self {
1311 self.max_encoded_len = max_encoded_size.into();
1312 self
1313 }
1314
1315 pub fn with_max_decoded_len(mut self, max_decoded_size: impl Into<ByteLength>) -> Self {
1319 self.max_decoded_len = max_decoded_size.into();
1320 self
1321 }
1322
1323 pub fn with_allow_path(mut self, allow_path: impl Into<PathFilter>) -> Self {
1327 self.allow_path = allow_path.into();
1328 self
1329 }
1330
1331 #[cfg(feature = "http")]
1335 pub fn with_allow_uri(mut self, allow_url: impl Into<UriFilter>) -> Self {
1336 self.allow_uri = allow_url.into();
1337 self
1338 }
1339}
1340impl Default for ImageLimits {
1341 fn default() -> Self {
1345 Self {
1346 max_encoded_len: 100.megabytes(),
1347 max_decoded_len: 4096.megabytes(),
1348 allow_path: PathFilter::allow_res(),
1349 #[cfg(feature = "http")]
1350 allow_uri: UriFilter::BlockAll,
1351 }
1352 }
1353}
1354impl_from_and_into_var! {
1355 fn from(some: ImageLimits) -> Option<ImageLimits>;
1356}