Implement lossless WebP encoding
This commit is contained in:
parent
0bdadd468f
commit
5de08ef1d6
|
@ -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"));
|
||||
|
|
|
@ -147,10 +147,11 @@ public:
|
|||
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;
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
@ -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>());
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<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()) {
|
||||
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"));
|
||||
|
||||
|
|
Loading…
Reference in New Issue