Skip to content

Commit

Permalink
Implement lossless WebP encoding
Browse files Browse the repository at this point in the history
  • Loading branch information
mortarroad committed Jun 11, 2021
1 parent 0bdadd4 commit 5de08ef
Show file tree
Hide file tree
Showing 11 changed files with 122 additions and 35 deletions.
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

0 comments on commit 5de08ef

Please sign in to comment.