Skip to main content

lumen_assets/loaders/
image.rs

1//! Raster image loader, decoding to RGBA8 plus a `peniko::Blob`.
2
3use std::sync::Arc;
4
5use crate::{
6    AssetKind, AssetLoader, ImageData, LoadContext, LoadErrorKind, LoadedAsset, LoadedImage,
7    PixBytes,
8};
9
10/// Extensions the image loader claims.
11///
12/// The workspace builds the `image` crate with PNG support only, so PNG is
13/// the format that decodes. Other raster extensions still reach this loader
14/// through [`crate::AssetLoaders`]' fallback and fail with a decoder message
15/// naming the format.
16pub const IMAGE_EXTENSIONS: &[&str] = &["png"];
17
18/// Decodes a raster image into [`ImageData`].
19///
20/// The decoded pixels are published twice over one allocation: as `rgba` for
21/// consumers that read pixels directly, and as a `peniko::Blob` whose
22/// identity keys a renderer's image upload cache across frames.
23pub struct ImageLoader;
24
25impl AssetLoader for ImageLoader {
26    fn extensions(&self) -> &[&str] {
27        IMAGE_EXTENSIONS
28    }
29
30    fn kind(&self) -> AssetKind {
31        AssetKind::Image
32    }
33
34    fn load(&self, ctx: &LoadContext<'_>) -> Result<LoadedAsset, LoadErrorKind> {
35        let path = ctx.path();
36        // When a source already produced the bytes the decode runs from
37        // memory; otherwise `image::open` reads the file itself, which is
38        // also what distinguishes a missing file from undecodable contents.
39        let img = match ctx.bytes() {
40            Some(bytes) => image::load_from_memory(bytes)
41                .map_err(|e| LoadErrorKind::DecodeFailed(format!("{path:?}: {e}")))?,
42            None => match image::open(path) {
43                Ok(img) => img,
44                Err(image::ImageError::IoError(io)) => return Err(LoadErrorKind::from(io)),
45                Err(image::ImageError::Unsupported(e)) => {
46                    return Err(LoadErrorKind::DecodeFailed(format!("{path:?}: {e}")));
47                }
48                Err(e) => return Err(LoadErrorKind::DecodeFailed(format!("{path:?}: {e}"))),
49            },
50        };
51        let rgba = img.to_rgba8();
52        let (width, height) = rgba.dimensions();
53        let pixels: Arc<[u8]> = Arc::from(rgba.into_raw());
54        let blob = peniko::Blob::new(Arc::new(PixBytes(pixels.clone())));
55        let data = ImageData {
56            width,
57            height,
58            rgba: pixels,
59            blob,
60        };
61        Ok(LoadedAsset::Image(LoadedImage(data.into())))
62    }
63}