diff --git a/extern/cgltf.h b/extern/cgltf.h index ac392aba4..36fd644e1 100644 --- a/extern/cgltf.h +++ b/extern/cgltf.h @@ -395,6 +395,8 @@ typedef struct cgltf_texture cgltf_sampler* sampler; cgltf_bool has_basisu; cgltf_image* basisu_image; + cgltf_bool has_webp; + cgltf_image* webp_image; cgltf_extras extras; cgltf_size extensions_count; cgltf_extension* extensions; @@ -4551,6 +4553,34 @@ static int cgltf_parse_json_texture(cgltf_options* options, jsmntok_t const* tok } } } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "EXT_texture_webp") == 0) + { + out_texture->has_webp = 1; + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int num_properties = tokens[i].size; + ++i; + + for (int t = 0; t < num_properties; ++t) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "source") == 0) + { + ++i; + out_texture->webp_image = CGLTF_PTRINDEX(cgltf_image, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + if (i < 0) + { + return i; + } + } + } else { i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_texture->extensions[out_texture->extensions_count++])); @@ -6561,6 +6591,7 @@ static int cgltf_fixup_pointers(cgltf_data* data) { CGLTF_PTRFIXUP(data->textures[i].image, data->images, data->images_count); CGLTF_PTRFIXUP(data->textures[i].basisu_image, data->images, data->images_count); + CGLTF_PTRFIXUP(data->textures[i].webp_image, data->images, data->images_count); CGLTF_PTRFIXUP(data->textures[i].sampler, data->samplers, data->samplers_count); } diff --git a/gltf/README.md b/gltf/README.md index ffe6d1e12..4b278676a 100644 --- a/gltf/README.md +++ b/gltf/README.md @@ -85,6 +85,7 @@ gltfpack supports most Khronos extensions and some multi-vendor extensions in th - KHR_texture_transform - EXT_mesh_gpu_instancing - EXT_meshopt_compression +- EXT_texture_webp Even if the source file does not use extensions, gltfpack may use some extensions in the output file either by default or when certain options are used: diff --git a/gltf/gltfpack.cpp b/gltf/gltfpack.cpp index f3156d3e3..ee46fa6a7 100644 --- a/gltf/gltfpack.cpp +++ b/gltf/gltfpack.cpp @@ -454,6 +454,7 @@ static void process(cgltf_data* data, const char* input_path, const char* output bool ext_instancing = false; bool ext_texture_transform = false; bool ext_texture_basisu = false; + bool ext_texture_webp = false; size_t accr_offset = 0; size_t node_offset = 0; @@ -512,6 +513,7 @@ static void process(cgltf_data* data, const char* input_path, const char* output assert(textures[i].remap == int(texture_offset)); texture_offset++; ext_texture_basisu = ext_texture_basisu || texture.has_basisu; + ext_texture_webp = ext_texture_webp || texture.has_webp; } for (size_t i = 0; i < data->materials_count; ++i) @@ -838,6 +840,7 @@ static void process(cgltf_data* data, const char* input_path, const char* output {"KHR_materials_variants", data->variants_count > 0, false}, {"KHR_lights_punctual", data->lights_count > 0, false}, {"KHR_texture_basisu", (!json_textures.empty() && settings.texture_ktx2) || ext_texture_basisu, true}, + {"EXT_texture_webp", ext_texture_webp, true}, {"EXT_mesh_gpu_instancing", ext_instancing, true}, }; diff --git a/gltf/image.cpp b/gltf/image.cpp index 129938a0e..972b4c441 100644 --- a/gltf/image.cpp +++ b/gltf/image.cpp @@ -8,6 +8,7 @@ static const char* kMimeTypes[][2] = { {"image/jpeg", ".jpeg"}, {"image/png", ".png"}, {"image/ktx2", ".ktx2"}, + {"image/webp", ".webp"}, }; static const char* inferMimeType(const char* path) @@ -128,6 +129,7 @@ static int readInt32LE(const std::string& data, size_t offset) (unsigned((unsigned char)data[offset + 3]) << 24); } +// https://en.wikipedia.org/wiki/PNG#File_format static bool getDimensionsPng(const std::string& data, int& width, int& height) { if (data.size() < 8 + 8 + 13 + 4) @@ -146,6 +148,7 @@ static bool getDimensionsPng(const std::string& data, int& width, int& height) return true; } +// https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format#File_format_structure static bool getDimensionsJpeg(const std::string& data, int& width, int& height) { size_t offset = 0; @@ -189,6 +192,7 @@ static bool getDimensionsJpeg(const std::string& data, int& width, int& height) return false; } +// https://en.wikipedia.org/wiki/PNG#File_format static bool hasTransparencyPng(const std::string& data) { if (data.size() < 8 + 8 + 13 + 4) @@ -224,6 +228,7 @@ static bool hasTransparencyPng(const std::string& data) return false; } +// https://github.khronos.org/KTX-Specification/ktxspec.v2.html static bool hasTransparencyKtx2(const std::string& data) { if (data.size() < 12 + 17 * 4) @@ -261,12 +266,45 @@ static bool hasTransparencyKtx2(const std::string& data) return false; } +// https://developers.google.com/speed/webp/docs/riff_container +static bool hasTransparencyWebP(const std::string& data) +{ + if (data.size() < 12 + 4 + 12) + return false; + + if (data.compare(0, 4, "RIFF") != 0) + return false; + if (data.compare(8, 4, "WEBP") != 0) + return false; + + // WebP data may use VP8L, VP8X or VP8 format, but VP8 does not support transparency + if (data.compare(12, 4, "VP8L") == 0) + { + if (unsigned(data[20]) != 0x2f) + return false; + + // width (14) | height (14) | alpha_is_used (1) | version_number(3) + unsigned int header = readInt32LE(data, 21); + return (header & (1 << 28)) != 0; + } + else if (data.compare(12, 4, "VP8X") == 0) + { + // zero (2) | icc (1) | alpha (1) | exif (1) | xmp (1) | animation (1) | zero (1) + unsigned char header = data[20]; + return (header & (1 << 4)) != 0; + } + + return false; +} + bool hasAlpha(const std::string& data, const char* mime_type) { if (strcmp(mime_type, "image/png") == 0) return hasTransparencyPng(data); else if (strcmp(mime_type, "image/ktx2") == 0) return hasTransparencyKtx2(data); + else if (strcmp(mime_type, "image/webp") == 0) + return hasTransparencyWebP(data); else return false; } diff --git a/gltf/material.cpp b/gltf/material.cpp index 0931b2e05..68d5610c8 100644 --- a/gltf/material.cpp +++ b/gltf/material.cpp @@ -11,10 +11,10 @@ static bool areTexturesEqual(const cgltf_texture& lhs, const cgltf_texture& rhs) if (lhs.sampler != rhs.sampler) return false; - if (lhs.has_basisu != rhs.has_basisu) + if (lhs.basisu_image != rhs.basisu_image) return false; - if (lhs.basisu_image != rhs.basisu_image) + if (lhs.webp_image != rhs.webp_image) return false; return true; @@ -446,9 +446,12 @@ static const cgltf_image* getTextureImage(const cgltf_texture* texture) if (texture && texture->image) return texture->image; - if (texture && texture->has_basisu && texture->basisu_image) + if (texture && texture->basisu_image) return texture->basisu_image; + if (texture && texture->webp_image) + return texture->webp_image; + return NULL; } diff --git a/gltf/write.cpp b/gltf/write.cpp index 13f2f3ad5..429d0c446 100644 --- a/gltf/write.cpp +++ b/gltf/write.cpp @@ -975,13 +975,20 @@ void writeTexture(std::string& json, const cgltf_texture& texture, const ImageIn } } - if (texture.has_basisu && texture.basisu_image) + if (texture.basisu_image) { comma(json); append(json, "\"extensions\":{\"KHR_texture_basisu\":{\"source\":"); append(json, size_t(texture.basisu_image - data->images)); append(json, "}}"); } + else if (texture.webp_image) + { + comma(json); + append(json, "\"extensions\":{\"EXT_texture_webp\":{\"source\":"); + append(json, size_t(texture.webp_image - data->images)); + append(json, "}}"); + } } void writeMeshAttributes(std::string& json, std::vector& views, std::string& json_accessors, size_t& accr_offset, const Mesh& mesh, int target, const QuantizationPosition& qp, const QuantizationTexture& qt, const Settings& settings)