Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.x] Implement lossless WebP encoding #47854

Merged
merged 1 commit into from
Jun 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions core/image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint8_t> (*Image::lossy_packer)(const Ref<Image> &, float) = nullptr;
Ref<Image> (*Image::lossy_unpacker)(const PoolVector<uint8_t> &) = nullptr;
PoolVector<uint8_t> (*Image::lossless_packer)(const Ref<Image> &) = nullptr;
Ref<Image> (*Image::lossless_unpacker)(const PoolVector<uint8_t> &) = nullptr;
PoolVector<uint8_t> (*Image::webp_lossy_packer)(const Ref<Image> &, float) = nullptr;
PoolVector<uint8_t> (*Image::webp_lossless_packer)(const Ref<Image> &) = nullptr;
Ref<Image> (*Image::webp_unpacker)(const PoolVector<uint8_t> &) = nullptr;
PoolVector<uint8_t> (*Image::png_packer)(const Ref<Image> &) = nullptr;
Ref<Image> (*Image::png_unpacker)(const PoolVector<uint8_t> &) = nullptr;

void Image::_set_data(const Dictionary &p_data) {
ERR_FAIL_COND(!p_data.has("width"));
Expand Down
9 changes: 5 additions & 4 deletions core/image.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,11 @@ class Image : public Resource {
static void (*_image_decompress_etc1)(Image *);
static void (*_image_decompress_etc2)(Image *);

static PoolVector<uint8_t> (*lossy_packer)(const Ref<Image> &p_image, float p_quality);
static Ref<Image> (*lossy_unpacker)(const PoolVector<uint8_t> &p_buffer);
static PoolVector<uint8_t> (*lossless_packer)(const Ref<Image> &p_image);
static Ref<Image> (*lossless_unpacker)(const PoolVector<uint8_t> &p_buffer);
static PoolVector<uint8_t> (*webp_lossy_packer)(const Ref<Image> &p_image, float p_quality);
static PoolVector<uint8_t> (*webp_lossless_packer)(const Ref<Image> &p_image);
static Ref<Image> (*webp_unpacker)(const PoolVector<uint8_t> &p_buffer);
static PoolVector<uint8_t> (*png_packer)(const Ref<Image> &p_image);
static Ref<Image> (*png_unpacker)(const PoolVector<uint8_t> &p_buffer);

PoolVector<uint8_t>::Write write_lock;

Expand Down
8 changes: 4 additions & 4 deletions core/io/resource_format_binary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -562,10 +562,10 @@ Error ResourceInteractiveLoaderBinary::parse_variant(Variant &r_v) {

Ref<Image> 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());

Expand Down
6 changes: 6 additions & 0 deletions doc/classes/ProjectSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1169,6 +1169,12 @@
<member name="rendering/limits/time/time_rollover_secs" type="float" setter="" getter="" default="3600">
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).
</member>
<member name="rendering/lossless_compression/force_png" type="bool" setter="" getter="" default="false">
If [code]true[/code], the texture importer will import lossless textures using the PNG format. Otherwise, it will default to using WebP.
</member>
<member name="rendering/lossless_compression/webp_compression_level" type="int" setter="" getter="" default="2">
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.
</member>
<member name="rendering/quality/depth/hdr" type="bool" setter="" getter="" default="true">
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.
Expand Down
4 changes: 2 additions & 2 deletions drivers/png/image_loader_png.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,6 @@ PoolVector<uint8_t> ImageLoaderPNG::lossless_pack_png(const Ref<Image> &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;
}
4 changes: 3 additions & 1 deletion editor/import/resource_importer_layered_texture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,9 @@ void ResourceImporterLayeredTexture::_save_tex(const Vector<Ref<Image>> &p_image
image->shrink_x2();
}

PoolVector<uint8_t> 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<uint8_t> data = Image::png_packer(image);
int data_len = data.size();
f->store_32(data_len);

Expand Down
19 changes: 15 additions & 4 deletions editor/import/resource_importer_texture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,8 @@ void ResourceImporterTexture::_save_stex(const Ref<Image> &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> image = p_image->duplicate();
if (p_mipmaps) {
image->generate_mipmaps();
Expand All @@ -263,7 +265,11 @@ void ResourceImporterTexture::_save_stex(const Ref<Image> &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);

Expand All @@ -272,7 +278,12 @@ void ResourceImporterTexture::_save_stex(const Ref<Image> &p_image, const String
image->shrink_x2();
}

PoolVector<uint8_t> data = Image::lossless_packer(image);
PoolVector<uint8_t> 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);

Expand All @@ -291,7 +302,7 @@ void ResourceImporterTexture::_save_stex(const Ref<Image> &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);

Expand All @@ -300,7 +311,7 @@ void ResourceImporterTexture::_save_stex(const Ref<Image> &p_image, const String
image->shrink_x2();
}

PoolVector<uint8_t> data = Image::lossy_packer(image, p_lossy_quality);
PoolVector<uint8_t> data = Image::webp_lossy_packer(image, p_lossy_quality);
int data_len = data.size();
f->store_32(data_len);

Expand Down
74 changes: 71 additions & 3 deletions modules/webp/image_loader_webp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <stdlib.h>
#include <webp/decode.h>
Expand Down Expand Up @@ -69,11 +70,77 @@ static PoolVector<uint8_t> _webp_lossy_pack(const Ref<Image> &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<uint8_t> _webp_lossless_pack(const Ref<Image> &p_image) {
ERR_FAIL_COND_V(p_image.is_null() || p_image->empty(), PoolVector<uint8_t>());

int compression_level = ProjectSettings::get_singleton()->get("rendering/lossless_compression/webp_compression_level");
compression_level = CLAMP(compression_level, 0, 9);

Ref<Image> 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<uint8_t> data = img->get_data();
PoolVector<uint8_t>::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<uint8_t>());
}

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<uint8_t>(), "WebP packing failed.");
}

// copy from wrt
PoolVector<uint8_t> dst;
dst.resize(4 + wrt.size);
PoolVector<uint8_t>::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<Image> _webp_lossy_unpack(const PoolVector<uint8_t> &p_buffer) {
int size = p_buffer.size() - 4;
ERR_FAIL_COND_V(size <= 0, Ref<Image>());
Expand Down Expand Up @@ -171,6 +238,7 @@ void ImageLoaderWEBP::get_recognized_extensions(List<String> *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;
}
10 changes: 5 additions & 5 deletions scene/resources/texture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -554,10 +554,10 @@ Error StreamTexture::_load_data(const String &p_path, int &tw, int &th, int &tw_
}

Ref<Image> 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()) {
Expand Down Expand Up @@ -2182,7 +2182,7 @@ Error TextureLayered::load(const String &p_path) {
f->get_buffer(w.ptr(), size);
}

Ref<Image> img = Image::lossless_unpacker(pv);
Ref<Image> img = Image::png_unpacker(pv);

if (img.is_null() || img->empty() || format != img->get_format()) {
f->close();
Expand Down
10 changes: 2 additions & 8 deletions scene/resources/texture.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions servers/visual_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"));

Expand Down