mirror of
https://github.com/bevyengine/bevy
synced 2024-09-20 06:22:01 +00:00
gltf-loader: support data url for images (#1828)
This allows the `glTF-Embedded` variants in the [sample models](https://github.com/KhronosGroup/glTF-Sample-Models/) to be used. The data url format is relatively small, so I didn't include a crate like [docs.rs/data-url](https://docs.rs/data-url/0.1.0/data_url/). Also fixes the 'Box With Spaces' model as URIs are now percent-decoded. cc #1802
This commit is contained in:
parent
04a37f722a
commit
d119c1ce14
2 changed files with 73 additions and 18 deletions
|
@ -30,3 +30,4 @@ gltf = { version = "0.15.2", default-features = false, features = ["utils", "nam
|
|||
thiserror = "1.0"
|
||||
anyhow = "1.0"
|
||||
base64 = "0.13.0"
|
||||
percent-encoding = "2.1"
|
||||
|
|
|
@ -229,16 +229,29 @@ async fn load_gltf<'a, 'b>(
|
|||
Texture::from_buffer(buffer, ImageType::MimeType(mime_type))?
|
||||
}
|
||||
gltf::image::Source::Uri { uri, mime_type } => {
|
||||
let parent = load_context.path().parent().unwrap();
|
||||
let image_path = parent.join(uri);
|
||||
let bytes = load_context.read_asset_bytes(image_path.clone()).await?;
|
||||
let uri = percent_encoding::percent_decode_str(uri)
|
||||
.decode_utf8()
|
||||
.unwrap();
|
||||
let uri = uri.as_ref();
|
||||
let (bytes, image_type) = match DataUri::parse(uri) {
|
||||
Ok(data_uri) => (data_uri.decode()?, ImageType::MimeType(data_uri.mime_type)),
|
||||
Err(()) => {
|
||||
let parent = load_context.path().parent().unwrap();
|
||||
let image_path = parent.join(uri);
|
||||
let bytes = load_context.read_asset_bytes(image_path.clone()).await?;
|
||||
|
||||
let extension = Path::new(uri).extension().unwrap().to_str().unwrap();
|
||||
let image_type = ImageType::Extension(extension);
|
||||
|
||||
(bytes, image_type)
|
||||
}
|
||||
};
|
||||
|
||||
Texture::from_buffer(
|
||||
&bytes,
|
||||
mime_type
|
||||
.map(|mt| ImageType::MimeType(mt))
|
||||
.unwrap_or_else(|| {
|
||||
ImageType::Extension(image_path.extension().unwrap().to_str().unwrap())
|
||||
}),
|
||||
.unwrap_or(image_type),
|
||||
)?
|
||||
}
|
||||
};
|
||||
|
@ -576,23 +589,27 @@ async fn load_buffers(
|
|||
load_context: &LoadContext<'_>,
|
||||
asset_path: &Path,
|
||||
) -> Result<Vec<Vec<u8>>, GltfError> {
|
||||
const OCTET_STREAM_URI: &str = "data:application/octet-stream;base64,";
|
||||
const OCTET_STREAM_URI: &str = "application/octet-stream";
|
||||
|
||||
let mut buffer_data = Vec::new();
|
||||
for buffer in gltf.buffers() {
|
||||
match buffer.source() {
|
||||
gltf::buffer::Source::Uri(uri) => {
|
||||
if uri.starts_with("data:") {
|
||||
buffer_data.push(base64::decode(
|
||||
uri.strip_prefix(OCTET_STREAM_URI)
|
||||
.ok_or(GltfError::BufferFormatUnsupported)?,
|
||||
)?);
|
||||
} else {
|
||||
// TODO: Remove this and add dep
|
||||
let buffer_path = asset_path.parent().unwrap().join(uri);
|
||||
let buffer_bytes = load_context.read_asset_bytes(buffer_path).await?;
|
||||
buffer_data.push(buffer_bytes);
|
||||
}
|
||||
let uri = percent_encoding::percent_decode_str(uri)
|
||||
.decode_utf8()
|
||||
.unwrap();
|
||||
let uri = uri.as_ref();
|
||||
let buffer_bytes = match DataUri::parse(uri) {
|
||||
Ok(data_uri) if data_uri.mime_type == OCTET_STREAM_URI => data_uri.decode()?,
|
||||
Ok(_) => return Err(GltfError::BufferFormatUnsupported),
|
||||
Err(()) => {
|
||||
// TODO: Remove this and add dep
|
||||
let buffer_path = asset_path.parent().unwrap().join(uri);
|
||||
let buffer_bytes = load_context.read_asset_bytes(buffer_path).await?;
|
||||
buffer_bytes
|
||||
}
|
||||
};
|
||||
buffer_data.push(buffer_bytes);
|
||||
}
|
||||
gltf::buffer::Source::Bin => {
|
||||
if let Some(blob) = gltf.blob.as_deref() {
|
||||
|
@ -653,6 +670,43 @@ fn resolve_node_hierarchy(
|
|||
.collect()
|
||||
}
|
||||
|
||||
struct DataUri<'a> {
|
||||
mime_type: &'a str,
|
||||
base64: bool,
|
||||
data: &'a str,
|
||||
}
|
||||
|
||||
fn split_once(input: &str, delimiter: char) -> Option<(&str, &str)> {
|
||||
let mut iter = input.splitn(2, delimiter);
|
||||
Some((iter.next()?, iter.next()?))
|
||||
}
|
||||
|
||||
impl<'a> DataUri<'a> {
|
||||
fn parse(uri: &'a str) -> Result<DataUri<'a>, ()> {
|
||||
let uri = uri.strip_prefix("data:").ok_or(())?;
|
||||
let (mime_type, data) = split_once(uri, ',').ok_or(())?;
|
||||
|
||||
let (mime_type, base64) = match mime_type.strip_suffix(";base64") {
|
||||
Some(mime_type) => (mime_type, true),
|
||||
None => (mime_type, false),
|
||||
};
|
||||
|
||||
Ok(DataUri {
|
||||
mime_type,
|
||||
base64,
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
fn decode(&self) -> Result<Vec<u8>, base64::DecodeError> {
|
||||
if self.base64 {
|
||||
base64::decode(self.data)
|
||||
} else {
|
||||
Ok(self.data.as_bytes().to_owned())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::resolve_node_hierarchy;
|
||||
|
|
Loading…
Reference in a new issue