diff --git a/Cargo.toml b/Cargo.toml index a94a7add..cee56ff5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,6 +54,7 @@ KHR_materials_variants = ["gltf-json/KHR_materials_variants"] KHR_materials_volume = ["gltf-json/KHR_materials_volume"] KHR_materials_specular = ["gltf-json/KHR_materials_specular"] KHR_materials_emissive_strength = ["gltf-json/KHR_materials_emissive_strength"] +EXT_texture_webp = ["gltf-json/EXT_texture_webp", "image/webp"] guess_mime_type = [] [[example]] diff --git a/gltf-json/Cargo.toml b/gltf-json/Cargo.toml index 015510df..d981e661 100644 --- a/gltf-json/Cargo.toml +++ b/gltf-json/Cargo.toml @@ -30,3 +30,4 @@ KHR_materials_variants = [] KHR_materials_volume = [] KHR_texture_transform = [] KHR_materials_emissive_strength = [] +EXT_texture_webp = [] diff --git a/gltf-json/src/extensions/mod.rs b/gltf-json/src/extensions/mod.rs index 611a8652..3112da95 100644 --- a/gltf-json/src/extensions/mod.rs +++ b/gltf-json/src/extensions/mod.rs @@ -55,7 +55,7 @@ pub const ENABLED_EXTENSIONS: &[&str] = &[ // Allowlisted texture extensions. Processing is delegated to the user. #[cfg(feature = "allow_empty_texture")] "KHR_texture_basisu", - #[cfg(feature = "allow_empty_texture")] + #[cfg(feature = "EXT_texture_webp")] "EXT_texture_webp", #[cfg(feature = "allow_empty_texture")] "MSFT_texture_dds", @@ -70,4 +70,5 @@ pub const SUPPORTED_EXTENSIONS: &[&str] = &[ "KHR_materials_transmission", "KHR_materials_ior", "KHR_materials_emissive_strength", + "EXT_texture_webp", ]; diff --git a/gltf-json/src/extensions/texture.rs b/gltf-json/src/extensions/texture.rs index 276720c0..010cc761 100644 --- a/gltf-json/src/extensions/texture.rs +++ b/gltf-json/src/extensions/texture.rs @@ -1,5 +1,8 @@ -#[cfg(feature = "KHR_texture_transform")] +#[cfg(any(feature = "KHR_texture_transform", feature = "EXT_texture_webp"))] use crate::{extras::Extras, validation::Validate}; +#[cfg(feature = "EXT_texture_webp")] +use crate::{image, Index}; + use gltf_derive::Validate; use serde_derive::{Deserialize, Serialize}; #[cfg(feature = "extensions")] @@ -19,6 +22,21 @@ pub struct Texture { #[cfg(feature = "extensions")] #[serde(default, flatten)] pub others: Map, + + #[cfg(feature = "EXT_texture_webp")] + #[serde( + default, + rename = "EXT_texture_webp", + skip_serializing_if = "Option::is_none" + )] + pub texture_webp: Option, +} + +#[cfg(feature = "EXT_texture_webp")] +#[derive(Clone, Debug, Deserialize, Serialize, Validate)] +pub struct TextureWebp { + /// The index of the webp image used by the texture. + pub source: Index, } #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] diff --git a/gltf-json/src/image.rs b/gltf-json/src/image.rs index 2ee1b62a..a13fb90f 100644 --- a/gltf-json/src/image.rs +++ b/gltf-json/src/image.rs @@ -4,7 +4,12 @@ use gltf_derive::Validate; use serde_derive::{Deserialize, Serialize}; /// All valid MIME types. -pub const VALID_MIME_TYPES: &[&str] = &["image/jpeg", "image/png"]; +pub const VALID_MIME_TYPES: &[&str] = &[ + "image/jpeg", + "image/png", + #[cfg(feature = "EXT_texture_webp")] + "image/webp", +]; /// Image data used to create a texture. #[derive(Clone, Debug, Deserialize, Serialize, Validate)] diff --git a/gltf-json/src/texture.rs b/gltf-json/src/texture.rs index c232f908..01aea1c3 100644 --- a/gltf-json/src/texture.rs +++ b/gltf-json/src/texture.rs @@ -1,3 +1,4 @@ +use crate::extensions::texture; use crate::validation::{Checked, Validate}; use crate::{extensions, image, Extras, Index}; use gltf_derive::Validate; @@ -179,7 +180,7 @@ where P: Fn() -> crate::Path, R: FnMut(&dyn Fn() -> crate::Path, crate::validation::Error), { - if cfg!(feature = "allow_empty_texture") { + if cfg!(any(feature = "allow_empty_texture",)) { if !source_is_empty(source) { source.validate(root, path, report); } @@ -217,6 +218,27 @@ pub struct Texture { pub extras: Extras, } +impl Texture { + /// The index of the image used by this texture. + pub fn primary_source(&self) -> Index { + #[allow(unused_mut)] + let mut source = self.source; + #[cfg(feature = "EXT_texture_webp")] + { + if let Some(texture_webp) = &self.extensions { + if let Some(texture_webp) = &texture_webp.texture_webp { + // Only use the webp source if the source is not empty + // Otherwise, fallback to whatever was there originally + if !source_is_empty(&texture_webp.source) { + source = texture_webp.source; + } + } + } + } + source + } +} + impl Validate for Texture { fn validate(&self, root: &crate::Root, path: P, report: &mut R) where @@ -227,7 +249,13 @@ impl Validate for Texture { .validate(root, || path().field("sampler"), report); self.extensions .validate(root, || path().field("extensions"), report); - source_validate(&self.source, root, || path().field("source"), report); + + source_validate( + &self.primary_source(), + root, + || path().field("source"), + report, + ); } } diff --git a/src/import.rs b/src/import.rs index da2c541d..fd23dbde 100644 --- a/src/import.rs +++ b/src/import.rs @@ -4,6 +4,8 @@ use std::borrow::Cow; use std::{fs, io}; use crate::{Document, Error, Gltf, Result}; +#[cfg(feature = "EXT_texture_webp")] +use image_crate::ImageFormat::WebP; use image_crate::ImageFormat::{Jpeg, Png}; use std::path::Path; @@ -148,6 +150,8 @@ impl image::Data { 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), + #[cfg(feature = "EXT_texture_webp")] + Ok(image_crate::ImageFormat::WebP) => Some(WebP), _ => None, }; #[cfg(not(feature = "guess_mime_type"))] @@ -159,6 +163,8 @@ impl image::Data { let encoded_format = match annoying_case { "image/png" => Png, "image/jpeg" => Jpeg, + #[cfg(feature = "EXT_texture_webp")] + "image/webp" => WebP, _ => match guess_format(&encoded_image) { Some(format) => format, None => return Err(Error::UnsupportedImageEncoding), @@ -173,6 +179,8 @@ impl image::Data { let encoded_format = match mime_type { Some("image/png") => Png, Some("image/jpeg") => Jpeg, + #[cfg(feature = "EXT_texture_webp")] + Some("image/webp") => WebP, Some(_) => match guess_format(&encoded_image) { Some(format) => format, None => return Err(Error::UnsupportedImageEncoding), @@ -180,6 +188,8 @@ impl image::Data { None => match uri.rsplit('.').next() { Some("png") => Png, Some("jpg") | Some("jpeg") => Jpeg, + #[cfg(feature = "EXT_texture_webp")] + Some("webp") => WebP, _ => match guess_format(&encoded_image) { Some(format) => format, None => return Err(Error::UnsupportedImageEncoding), @@ -197,6 +207,8 @@ impl image::Data { let encoded_format = match mime_type { "image/png" => Png, "image/jpeg" => Jpeg, + #[cfg(feature = "EXT_texture_webp")] + "image/webp" => WebP, _ => match guess_format(encoded_image) { Some(format) => format, None => return Err(Error::UnsupportedImageEncoding), diff --git a/src/texture.rs b/src/texture.rs index bd99b024..4af15165 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -160,7 +160,7 @@ impl<'a> Texture<'a> { /// Returns the image used by this texture. #[cfg(feature = "allow_empty_texture")] pub fn source(&self) -> Option> { - let index = self.json.source.value(); + let index = self.json.primary_source().value(); if index == u32::MAX as usize { None } else { @@ -173,7 +173,7 @@ impl<'a> Texture<'a> { pub fn source(&self) -> image::Image<'a> { self.document .images() - .nth(self.json.source.value()) + .nth(self.json.primary_source().value()) .unwrap() }