From 5de08ef1d66829242cb27485f927edb6b640a321 Mon Sep 17 00:00:00 2001 From: Morris Tabor <80684659+mortarroad@users.noreply.github.com> Date: Sat, 8 May 2021 19:14:41 +0200 Subject: [PATCH] Implement lossless WebP encoding --- core/image.cpp | 9 ++- core/image.h | 9 ++- core/io/resource_format_binary.cpp | 8 +- doc/classes/ProjectSettings.xml | 6 ++ drivers/png/image_loader_png.cpp | 4 +- .../resource_importer_layered_texture.cpp | 4 +- editor/import/resource_importer_texture.cpp | 19 ++++- modules/webp/image_loader_webp.cpp | 74 ++++++++++++++++++- scene/resources/texture.cpp | 10 +-- scene/resources/texture.h | 10 +-- servers/visual_server.cpp | 4 + 11 files changed, 122 insertions(+), 35 deletions(-) diff --git a/core/image.cpp b/core/image.cpp index f4f8d7fe9f24..570f7d6a17cb 100644 --- a/core/image.cpp +++ b/core/image.cpp @@ -2487,10 +2487,11 @@ void (*Image::_image_decompress_bptc)(Image *) = nullptr; void (*Image::_image_decompress_etc1)(Image *) = nullptr; void (*Image::_image_decompress_etc2)(Image *) = nullptr; -PoolVector (*Image::lossy_packer)(const Ref &, float) = nullptr; -Ref (*Image::lossy_unpacker)(const PoolVector &) = nullptr; -PoolVector (*Image::lossless_packer)(const Ref &) = nullptr; -Ref (*Image::lossless_unpacker)(const PoolVector &) = nullptr; +PoolVector (*Image::webp_lossy_packer)(const Ref &, float) = nullptr; +PoolVector (*Image::webp_lossless_packer)(const Ref &) = nullptr; +Ref (*Image::webp_unpacker)(const PoolVector &) = nullptr; +PoolVector (*Image::png_packer)(const Ref &) = nullptr; +Ref (*Image::png_unpacker)(const PoolVector &) = nullptr; void Image::_set_data(const Dictionary &p_data) { ERR_FAIL_COND(!p_data.has("width")); diff --git a/core/image.h b/core/image.h index 631a6d9c57a9..c03b8982408c 100644 --- a/core/image.h +++ b/core/image.h @@ -147,10 +147,11 @@ class Image : public Resource { static void (*_image_decompress_etc1)(Image *); static void (*_image_decompress_etc2)(Image *); - static PoolVector (*lossy_packer)(const Ref &p_image, float p_quality); - static Ref (*lossy_unpacker)(const PoolVector &p_buffer); - static PoolVector (*lossless_packer)(const Ref &p_image); - static Ref (*lossless_unpacker)(const PoolVector &p_buffer); + static PoolVector (*webp_lossy_packer)(const Ref &p_image, float p_quality); + static PoolVector (*webp_lossless_packer)(const Ref &p_image); + static Ref (*webp_unpacker)(const PoolVector &p_buffer); + static PoolVector (*png_packer)(const Ref &p_image); + static Ref (*png_unpacker)(const PoolVector &p_buffer); PoolVector::Write write_lock; diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index 5f9cd91a5bd7..5467224b668c 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -562,10 +562,10 @@ Error ResourceInteractiveLoaderBinary::parse_variant(Variant &r_v) { Ref image; - if (encoding == IMAGE_ENCODING_LOSSY && Image::lossy_unpacker) { - image = Image::lossy_unpacker(data); - } else if (encoding == IMAGE_ENCODING_LOSSLESS && Image::lossless_unpacker) { - image = Image::lossless_unpacker(data); + if (encoding == IMAGE_ENCODING_LOSSY && Image::webp_unpacker) { + image = Image::webp_unpacker(data); // IMAGE_ENCODING_LOSSY always meant WebP + } else if (encoding == IMAGE_ENCODING_LOSSLESS && Image::png_unpacker) { + image = Image::png_unpacker(data); // IMAGE_ENCODING_LOSSLESS always meant png } _advance_padding(data.size()); diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index fbef3e599df6..cde133b7b339 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1169,6 +1169,12 @@ Shaders have a time variable that constantly increases. At some point, it needs to be rolled back to zero to avoid precision errors on shader animations. This setting specifies when (in seconds). + + If [code]true[/code], the texture importer will import lossless textures using the PNG format. Otherwise, it will default to using WebP. + + + The default compression level for lossless WebP. Higher levels result in smaller files at the cost of compression speed. Decompression speed is mostly unaffected by the compression level. Supported values are 0 to 9. Note that compression levels above 6 are very slow and offer very little savings. + If [code]true[/code], allocates the main framebuffer with high dynamic range. High dynamic range allows the use of [Color] values greater than 1. [b]Note:[/b] Only available on the GLES3 backend. diff --git a/drivers/png/image_loader_png.cpp b/drivers/png/image_loader_png.cpp index 3fe28b71fc68..14b78fa187a7 100644 --- a/drivers/png/image_loader_png.cpp +++ b/drivers/png/image_loader_png.cpp @@ -101,6 +101,6 @@ PoolVector ImageLoaderPNG::lossless_pack_png(const Ref &p_image) ImageLoaderPNG::ImageLoaderPNG() { Image::_png_mem_loader_func = load_mem_png; - Image::lossless_unpacker = lossless_unpack_png; - Image::lossless_packer = lossless_pack_png; + Image::png_unpacker = lossless_unpack_png; + Image::png_packer = lossless_pack_png; } diff --git a/editor/import/resource_importer_layered_texture.cpp b/editor/import/resource_importer_layered_texture.cpp index cb0de68bbf9c..b3a3dea3845b 100644 --- a/editor/import/resource_importer_layered_texture.cpp +++ b/editor/import/resource_importer_layered_texture.cpp @@ -128,7 +128,9 @@ void ResourceImporterLayeredTexture::_save_tex(const Vector> &p_image image->shrink_x2(); } - PoolVector data = Image::lossless_packer(image); + // we have to use png here for backward compatibility. + // in 3.x, we don't store type information here + PoolVector data = Image::png_packer(image); int data_len = data.size(); f->store_32(data_len); diff --git a/editor/import/resource_importer_texture.cpp b/editor/import/resource_importer_texture.cpp index e9981c0d78f7..94618b75549d 100644 --- a/editor/import/resource_importer_texture.cpp +++ b/editor/import/resource_importer_texture.cpp @@ -254,6 +254,8 @@ void ResourceImporterTexture::_save_stex(const Ref &p_image, const String switch (p_compress_mode) { case COMPRESS_LOSSLESS: { + bool lossless_force_png = ProjectSettings::get_singleton()->get("rendering/lossless_compression/force_png"); + bool use_webp = !lossless_force_png && p_image->get_width() <= 16383 && p_image->get_height() <= 16383; // WebP has a size limit Ref image = p_image->duplicate(); if (p_mipmaps) { image->generate_mipmaps(); @@ -263,7 +265,11 @@ void ResourceImporterTexture::_save_stex(const Ref &p_image, const String int mmc = image->get_mipmap_count() + 1; - format |= StreamTexture::FORMAT_BIT_LOSSLESS; + if (use_webp) { + format |= StreamTexture::FORMAT_BIT_WEBP; + } else { + format |= StreamTexture::FORMAT_BIT_PNG; + } f->store_32(format); f->store_32(mmc); @@ -272,7 +278,12 @@ void ResourceImporterTexture::_save_stex(const Ref &p_image, const String image->shrink_x2(); } - PoolVector data = Image::lossless_packer(image); + PoolVector data; + if (use_webp) { + data = Image::webp_lossless_packer(image); + } else { + data = Image::png_packer(image); + } int data_len = data.size(); f->store_32(data_len); @@ -291,7 +302,7 @@ void ResourceImporterTexture::_save_stex(const Ref &p_image, const String int mmc = image->get_mipmap_count() + 1; - format |= StreamTexture::FORMAT_BIT_LOSSY; + format |= StreamTexture::FORMAT_BIT_WEBP; f->store_32(format); f->store_32(mmc); @@ -300,7 +311,7 @@ void ResourceImporterTexture::_save_stex(const Ref &p_image, const String image->shrink_x2(); } - PoolVector data = Image::lossy_packer(image, p_lossy_quality); + PoolVector data = Image::webp_lossy_packer(image, p_lossy_quality); int data_len = data.size(); f->store_32(data_len); diff --git a/modules/webp/image_loader_webp.cpp b/modules/webp/image_loader_webp.cpp index 2bb84d9d4f4a..d748b8149c71 100644 --- a/modules/webp/image_loader_webp.cpp +++ b/modules/webp/image_loader_webp.cpp @@ -33,6 +33,7 @@ #include "core/io/marshalls.h" #include "core/os/os.h" #include "core/print_string.h" +#include "core/project_settings.h" #include #include @@ -69,11 +70,77 @@ static PoolVector _webp_lossy_pack(const Ref &p_image, float p_q w[2] = 'B'; w[3] = 'P'; memcpy(&w[4], dst_buff, dst_size); - free(dst_buff); + WebPFree(dst_buff); w.release(); return dst; } +static PoolVector _webp_lossless_pack(const Ref &p_image) { + ERR_FAIL_COND_V(p_image.is_null() || p_image->empty(), PoolVector()); + + int compression_level = ProjectSettings::get_singleton()->get("rendering/lossless_compression/webp_compression_level"); + compression_level = CLAMP(compression_level, 0, 9); + + Ref img = p_image->duplicate(); + if (img->detect_alpha()) { + img->convert(Image::FORMAT_RGBA8); + } else { + img->convert(Image::FORMAT_RGB8); + } + + Size2 s(img->get_width(), img->get_height()); + PoolVector data = img->get_data(); + PoolVector::Read r = data.read(); + + // we need to use the more complex API in order to access the 'exact' flag... + + WebPConfig config; + WebPPicture pic; + if (!WebPConfigInit(&config) || !WebPConfigLosslessPreset(&config, compression_level) || !WebPPictureInit(&pic)) { + ERR_FAIL_V(PoolVector()); + } + + WebPMemoryWriter wrt; + config.exact = 1; + pic.use_argb = 1; + pic.width = s.width; + pic.height = s.height; + pic.writer = WebPMemoryWrite; + pic.custom_ptr = &wrt; + WebPMemoryWriterInit(&wrt); + + bool success_import = false; + if (img->get_format() == Image::FORMAT_RGB8) { + success_import = WebPPictureImportRGB(&pic, r.ptr(), 3 * s.width); + } else { + success_import = WebPPictureImportRGBA(&pic, r.ptr(), 4 * s.width); + } + bool success_encode = false; + if (success_import) { + success_encode = WebPEncode(&config, &pic); + } + WebPPictureFree(&pic); + + if (!success_encode) { + WebPMemoryWriterClear(&wrt); + ERR_FAIL_V_MSG(PoolVector(), "WebP packing failed."); + } + + // copy from wrt + PoolVector dst; + dst.resize(4 + wrt.size); + PoolVector::Write w = dst.write(); + w[0] = 'W'; + w[1] = 'E'; + w[2] = 'B'; + w[3] = 'P'; + memcpy(&w[4], wrt.mem, wrt.size); + w.release(); + WebPMemoryWriterClear(&wrt); + + return dst; +} + static Ref _webp_lossy_unpack(const PoolVector &p_buffer) { int size = p_buffer.size() - 4; ERR_FAIL_COND_V(size <= 0, Ref()); @@ -171,6 +238,7 @@ void ImageLoaderWEBP::get_recognized_extensions(List *p_extensions) cons ImageLoaderWEBP::ImageLoaderWEBP() { Image::_webp_mem_loader_func = _webp_mem_loader_func; - Image::lossy_packer = _webp_lossy_pack; - Image::lossy_unpacker = _webp_lossy_unpack; + Image::webp_lossy_packer = _webp_lossy_pack; + Image::webp_lossless_packer = _webp_lossless_pack; + Image::webp_unpacker = _webp_lossy_unpack; } diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index 45adfdd95bde..254a84fc8365 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -516,7 +516,7 @@ Error StreamTexture::_load_data(const String &p_path, int &tw, int &th, int &tw_ p_size_limit = 0; } - if (df & FORMAT_BIT_LOSSLESS || df & FORMAT_BIT_LOSSY) { + if (df & FORMAT_BIT_PNG || df & FORMAT_BIT_WEBP) { //look for a PNG or WEBP file inside int sw = tw; @@ -554,10 +554,10 @@ Error StreamTexture::_load_data(const String &p_path, int &tw, int &th, int &tw_ } Ref img; - if (df & FORMAT_BIT_LOSSLESS) { - img = Image::lossless_unpacker(pv); + if (df & FORMAT_BIT_PNG) { + img = Image::png_unpacker(pv); } else { - img = Image::lossy_unpacker(pv); + img = Image::webp_unpacker(pv); } if (img.is_null() || img->empty()) { @@ -2182,7 +2182,7 @@ Error TextureLayered::load(const String &p_path) { f->get_buffer(w.ptr(), size); } - Ref img = Image::lossless_unpacker(pv); + Ref img = Image::png_unpacker(pv); if (img.is_null() || img->empty() || format != img->get_format()) { f->close(); diff --git a/scene/resources/texture.h b/scene/resources/texture.h index 3c82ca9ea722..e0334e976d0d 100644 --- a/scene/resources/texture.h +++ b/scene/resources/texture.h @@ -164,16 +164,10 @@ class StreamTexture : public Texture { GDCLASS(StreamTexture, Texture); public: - enum DataFormat { - DATA_FORMAT_IMAGE, - DATA_FORMAT_LOSSLESS, - DATA_FORMAT_LOSSY - }; - enum FormatBits { FORMAT_MASK_IMAGE_FORMAT = (1 << 20) - 1, - FORMAT_BIT_LOSSLESS = 1 << 20, - FORMAT_BIT_LOSSY = 1 << 21, + FORMAT_BIT_PNG = 1 << 20, + FORMAT_BIT_WEBP = 1 << 21, FORMAT_BIT_STREAM = 1 << 22, FORMAT_BIT_HAS_MIPMAPS = 1 << 23, FORMAT_BIT_DETECT_3D = 1 << 24, diff --git a/servers/visual_server.cpp b/servers/visual_server.cpp index f1aabb44a000..00146a7e3d5a 100644 --- a/servers/visual_server.cpp +++ b/servers/visual_server.cpp @@ -2250,6 +2250,10 @@ VisualServer::VisualServer() { GLOBAL_DEF_RST("rendering/vram_compression/import_etc2", true); GLOBAL_DEF_RST("rendering/vram_compression/import_pvrtc", false); + GLOBAL_DEF("rendering/lossless_compression/force_png", false); + GLOBAL_DEF("rendering/lossless_compression/webp_compression_level", 2); + ProjectSettings::get_singleton()->set_custom_property_info("rendering/lossless_compression/webp_compression_level", PropertyInfo(Variant::INT, "rendering/lossless_compression/webp_compression_level", PROPERTY_HINT_RANGE, "0,9,1")); + GLOBAL_DEF("rendering/limits/time/time_rollover_secs", 3600); ProjectSettings::get_singleton()->set_custom_property_info("rendering/limits/time/time_rollover_secs", PropertyInfo(Variant::REAL, "rendering/limits/time/time_rollover_secs", PROPERTY_HINT_RANGE, "0,10000,1,or_greater"));