mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +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"
|
thiserror = "1.0"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
base64 = "0.13.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))?
|
Texture::from_buffer(buffer, ImageType::MimeType(mime_type))?
|
||||||
}
|
}
|
||||||
gltf::image::Source::Uri { uri, mime_type } => {
|
gltf::image::Source::Uri { uri, mime_type } => {
|
||||||
|
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 parent = load_context.path().parent().unwrap();
|
||||||
let image_path = parent.join(uri);
|
let image_path = parent.join(uri);
|
||||||
let bytes = load_context.read_asset_bytes(image_path.clone()).await?;
|
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(
|
Texture::from_buffer(
|
||||||
&bytes,
|
&bytes,
|
||||||
mime_type
|
mime_type
|
||||||
.map(|mt| ImageType::MimeType(mt))
|
.map(|mt| ImageType::MimeType(mt))
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or(image_type),
|
||||||
ImageType::Extension(image_path.extension().unwrap().to_str().unwrap())
|
|
||||||
}),
|
|
||||||
)?
|
)?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -576,23 +589,27 @@ async fn load_buffers(
|
||||||
load_context: &LoadContext<'_>,
|
load_context: &LoadContext<'_>,
|
||||||
asset_path: &Path,
|
asset_path: &Path,
|
||||||
) -> Result<Vec<Vec<u8>>, GltfError> {
|
) -> 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();
|
let mut buffer_data = Vec::new();
|
||||||
for buffer in gltf.buffers() {
|
for buffer in gltf.buffers() {
|
||||||
match buffer.source() {
|
match buffer.source() {
|
||||||
gltf::buffer::Source::Uri(uri) => {
|
gltf::buffer::Source::Uri(uri) => {
|
||||||
if uri.starts_with("data:") {
|
let uri = percent_encoding::percent_decode_str(uri)
|
||||||
buffer_data.push(base64::decode(
|
.decode_utf8()
|
||||||
uri.strip_prefix(OCTET_STREAM_URI)
|
.unwrap();
|
||||||
.ok_or(GltfError::BufferFormatUnsupported)?,
|
let uri = uri.as_ref();
|
||||||
)?);
|
let buffer_bytes = match DataUri::parse(uri) {
|
||||||
} else {
|
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
|
// TODO: Remove this and add dep
|
||||||
let buffer_path = asset_path.parent().unwrap().join(uri);
|
let buffer_path = asset_path.parent().unwrap().join(uri);
|
||||||
let buffer_bytes = load_context.read_asset_bytes(buffer_path).await?;
|
let buffer_bytes = load_context.read_asset_bytes(buffer_path).await?;
|
||||||
buffer_data.push(buffer_bytes);
|
buffer_bytes
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
buffer_data.push(buffer_bytes);
|
||||||
}
|
}
|
||||||
gltf::buffer::Source::Bin => {
|
gltf::buffer::Source::Bin => {
|
||||||
if let Some(blob) = gltf.blob.as_deref() {
|
if let Some(blob) = gltf.blob.as_deref() {
|
||||||
|
@ -653,6 +670,43 @@ fn resolve_node_hierarchy(
|
||||||
.collect()
|
.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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::resolve_node_hierarchy;
|
use super::resolve_node_hierarchy;
|
||||||
|
|
Loading…
Reference in a new issue