[go: up one dir, main page]

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