mugltf/loader/
file_loader.rs1#![cfg(feature = "file-loader")]
4
5use super::GltfResourceLoader;
6use crate::Error;
7use alloc::{boxed::Box, string::String, vec::Vec};
8use async_trait::async_trait;
9use core::fmt::{self, Debug};
10use data_url::DataUrl;
11use image::{
12 error::{ImageFormatHint, UnsupportedError, UnsupportedErrorKind},
13 ImageError, ImageFormat, ImageResult,
14};
15use mugl::Extent2D;
16use std::{fs::File, io::Read, path::PathBuf};
17
18#[derive(Debug)]
20pub struct GltfResourceFileLoader {
21 path: String,
22}
23
24impl Default for GltfResourceFileLoader {
25 fn default() -> Self {
26 Self { path: "./".into() }
27 }
28}
29
30#[async_trait(?Send)]
31impl GltfResourceLoader for GltfResourceFileLoader {
32 type Error = Box<Error>;
33 type ImageData = (Vec<u8>, Extent2D);
34
35 #[inline]
36 fn set_path(&mut self, path: &str) {
37 self.path = path.into();
38 }
39
40 async fn get_gltf(&self, uri: &str) -> Result<Vec<u8>, Self::Error> {
41 Ok(read_file(&self.path, uri)?)
42 }
43
44 async fn get_buffer(&self, uri: &str) -> Result<Vec<u8>, Self::Error> {
45 if let Some(data) = try_read_data_url(uri, false)? {
46 Ok(data)
47 } else {
48 Ok(read_file(&self.path, uri)?)
49 }
50 }
51
52 async fn get_image(&self, uri: &str) -> Result<Self::ImageData, Self::Error> {
53 let data = if let Some(data) = try_read_data_url(uri, true)? {
54 data
55 } else {
56 read_file(&self.path, uri)?
57 };
58 let dynimage = image::load_from_memory(data.as_slice())?;
59 let size = Extent2D(dynimage.width(), dynimage.height());
60 Ok((dynimage.into_bytes(), size))
61 }
62
63 async fn decode_image(
64 &self,
65 img: &[u8],
66 mime_type: &str,
67 ) -> Result<Self::ImageData, Self::Error> {
68 let format = get_image_format(mime_type)?;
69 let dynimage = image::load_from_memory_with_format(img, format)?;
70 let size = Extent2D(dynimage.width(), dynimage.height());
71 Ok((dynimage.into_bytes(), size))
72 }
73}
74
75fn read_file(path: &str, file: &str) -> Result<Vec<u8>, std::io::Error> {
76 let mut file_path = PathBuf::from(path);
77 file_path.push(file);
78 let mut file = File::open(file_path)?;
79 let mut content = Vec::new();
80 file.read_to_end(&mut content)?;
81 Ok(content)
82}
83
84fn try_read_data_url(uri: &str, is_image: bool) -> Result<Option<Vec<u8>>, Box<Error>> {
85 if uri.starts_with("data:") {
86 let data_url = DataUrl::process(uri).map_err(|err| DataUrlError::InvalidDataUrl(err))?;
87 let is_supported_mime = if is_image {
88 is_supported_image_mime(&data_url.mime_type().type_, &data_url.mime_type().subtype)
89 } else {
90 is_supported_buffer_mime(&data_url.mime_type().type_, &data_url.mime_type().subtype)
91 };
92 if !is_supported_mime {
93 return Err(DataUrlError::UnsupportedMimeType(
94 data_url.mime_type().type_.clone(),
95 data_url.mime_type().subtype.clone(),
96 )
97 .into());
98 }
99
100 let (data, _) = data_url
101 .decode_to_vec()
102 .map_err(|err| DataUrlError::InvalidBase64(err))?;
103 Ok(Some(data))
104 } else {
105 Ok(None)
106 }
107}
108
109#[inline]
110fn is_supported_buffer_mime(type_: &str, subtype: &str) -> bool {
111 type_ == "application" && (subtype == "gltf-buffer" || subtype == "octet-stream")
112}
113
114#[inline]
115fn is_supported_image_mime(type_: &str, subtype: &str) -> bool {
116 type_ == "image" && (subtype == "png" || subtype == "jpeg")
117}
118
119fn get_image_format(mime_type: &str) -> ImageResult<ImageFormat> {
120 if mime_type == "image/png" {
121 Ok(ImageFormat::Png)
122 } else if mime_type == "image/jpeg" {
123 Ok(ImageFormat::Jpeg)
124 } else {
125 let format_hint = ImageFormatHint::Name(mime_type.into());
126 Err(ImageError::Unsupported(
127 UnsupportedError::from_format_and_kind(
128 format_hint.clone(),
129 UnsupportedErrorKind::Format(format_hint),
130 ),
131 ))
132 }
133}
134
135#[derive(Debug)]
136pub enum DataUrlError {
137 InvalidDataUrl(data_url::DataUrlError),
138 InvalidBase64(data_url::forgiving_base64::InvalidBase64),
139 UnsupportedMimeType(String, String),
140}
141
142impl fmt::Display for DataUrlError {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 match self {
145 DataUrlError::InvalidDataUrl(err) => err.fmt(f),
146 DataUrlError::InvalidBase64(err) => err.fmt(f),
147 DataUrlError::UnsupportedMimeType(type_, sub_type) => {
148 write!(f, "unsupported mime type: {}/{}", type_, sub_type)
149 }
150 }
151 }
152}
153
154impl std::error::Error for DataUrlError {}