use crate::buffer;
use crate::image;
use std::borrow::Cow;
use std::{fs, io};
use crate::{Document, Error, Gltf, Result};
use image_crate::ImageFormat::{Jpeg, Png};
use std::path::Path;
type Import = (Document, Vec<buffer::Data>, Vec<image::Data>);
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
enum Scheme<'a> {
Data(Option<&'a str>, &'a str),
File(&'a str),
Relative(Cow<'a, str>),
Unsupported,
}
impl<'a> Scheme<'a> {
fn parse(uri: &str) -> Scheme<'_> {
if uri.contains(':') {
if let Some(rest) = uri.strip_prefix("data:") {
let mut it = rest.split(";base64,");
match (it.next(), it.next()) {
(match0_opt, Some(match1)) => Scheme::Data(match0_opt, match1),
(Some(match0), _) => Scheme::Data(None, match0),
_ => Scheme::Unsupported,
}
} else if let Some(rest) = uri.strip_prefix("file://") {
Scheme::File(rest)
} else if let Some(rest) = uri.strip_prefix("file:") {
Scheme::File(rest)
} else {
Scheme::Unsupported
}
} else {
Scheme::Relative(urlencoding::decode(uri).unwrap())
}
}
fn read(base: Option<&Path>, uri: &str) -> Result<Vec<u8>> {
match Scheme::parse(uri) {
Scheme::Data(_, base64) => base64::decode(base64).map_err(Error::Base64),
Scheme::File(path) if base.is_some() => read_to_end(path),
Scheme::Relative(path) if base.is_some() => read_to_end(base.unwrap().join(&*path)),
Scheme::Unsupported => Err(Error::UnsupportedScheme),
_ => Err(Error::ExternalReferenceInSliceImport),
}
}
}
fn read_to_end<P>(path: P) -> Result<Vec<u8>>
where
P: AsRef<Path>,
{
use io::Read;
let file = fs::File::open(path.as_ref()).map_err(Error::Io)?;
let length = file.metadata().map(|x| x.len() + 1).unwrap_or(0);
let mut reader = io::BufReader::new(file);
let mut data = Vec::with_capacity(length as usize);
reader.read_to_end(&mut data).map_err(Error::Io)?;
Ok(data)
}
impl buffer::Data {
pub fn from_source(source: buffer::Source<'_>, base: Option<&Path>) -> Result<Self> {
Self::from_source_and_blob(source, base, &mut None)
}
pub fn from_source_and_blob(
source: buffer::Source<'_>,
base: Option<&Path>,
blob: &mut Option<Vec<u8>>,
) -> Result<Self> {
let mut data = match source {
buffer::Source::Uri(uri) => Scheme::read(base, uri),
buffer::Source::Bin => blob.take().ok_or(Error::MissingBlob),
}?;
while data.len() % 4 != 0 {
data.push(0);
}
Ok(buffer::Data(data))
}
}
pub fn import_buffers(
document: &Document,
base: Option<&Path>,
mut blob: Option<Vec<u8>>,
) -> Result<Vec<buffer::Data>> {
let mut buffers = Vec::new();
for buffer in document.buffers() {
let data = buffer::Data::from_source_and_blob(buffer.source(), base, &mut blob)?;
if data.len() < buffer.length() {
return Err(Error::BufferLength {
buffer: buffer.index(),
expected: buffer.length(),
actual: data.len(),
});
}
buffers.push(data);
}
Ok(buffers)
}
impl image::Data {
pub fn from_source(
source: image::Source<'_>,
base: Option<&Path>,
buffer_data: &[buffer::Data],
) -> Result<Self> {
#[cfg(feature = "guess_mime_type")]
let guess_format = |encoded_image: &[u8]| match image_crate::guess_format(encoded_image) {
Ok(image_crate::ImageFormat::Png) => Some(Png),
Ok(image_crate::ImageFormat::Jpeg) => Some(Jpeg),
_ => None,
};
#[cfg(not(feature = "guess_mime_type"))]
let guess_format = |_encoded_image: &[u8]| None;
let decoded_image = match source {
image::Source::Uri { uri, mime_type } if base.is_some() => match Scheme::parse(uri) {
Scheme::Data(Some(annoying_case), base64) => {
let encoded_image = base64::decode(base64).map_err(Error::Base64)?;
let encoded_format = match annoying_case {
"image/png" => Png,
"image/jpeg" => Jpeg,
_ => match guess_format(&encoded_image) {
Some(format) => format,
None => return Err(Error::UnsupportedImageEncoding),
},
};
image_crate::load_from_memory_with_format(&encoded_image, encoded_format)?
}
Scheme::Unsupported => return Err(Error::UnsupportedScheme),
_ => {
let encoded_image = Scheme::read(base, uri)?;
let encoded_format = match mime_type {
Some("image/png") => Png,
Some("image/jpeg") => Jpeg,
Some(_) => match guess_format(&encoded_image) {
Some(format) => format,
None => return Err(Error::UnsupportedImageEncoding),
},
None => match uri.rsplit('.').next() {
Some("png") => Png,
Some("jpg") | Some("jpeg") => Jpeg,
_ => match guess_format(&encoded_image) {
Some(format) => format,
None => return Err(Error::UnsupportedImageEncoding),
},
},
};
image_crate::load_from_memory_with_format(&encoded_image, encoded_format)?
}
},
image::Source::View { view, mime_type } => {
let parent_buffer_data = &buffer_data[view.buffer().index()].0;
let begin = view.offset();
let end = begin + view.length();
let encoded_image = &parent_buffer_data[begin..end];
let encoded_format = match mime_type {
"image/png" => Png,
"image/jpeg" => Jpeg,
_ => match guess_format(encoded_image) {
Some(format) => format,
None => return Err(Error::UnsupportedImageEncoding),
},
};
image_crate::load_from_memory_with_format(encoded_image, encoded_format)?
}
_ => return Err(Error::ExternalReferenceInSliceImport),
};
image::Data::new(decoded_image)
}
}
pub fn import_images(
document: &Document,
base: Option<&Path>,
buffer_data: &[buffer::Data],
) -> Result<Vec<image::Data>> {
let mut images = Vec::new();
for image in document.images() {
images.push(image::Data::from_source(image.source(), base, buffer_data)?);
}
Ok(images)
}
fn import_impl(Gltf { document, blob }: Gltf, base: Option<&Path>) -> Result<Import> {
let buffer_data = import_buffers(&document, base, blob)?;
let image_data = import_images(&document, base, &buffer_data)?;
let import = (document, buffer_data, image_data);
Ok(import)
}
fn import_path(path: &Path) -> Result<Import> {
let base = path.parent().unwrap_or_else(|| Path::new("./"));
let file = fs::File::open(path).map_err(Error::Io)?;
let reader = io::BufReader::new(file);
import_impl(Gltf::from_reader(reader)?, Some(base))
}
pub fn import<P>(path: P) -> Result<Import>
where
P: AsRef<Path>,
{
import_path(path.as_ref())
}
fn import_slice_impl(slice: &[u8]) -> Result<Import> {
import_impl(Gltf::from_slice(slice)?, None)
}
pub fn import_slice<S>(slice: S) -> Result<Import>
where
S: AsRef<[u8]>,
{
import_slice_impl(slice.as_ref())
}