1use std::{
2 collections::hash_map::DefaultHasher,
3 fs,
4 hash::{
5 Hash,
6 Hasher,
7 },
8 path::PathBuf,
9 rc::Rc,
10 sync::LazyLock,
11};
12
13use anyhow::Context;
14use async_lock::Semaphore;
15use bytes::Bytes;
16use freya_core::{
17 elements::image::*,
18 prelude::*,
19};
20use freya_engine::prelude::{
21 Paint,
22 SkData,
23 SkImage,
24 SkRect,
25 raster_n32_premul,
26};
27use torin::prelude::{
28 Size,
29 Size2D,
30};
31#[cfg(feature = "remote-asset")]
32use url::Url;
33
34use crate::{
35 cache::*,
36 loader::CircularLoader,
37};
38
39#[derive(PartialEq, Clone)]
90pub enum ImageSource {
91 #[cfg(feature = "remote-asset")]
95 Uri(Url),
96
97 Path(PathBuf),
98
99 Bytes(u64, Bytes),
100}
101
102impl<H: Hash> From<(H, Bytes)> for ImageSource {
103 fn from((id, bytes): (H, Bytes)) -> Self {
104 let mut hasher = DefaultHasher::default();
105 id.hash(&mut hasher);
106 Self::Bytes(hasher.finish(), bytes)
107 }
108}
109
110impl<H: Hash> From<(H, &'static [u8])> for ImageSource {
111 fn from((id, bytes): (H, &'static [u8])) -> Self {
112 (id, Bytes::from_static(bytes)).into()
113 }
114}
115
116impl<const N: usize, H: Hash> From<(H, &'static [u8; N])> for ImageSource {
117 fn from((id, bytes): (H, &'static [u8; N])) -> Self {
118 (id, Bytes::from_static(bytes)).into()
119 }
120}
121
122impl From<Bytes> for ImageSource {
123 fn from(bytes: Bytes) -> Self {
124 let mut hasher = DefaultHasher::default();
125 bytes.hash(&mut hasher);
126 Self::Bytes(hasher.finish(), bytes)
127 }
128}
129
130impl From<&'static [u8]> for ImageSource {
131 fn from(bytes: &'static [u8]) -> Self {
132 Bytes::from_static(bytes).into()
133 }
134}
135
136impl<const N: usize> From<&'static [u8; N]> for ImageSource {
137 fn from(bytes: &'static [u8; N]) -> Self {
138 Bytes::from_static(bytes).into()
139 }
140}
141
142#[cfg_attr(feature = "docs", doc(cfg(feature = "remote-asset")))]
143#[cfg(feature = "remote-asset")]
144impl From<Url> for ImageSource {
145 fn from(uri: Url) -> Self {
146 Self::Uri(uri)
147 }
148}
149
150#[cfg_attr(feature = "docs", doc(cfg(feature = "remote-asset")))]
151#[cfg(feature = "remote-asset")]
152impl From<&'static str> for ImageSource {
153 fn from(src: &'static str) -> Self {
154 Self::Uri(Url::parse(src).expect("Invalid URL"))
155 }
156}
157
158impl From<PathBuf> for ImageSource {
159 fn from(path: PathBuf) -> Self {
160 Self::Path(path)
161 }
162}
163
164impl Hash for ImageSource {
165 fn hash<H: Hasher>(&self, state: &mut H) {
166 match self {
167 #[cfg(feature = "remote-asset")]
168 Self::Uri(uri) => uri.hash(state),
169 Self::Path(path) => path.hash(state),
170 Self::Bytes(id, _) => id.hash(state),
171 }
172 }
173}
174
175pub type DecodeSize = euclid::Size2D<u32, ()>;
176
177static DECODE_LIMIT: LazyLock<Semaphore> = LazyLock::new(|| Semaphore::new(4));
179
180impl ImageSource {
181 pub(crate) fn is_remote(&self) -> bool {
182 #[cfg(feature = "remote-asset")]
183 if let Self::Uri(_) = self {
184 return true;
185 }
186 false
187 }
188
189 pub(crate) fn fetch(self) -> anyhow::Result<Bytes> {
191 Ok(match self {
192 #[cfg(feature = "remote-asset")]
193 Self::Uri(uri) => anyhow::bail!("{uri} must be loaded in parallel."),
194 Self::Path(path) => fs::read(path).map(Bytes::from)?,
195 Self::Bytes(_, bytes) => bytes,
196 })
197 }
198
199 pub(crate) async fn bytes(self) -> anyhow::Result<Bytes> {
201 match self {
202 #[cfg(feature = "remote-asset")]
203 Self::Uri(uri) => crate::http::fetch(uri).await,
204 source => thread(move || source.fetch()).await,
205 }
206 }
207
208 pub async fn load(
210 &self,
211 decode_size: Option<DecodeSize>,
212 sampling_mode: SamplingMode,
213 ) -> anyhow::Result<(SkImage, Bytes)> {
214 let _decode_permit = DECODE_LIMIT.acquire().await;
215 let bytes = self.clone().bytes().await?;
216 thread(move || {
217 let image = SkImage::from_encoded(unsafe { SkData::new_bytes(&bytes) })
218 .context("Failed to decode Image.")?;
219 let image = image.make_raster_image(None, None).unwrap_or(image);
220 let image = decode_size
221 .and_then(|target| Self::downsample(&image, target, &sampling_mode))
222 .unwrap_or(image);
223 Ok((image, bytes))
224 })
225 .await
226 }
227
228 fn downsample(
229 encoded: &SkImage,
230 target: DecodeSize,
231 sampling_mode: &SamplingMode,
232 ) -> Option<SkImage> {
233 let natural_width = encoded.width() as f32;
234 let natural_height = encoded.height() as f32;
235 let target_width = target.width as f32;
236 let target_height = target.height as f32;
237 if natural_width <= target_width && natural_height <= target_height {
238 return None;
239 }
240 let ratio = (target_width / natural_width).min(target_height / natural_height);
241 let width = (natural_width * ratio).round().max(1.);
242 let height = (natural_height * ratio).round().max(1.);
243
244 let mut surface = raster_n32_premul((width as i32, height as i32))?;
245 let destination = SkRect::from_xywh(0., 0., width, height);
246 let sampling = sampling_mode.sampling_options();
247 let mut paint = Paint::default();
248 paint.set_anti_alias(true);
249 surface.canvas().draw_image_rect_with_sampling_options(
250 encoded,
251 None,
252 destination,
253 sampling,
254 &paint,
255 );
256 Some(surface.image_snapshot())
257 }
258}
259
260#[derive(Default, Clone, Debug, PartialEq, Copy)]
262pub enum DecodeMode {
263 #[default]
266 FromLayout,
267 Source,
269 Custom(Size2D),
271}
272
273impl DecodeMode {
274 fn resolve(&self, layout: &LayoutData, scale_factor: f64) -> Option<DecodeSize> {
275 let scale = scale_factor as f32;
276 let size = match self {
277 Self::Source => return None,
278 Self::FromLayout => match (&layout.width, &layout.height) {
279 (Size::Pixels(width), Size::Pixels(height)) => {
280 Size2D::new(width.get() * scale, height.get() * scale)
281 }
282 _ => return None,
283 },
284 Self::Custom(size) => *size,
285 };
286 Some(DecodeSize::new(
287 size.width.round().max(1.) as u32,
288 size.height.round().max(1.) as u32,
289 ))
290 }
291}
292
293#[derive(PartialEq)]
315pub struct ImageViewer {
316 source: ImageSource,
317 asset_age: AssetAge,
318
319 layout: LayoutData,
320 image_data: ImageData,
321 accessibility: AccessibilityData,
322 effect: EffectData,
323 style: StyleState,
324 decode_mode: DecodeMode,
325
326 children: Vec<Element>,
327 loading_placeholder: Option<Element>,
328 error_renderer: Option<Callback<String, Element>>,
329
330 key: DiffKey,
331}
332
333impl ImageViewer {
334 pub fn new(source: impl Into<ImageSource>) -> Self {
335 ImageViewer {
336 source: source.into(),
337 asset_age: AssetAge::default(),
338 layout: LayoutData::default(),
339 image_data: ImageData::default(),
340 accessibility: AccessibilityData::default(),
341 effect: EffectData::default(),
342 style: StyleState::default(),
343 decode_mode: DecodeMode::default(),
344 children: Vec::new(),
345 loading_placeholder: None,
346 error_renderer: None,
347 key: DiffKey::None,
348 }
349 }
350}
351
352impl KeyExt for ImageViewer {
353 fn key_mut(&mut self) -> &mut DiffKey {
354 &mut self.key
355 }
356}
357
358impl LayoutExt for ImageViewer {
359 fn layout_mut(&mut self) -> &mut LayoutData {
360 &mut self.layout
361 }
362}
363
364impl ContainerSizeExt for ImageViewer {}
365impl ContainerWithContentExt for ImageViewer {}
366impl ContainerPositionExt for ImageViewer {}
367
368impl ImageExt for ImageViewer {
369 fn image_data_mut(&mut self) -> &mut ImageData {
370 &mut self.image_data
371 }
372}
373
374impl AccessibilityExt for ImageViewer {
375 fn accessibility_data_mut(&mut self) -> &mut AccessibilityData {
376 &mut self.accessibility
377 }
378}
379
380impl ChildrenExt for ImageViewer {
381 fn children_mut(&mut self) -> &mut Vec<Element> {
382 &mut self.children
383 }
384}
385
386impl EffectExt for ImageViewer {
387 fn effect_mut(&mut self) -> &mut EffectData {
388 &mut self.effect
389 }
390}
391
392impl DecorationExt for ImageViewer {}
393
394impl StyleExt for ImageViewer {
395 fn style_mut(&mut self) -> &mut StyleState {
396 &mut self.style
397 }
398}
399
400impl ImageViewer {
401 pub fn loading_placeholder(mut self, placeholder: impl Into<Element>) -> Self {
403 self.loading_placeholder = Some(placeholder.into());
404 self
405 }
406
407 pub fn decode_mode(mut self, decode_mode: DecodeMode) -> Self {
409 self.decode_mode = decode_mode;
410 self
411 }
412
413 pub fn asset_age(mut self, asset_age: impl Into<AssetAge>) -> Self {
417 self.asset_age = asset_age.into();
418 self
419 }
420
421 pub fn error_renderer(mut self, renderer: impl Into<Callback<String, Element>>) -> Self {
423 self.error_renderer = Some(renderer.into());
424 self
425 }
426}
427
428impl Component for ImageViewer {
429 fn render(&self) -> impl IntoElement {
430 let target = self.decode_mode.resolve(
431 &self.layout,
432 *Platform::get().current_window().scale_factor.read(),
433 );
434 let sampling_mode = self.image_data.sampling_mode.clone();
435 let asset_config =
436 AssetConfiguration::new((&self.source, target, &sampling_mode), self.asset_age);
437 use_asset(&asset_config);
438 let mut asset_cacher = use_hook(AssetCacher::get);
439
440 use_side_effect_with_deps(
441 &(
442 self.source.clone(),
443 asset_config.clone(),
444 target,
445 sampling_mode,
446 ),
447 move |(source, asset_config, target, sampling_mode)| {
448 if matches!(
449 asset_cacher.read_asset(asset_config),
450 Some(Asset::Pending) | Some(Asset::Error(_))
451 ) {
452 asset_cacher.update_asset(asset_config.clone(), Asset::Loading);
453
454 let source = source.clone();
455 let asset_config = asset_config.clone();
456 let target = *target;
457 let sampling_mode = sampling_mode.clone();
458 spawn_in_window(async move {
459 match source.load(target, sampling_mode).await {
460 Ok((image, bytes)) => {
461 asset_cacher.update_asset(
462 asset_config,
463 Asset::Cached(Rc::new(ImageHandle::new(image, bytes))),
464 );
465 }
466 Err(err) => {
467 asset_cacher
468 .update_asset(asset_config, Asset::Error(err.to_string()));
469 }
470 }
471 });
472 }
473 },
474 );
475
476 let asset = asset_cacher
477 .read_asset(&asset_config)
478 .expect("Asset should exist by now");
479
480 match asset {
481 Asset::Cached(asset) => {
482 let asset = asset.downcast_ref::<ImageHandle>().unwrap().clone();
483 image(asset)
484 .accessibility(self.accessibility.clone())
485 .a11y_role(AccessibilityRole::Image)
486 .layout(self.layout.clone())
487 .image_data(self.image_data.clone())
488 .effect(self.effect.clone())
489 .children(self.children.clone())
490 .style(self.style.clone())
491 .into_element()
492 }
493 Asset::Pending | Asset::Loading => rect()
494 .layout(self.layout.clone())
495 .center()
496 .child(
497 self.loading_placeholder
498 .clone()
499 .unwrap_or_else(|| CircularLoader::new().into_element()),
500 )
501 .into(),
502 Asset::Error(err) => match &self.error_renderer {
503 Some(renderer) => renderer.call(err),
504 None => err.into(),
505 },
506 }
507 }
508
509 fn render_key(&self) -> DiffKey {
510 self.key.clone().or(self.default_key())
511 }
512}