gltf_v1/
import.rs

1use indexmap::IndexMap;
2
3use crate::{GLTF_Error, buffer, document::Document, error::Result, image};
4use std::{borrow::Cow, fs, io, path::Path};
5
6#[derive(Clone, Debug, Eq, Hash, PartialEq)]
7enum Scheme<'a> {
8    Data(Option<&'a str>, &'a str),
9    File(&'a str),
10    Relative(Cow<'a, str>),
11    Unsupported,
12}
13
14impl Scheme<'_> {
15    fn parse(uri: &str) -> Scheme<'_> {
16        if uri.contains(':') {
17            if let Some(rest) = uri.strip_prefix("data:") {
18                let mut it = rest.split(";base64,");
19
20                match (it.next(), it.next()) {
21                    (match0_opt, Some(match1)) => Scheme::Data(match0_opt, match1),
22                    (Some(match0), _) => Scheme::Data(None, match0),
23                    _ => Scheme::Unsupported,
24                }
25            } else if let Some(rest) = uri.strip_prefix("file://") {
26                Scheme::File(rest)
27            } else if let Some(rest) = uri.strip_prefix("file:") {
28                Scheme::File(rest)
29            } else {
30                Scheme::Unsupported
31            }
32        } else {
33            Scheme::Relative(urlencoding::decode(uri).unwrap())
34        }
35    }
36
37    fn read(base: Option<&Path>, uri: &str) -> Result<Vec<u8>> {
38        match Scheme::parse(uri) {
39            // The path may be unused in the Scheme::Data case
40            // Example: "uri" : "data:application/octet-stream;base64,wsVHPgA...."
41            Scheme::Data(_, base64) => base64::decode(base64).map_err(GLTF_Error::Base64),
42            Scheme::File(path) if base.is_some() => read_to_end(path),
43            Scheme::Relative(path) if base.is_some() => read_to_end(base.unwrap().join(&*path)),
44            Scheme::Unsupported => Err(GLTF_Error::UnsupportedScheme),
45            _ => Err(GLTF_Error::ExternalReferenceInSliceImport),
46        }
47    }
48}
49
50fn read_to_end<P>(path: P) -> Result<Vec<u8>>
51where
52    P: AsRef<Path>,
53{
54    use io::Read;
55    let file = fs::File::open(path.as_ref()).map_err(GLTF_Error::Io)?;
56    // Allocate one extra byte so the buffer doesn't need to grow before the
57    // final `read` call at the end of the file.  Don't worry about `usize`
58    // overflow because reading will fail regardless in that case.
59    let length = file.metadata().map(|x| x.len() + 1).unwrap_or(0);
60    let mut reader = io::BufReader::new(file);
61    let mut data = Vec::with_capacity(length as usize);
62    reader.read_to_end(&mut data).map_err(GLTF_Error::Io)?;
63    Ok(data)
64}
65
66impl buffer::Data {
67    pub fn from_source(source: buffer::Source<'_>, base: Option<&Path>) -> Result<Self> {
68        Self::from_source_and_blob(source, base, &mut None)
69    }
70
71    pub fn from_source_and_blob(
72        source: buffer::Source<'_>,
73        base: Option<&Path>,
74        blob: &mut Option<Vec<u8>>,
75    ) -> Result<Self> {
76        let mut data = match source {
77            buffer::Source::Uri(uri) => Scheme::read(base, uri),
78            buffer::Source::Bin => blob.take().ok_or(GLTF_Error::MissingBlob),
79        }?;
80        while data.len() % 4 != 0 {
81            data.push(0);
82        }
83        Ok(buffer::Data(data))
84    }
85}
86
87pub fn import_buffers(
88    document: &Document,
89    base: Option<&Path>,
90    mut blob: Option<Vec<u8>>,
91) -> Result<IndexMap<String, buffer::Data>> {
92    let mut buffers = IndexMap::new();
93    for buffer in document.buffers() {
94        let index = buffer.index();
95        let data = buffer::Data::from_source_and_blob(buffer.source(), base, &mut blob)?;
96        if data.len() < buffer.length() {
97            return Err(GLTF_Error::BufferLength {
98                buffer: buffer.index().to_string(),
99                expected: buffer.length(),
100                actual: data.len(),
101            });
102        }
103        buffers.insert(index.to_string(), data);
104    }
105    Ok(buffers)
106}
107
108pub fn import_images(
109    document: &Document,
110    base: Option<&Path>,
111    buffer_data: &IndexMap<String, buffer::Data>,
112) -> Result<IndexMap<String, image::Data>> {
113    let mut images = IndexMap::new();
114    for image in document.images() {
115        let index = image.index();
116        let data = image::Data::from_source(image.source(), base, buffer_data)?;
117        images.insert(index.to_string(), data);
118    }
119    Ok(images)
120}
121
122impl image::Data {
123    pub fn from_source(
124        source: image::Source<'_>,
125        base: Option<&Path>,
126        buffer_data: &IndexMap<String, buffer::Data>,
127    ) -> Result<Self> {
128        let guess_format = |encoded_image: &[u8]| match image_crate::guess_format(encoded_image) {
129            Ok(image_crate::ImageFormat::Png) => Some(image_crate::ImageFormat::Png),
130            Ok(image_crate::ImageFormat::Jpeg) => Some(image_crate::ImageFormat::Jpeg),
131            Ok(image_crate::ImageFormat::Bmp) => Some(image_crate::ImageFormat::Bmp),
132            Ok(image_crate::ImageFormat::Gif) => Some(image_crate::ImageFormat::Gif),
133            _ => None,
134        };
135        let decoded_image = match source {
136            image::Source::Uri(uri) if base.is_some() => match Scheme::parse(uri) {
137                Scheme::Data(Some(annoying_case), base64) => {
138                    let encoded_image = base64::decode(base64).map_err(GLTF_Error::Base64)?;
139                    let encoded_format = match annoying_case {
140                        "image/png" => image_crate::ImageFormat::Png,
141                        "image/jpeg" => image_crate::ImageFormat::Jpeg,
142                        "image/bmp" => image_crate::ImageFormat::Bmp,
143                        "image/gif" => image_crate::ImageFormat::Gif,
144                        _ => match guess_format(&encoded_image) {
145                            Some(format) => format,
146                            None => return Err(GLTF_Error::UnsupportedImageEncoding),
147                        },
148                    };
149                    image_crate::load_from_memory_with_format(&encoded_image, encoded_format)?
150                }
151                Scheme::Unsupported => return Err(GLTF_Error::UnsupportedScheme),
152                _ => {
153                    let encoded_image = Scheme::read(base, uri)?;
154                    let encoded_format = image::Source::mime_type_format(uri).unwrap_or(
155                        match guess_format(&encoded_image) {
156                            Some(format) => format,
157                            None => return Err(GLTF_Error::UnsupportedImageEncoding),
158                        },
159                    );
160                    image_crate::load_from_memory_with_format(&encoded_image, encoded_format)?
161                }
162            },
163            image::Source::View { view, json } => {
164                let parent_buffer_data = &buffer_data[view.buffer().index()].0;
165                let begin = view.offset();
166                let end = begin + view.length();
167                let encoded_image = &parent_buffer_data[begin..end];
168                let encoded_format = match json.mime_type.as_str() {
169                    "image/png" => image_crate::ImageFormat::Png,
170                    "image/jpeg" => image_crate::ImageFormat::Jpeg,
171                    "image/bmp" => image_crate::ImageFormat::Bmp,
172                    "image/gif" => image_crate::ImageFormat::Gif,
173                    _ => match guess_format(encoded_image) {
174                        Some(format) => format,
175                        None => return Err(GLTF_Error::UnsupportedImageEncoding),
176                    },
177                };
178                image_crate::load_from_memory_with_format(encoded_image, encoded_format)?
179            }
180            _ => return Err(GLTF_Error::ExternalReferenceInSliceImport),
181        };
182        image::Data::new(decoded_image)
183    }
184}