Skip to main content

gltf/
import.rs

1use crate::buffer;
2use crate::image;
3use std::borrow::Cow;
4use std::{fs, io};
5
6use crate::{Document, Error, Gltf, Result};
7use base64::{Engine, engine::{general_purpose}};
8#[cfg(feature = "EXT_texture_webp")]
9use image_crate::ImageFormat::WebP;
10use image_crate::ImageFormat::{Jpeg, Png};
11use std::path::Path;
12
13/// Return type of `import`.
14type Import = (Document, Vec<buffer::Data>, Vec<image::Data>);
15
16/// Represents the set of URI schemes the importer supports.
17#[derive(Clone, Debug, Eq, Hash, PartialEq)]
18enum Scheme<'a> {
19    /// `data:[<media type>];base64,<data>`.
20    Data(Option<&'a str>, &'a str),
21
22    /// `file:[//]<absolute file path>`.
23    ///
24    /// Note: The file scheme does not implement authority.
25    File(&'a str),
26
27    /// `../foo`, etc.
28    Relative(Cow<'a, str>),
29
30    /// Placeholder for an unsupported URI scheme identifier.
31    Unsupported,
32}
33
34impl<'a> Scheme<'a> {
35    fn parse(uri: &str) -> Scheme<'_> {
36        if uri.contains(':') {
37            if let Some(rest) = uri.strip_prefix("data:") {
38                let mut it = rest.split(";base64,");
39
40                match (it.next(), it.next()) {
41                    (match0_opt, Some(match1)) => Scheme::Data(match0_opt, match1),
42                    (Some(match0), _) => Scheme::Data(None, match0),
43                    _ => Scheme::Unsupported,
44                }
45            } else if let Some(rest) = uri.strip_prefix("file://") {
46                Scheme::File(rest)
47            } else if let Some(rest) = uri.strip_prefix("file:") {
48                Scheme::File(rest)
49            } else {
50                Scheme::Unsupported
51            }
52        } else {
53            Scheme::Relative(urlencoding::decode(uri).unwrap())
54        }
55    }
56
57    fn read(base: Option<&Path>, uri: &str) -> Result<Vec<u8>> {
58        match Scheme::parse(uri) {
59            // The path may be unused in the Scheme::Data case
60            // Example: "uri" : "data:application/octet-stream;base64,wsVHPgA...."
61            Scheme::Data(_, base64) => general_purpose::STANDARD.decode(base64).map_err(Error::Base64),
62            Scheme::File(path) if base.is_some() => read_to_end(path),
63            Scheme::Relative(path) if base.is_some() => read_to_end(base.unwrap().join(&*path)),
64            Scheme::Unsupported => Err(Error::UnsupportedScheme),
65            _ => Err(Error::ExternalReferenceInSliceImport),
66        }
67    }
68}
69
70fn read_to_end<P>(path: P) -> Result<Vec<u8>>
71where
72    P: AsRef<Path>,
73{
74    use io::Read;
75    let file = fs::File::open(path.as_ref()).map_err(Error::Io)?;
76    // Allocate one extra byte so the buffer doesn't need to grow before the
77    // final `read` call at the end of the file.  Don't worry about `usize`
78    // overflow because reading will fail regardless in that case.
79    let length = file.metadata().map(|x| x.len() + 1).unwrap_or(0);
80    let mut reader = io::BufReader::new(file);
81    let mut data = Vec::with_capacity(length as usize);
82    reader.read_to_end(&mut data).map_err(Error::Io)?;
83    Ok(data)
84}
85
86impl buffer::Data {
87    /// Construct a buffer data object by reading the given source.
88    /// If `base` is provided, then external filesystem references will
89    /// be resolved from this directory.
90    pub fn from_source(source: buffer::Source<'_>, base: Option<&Path>) -> Result<Self> {
91        Self::from_source_and_blob(source, base, &mut None)
92    }
93
94    /// Construct a buffer data object by reading the given source.
95    /// If `base` is provided, then external filesystem references will
96    /// be resolved from this directory.
97    /// `blob` represents the `BIN` section of a binary glTF file,
98    /// and it will be taken to fill the buffer if the `source` refers to it.
99    pub fn from_source_and_blob(
100        source: buffer::Source<'_>,
101        base: Option<&Path>,
102        blob: &mut Option<Vec<u8>>,
103    ) -> Result<Self> {
104        let mut data = match source {
105            buffer::Source::Uri(uri) => Scheme::read(base, uri),
106            buffer::Source::Bin => blob.take().ok_or(Error::MissingBlob),
107        }?;
108        while data.len() % 4 != 0 {
109            data.push(0);
110        }
111        Ok(buffer::Data(data))
112    }
113}
114
115/// Import buffer data referenced by a glTF document.
116///
117/// ### Note
118///
119/// This function is intended for advanced users who wish to forego loading image data.
120/// A typical user should call [`import`] instead.
121pub fn import_buffers(
122    document: &Document,
123    base: Option<&Path>,
124    mut blob: Option<Vec<u8>>,
125) -> Result<Vec<buffer::Data>> {
126    let mut buffers = Vec::new();
127    for buffer in document.buffers() {
128        let data = buffer::Data::from_source_and_blob(buffer.source(), base, &mut blob)?;
129        if data.len() < buffer.length() {
130            return Err(Error::BufferLength {
131                buffer: buffer.index(),
132                expected: buffer.length(),
133                actual: data.len(),
134            });
135        }
136        buffers.push(data);
137    }
138    Ok(buffers)
139}
140
141impl image::Data {
142    /// Construct an image data object by reading the given source.
143    /// If `base` is provided, then external filesystem references will
144    /// be resolved from this directory.
145    pub fn from_source(
146        source: image::Source<'_>,
147        base: Option<&Path>,
148        buffer_data: &[buffer::Data],
149    ) -> Result<Self> {
150        #[cfg(feature = "guess_mime_type")]
151        let guess_format = |encoded_image: &[u8]| match image_crate::guess_format(encoded_image) {
152            Ok(image_crate::ImageFormat::Png) => Some(Png),
153            Ok(image_crate::ImageFormat::Jpeg) => Some(Jpeg),
154            #[cfg(feature = "EXT_texture_webp")]
155            Ok(image_crate::ImageFormat::WebP) => Some(WebP),
156            _ => None,
157        };
158        #[cfg(not(feature = "guess_mime_type"))]
159        let guess_format = |_encoded_image: &[u8]| None;
160        let decoded_image = match source {
161            image::Source::Uri { uri, mime_type } if base.is_some() => match Scheme::parse(uri) {
162                Scheme::Data(Some(annoying_case), base64) => {
163                    let encoded_image = general_purpose::STANDARD.decode(base64).map_err(Error::Base64)?;
164                    let encoded_format = match annoying_case {
165                        "image/png" => Png,
166                        "image/jpeg" => Jpeg,
167                        #[cfg(feature = "EXT_texture_webp")]
168                        "image/webp" => WebP,
169                        _ => match guess_format(&encoded_image) {
170                            Some(format) => format,
171                            None => return Err(Error::UnsupportedImageEncoding),
172                        },
173                    };
174
175                    image_crate::load_from_memory_with_format(&encoded_image, encoded_format)?
176                }
177                Scheme::Unsupported => return Err(Error::UnsupportedScheme),
178                _ => {
179                    let encoded_image = Scheme::read(base, uri)?;
180                    let encoded_format = match mime_type {
181                        Some("image/png") => Png,
182                        Some("image/jpeg") => Jpeg,
183                        #[cfg(feature = "EXT_texture_webp")]
184                        Some("image/webp") => WebP,
185                        Some(_) => match guess_format(&encoded_image) {
186                            Some(format) => format,
187                            None => return Err(Error::UnsupportedImageEncoding),
188                        },
189                        None => match uri.rsplit('.').next() {
190                            Some("png") => Png,
191                            Some("jpg") | Some("jpeg") => Jpeg,
192                            #[cfg(feature = "EXT_texture_webp")]
193                            Some("webp") => WebP,
194                            _ => match guess_format(&encoded_image) {
195                                Some(format) => format,
196                                None => return Err(Error::UnsupportedImageEncoding),
197                            },
198                        },
199                    };
200                    image_crate::load_from_memory_with_format(&encoded_image, encoded_format)?
201                }
202            },
203            image::Source::View { view, mime_type } => {
204                let parent_buffer_data = &buffer_data[view.buffer().index()].0;
205                let begin = view.offset();
206                let end = begin + view.length();
207                let encoded_image = &parent_buffer_data[begin..end];
208                let encoded_format = match mime_type {
209                    "image/png" => Png,
210                    "image/jpeg" => Jpeg,
211                    #[cfg(feature = "EXT_texture_webp")]
212                    "image/webp" => WebP,
213                    _ => match guess_format(encoded_image) {
214                        Some(format) => format,
215                        None => return Err(Error::UnsupportedImageEncoding),
216                    },
217                };
218                image_crate::load_from_memory_with_format(encoded_image, encoded_format)?
219            }
220            _ => return Err(Error::ExternalReferenceInSliceImport),
221        };
222
223        image::Data::new(decoded_image)
224    }
225}
226
227/// Import image data referenced by a glTF document.
228///
229/// ### Note
230///
231/// This function is intended for advanced users who wish to forego loading buffer data.
232/// A typical user should call [`import`] instead.
233pub fn import_images(
234    document: &Document,
235    base: Option<&Path>,
236    buffer_data: &[buffer::Data],
237) -> Result<Vec<image::Data>> {
238    let mut images = Vec::new();
239    for image in document.images() {
240        images.push(image::Data::from_source(image.source(), base, buffer_data)?);
241    }
242    Ok(images)
243}
244
245fn import_impl(Gltf { document, blob }: Gltf, base: Option<&Path>) -> Result<Import> {
246    let buffer_data = import_buffers(&document, base, blob)?;
247    let image_data = import_images(&document, base, &buffer_data)?;
248    let import = (document, buffer_data, image_data);
249    Ok(import)
250}
251
252fn import_path(path: &Path) -> Result<Import> {
253    let base = path.parent().unwrap_or_else(|| Path::new("./"));
254    let file = fs::File::open(path).map_err(Error::Io)?;
255    let reader = io::BufReader::new(file);
256    import_impl(Gltf::from_reader(reader)?, Some(base))
257}
258
259/// Import glTF 2.0 from the file system.
260///
261/// ```
262/// # fn run() -> Result<(), gltf::Error> {
263/// # let path = "examples/Box.gltf";
264/// # #[allow(unused)]
265/// let (document, buffers, images) = gltf::import(path)?;
266/// # Ok(())
267/// # }
268/// # fn main() {
269/// #     run().expect("test failure");
270/// # }
271/// ```
272///
273/// ### Note
274///
275/// This function is provided as a convenience for loading glTF and associated
276/// resources from the file system. It is suitable for real world use but may
277/// not be suitable for all real world use cases. More complex import scenarios
278/// such downloading from web URLs are not handled by this function. These
279/// scenarios are delegated to the user.
280///
281/// You can read glTF without loading resources by constructing the [`Gltf`]
282/// (standard glTF) or [`Glb`] (binary glTF) data structures explicitly.
283///
284/// [`Gltf`]: struct.Gltf.html
285/// [`Glb`]: struct.Glb.html
286pub fn import<P>(path: P) -> Result<Import>
287where
288    P: AsRef<Path>,
289{
290    import_path(path.as_ref())
291}
292
293fn import_slice_impl(slice: &[u8]) -> Result<Import> {
294    import_impl(Gltf::from_slice(slice)?, None)
295}
296
297/// Import glTF 2.0 from a slice.
298///
299/// File paths in the document are assumed to be relative to the current working
300/// directory.
301///
302/// ### Note
303///
304/// This function is intended for advanced users.
305/// A typical user should call [`import`] instead.
306///
307/// ```
308/// # extern crate gltf;
309/// # use std::fs;
310/// # use std::io::Read;
311/// # fn run() -> Result<(), gltf::Error> {
312/// # let path = "examples/Box.glb";
313/// # let mut file = fs::File::open(path).map_err(gltf::Error::Io)?;
314/// # let mut bytes = Vec::new();
315/// # file.read_to_end(&mut bytes).map_err(gltf::Error::Io)?;
316/// # #[allow(unused)]
317/// let (document, buffers, images) = gltf::import_slice(bytes.as_slice())?;
318/// # Ok(())
319/// # }
320/// # fn main() {
321/// #     run().expect("test failure");
322/// # }
323/// ```
324pub fn import_slice<S>(slice: S) -> Result<Import>
325where
326    S: AsRef<[u8]>,
327{
328    import_slice_impl(slice.as_ref())
329}