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 f4f8d7fe9f2..570f7d6a17c 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 631a6d9c57a..c03b8982408 100644 --- a/core/image.h +++ b/core/image.h @@ -147,10 +147,11 @@ public: 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 5f9cd91a5bd..5467224b668 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 fbef3e599df..cde133b7b33 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 3fe28b71fc6..14b78fa187a 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 cb0de68bbf9..b3a3dea3845 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 e9981c0d78f..94618b75549 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 2bb84d9d4f4..d748b8149c7 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 45adfdd95bd..254a84fc836 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 3c82ca9ea72..e0334e976d0 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 f1aabb44a00..00146a7e3d5 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"));