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 Apr 12, 2021
1 parent cee5414 commit d1756eb
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 52 deletions.
9 changes: 5 additions & 4 deletions core/io/image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2719,10 +2719,11 @@ void (*Image::_image_decompress_bptc)(Image *) = nullptr;
void (*Image::_image_decompress_etc1)(Image *) = nullptr;
void (*Image::_image_decompress_etc2)(Image *) = nullptr;

Vector<uint8_t> (*Image::lossy_packer)(const Ref<Image> &, float) = nullptr;
Ref<Image> (*Image::lossy_unpacker)(const Vector<uint8_t> &) = nullptr;
Vector<uint8_t> (*Image::lossless_packer)(const Ref<Image> &) = nullptr;
Ref<Image> (*Image::lossless_unpacker)(const Vector<uint8_t> &) = nullptr;
Vector<uint8_t> (*Image::webp_lossy_packer)(const Ref<Image> &, float) = nullptr;
Vector<uint8_t> (*Image::webp_lossless_packer)(const Ref<Image> &) = nullptr;
Ref<Image> (*Image::webp_unpacker)(const Vector<uint8_t> &) = nullptr;
Vector<uint8_t> (*Image::png_packer)(const Ref<Image> &) = nullptr;
Ref<Image> (*Image::png_unpacker)(const Vector<uint8_t> &) = nullptr;
Vector<uint8_t> (*Image::basis_universal_packer)(const Ref<Image> &, Image::UsedChannels) = nullptr;
Ref<Image> (*Image::basis_universal_unpacker)(const Vector<uint8_t> &) = nullptr;

Expand Down
9 changes: 5 additions & 4 deletions core/io/image.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,11 @@ class Image : public Resource {
static void (*_image_decompress_etc1)(Image *);
static void (*_image_decompress_etc2)(Image *);

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

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 @@ Vector<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;
}
18 changes: 11 additions & 7 deletions editor/import/resource_importer_layered_texture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ String ResourceImporterLayeredTexture::get_resource_type() const {
bool ResourceImporterLayeredTexture::get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const {
if (p_option == "compress/lossy_quality" && p_options.has("compress/mode")) {
return int(p_options["compress/mode"]) == COMPRESS_LOSSY;
} else if (p_option == "compress/lossless_force_png" && p_options.has("compress/mode")) {
return int(p_options["compress/mode"]) == COMPRESS_LOSSLESS;
}
return true;
}
Expand All @@ -136,6 +138,7 @@ String ResourceImporterLayeredTexture::get_preset_name(int p_idx) const {
void ResourceImporterLayeredTexture::get_import_options(List<ImportOption> *r_options, int p_preset) const {
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/mode", PROPERTY_HINT_ENUM, "Lossless (PNG),Lossy (WebP),Video RAM (S3TC/ETC/BPTC),Uncompressed,Basis Universal", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 1));
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "compress/lossy_quality", PROPERTY_HINT_RANGE, "0,1,0.01"), 0.7));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "compress/lossless_force_png"), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/hdr_compression", PROPERTY_HINT_ENUM, "Disabled,Opaque Only,Always"), 1));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/bptc_ldr", PROPERTY_HINT_ENUM, "Disabled,Enabled,RGBA Only"), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/channel_pack", PROPERTY_HINT_ENUM, "sRGB Friendly,Optimized"), 0));
Expand All @@ -155,7 +158,7 @@ void ResourceImporterLayeredTexture::get_import_options(List<ImportOption> *r_op
}
}

void ResourceImporterLayeredTexture::_save_tex(Vector<Ref<Image>> p_images, const String &p_to_path, int p_compress_mode, float p_lossy, Image::CompressMode p_vram_compression, Image::CompressSource p_csource, Image::UsedChannels used_channels, bool p_mipmaps, bool p_force_po2) {
void ResourceImporterLayeredTexture::_save_tex(Vector<Ref<Image>> p_images, const String &p_to_path, int p_compress_mode, float p_lossy, bool p_lossless_force_png, Image::CompressMode p_vram_compression, Image::CompressSource p_csource, Image::UsedChannels used_channels, bool p_mipmaps, bool p_force_po2) {
Vector<Ref<Image>> mipmap_images; //for 3D

if (mode == MODE_3D) {
Expand Down Expand Up @@ -273,11 +276,11 @@ void ResourceImporterLayeredTexture::_save_tex(Vector<Ref<Image>> p_images, cons
f->store_32(0);

for (int i = 0; i < p_images.size(); i++) {
ResourceImporterTexture::save_to_stex_format(f, p_images[i], ResourceImporterTexture::CompressMode(p_compress_mode), used_channels, p_vram_compression, p_lossy);
ResourceImporterTexture::save_to_stex_format(f, p_images[i], ResourceImporterTexture::CompressMode(p_compress_mode), used_channels, p_vram_compression, p_lossy, p_lossless_force_png);
}

for (int i = 0; i < mipmap_images.size(); i++) {
ResourceImporterTexture::save_to_stex_format(f, mipmap_images[i], ResourceImporterTexture::CompressMode(p_compress_mode), used_channels, p_vram_compression, p_lossy);
ResourceImporterTexture::save_to_stex_format(f, mipmap_images[i], ResourceImporterTexture::CompressMode(p_compress_mode), used_channels, p_vram_compression, p_lossy, p_lossless_force_png);
}

f->close();
Expand All @@ -286,6 +289,7 @@ void ResourceImporterLayeredTexture::_save_tex(Vector<Ref<Image>> p_images, cons
Error ResourceImporterLayeredTexture::import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
int compress_mode = p_options["compress/mode"];
float lossy = p_options["compress/lossy_quality"];
bool lossless_force_png = p_options["compress/lossless_force_png"];
int hdr_compression = p_options["compress/hdr_compression"];
int bptc_ldr = p_options["compress/bptc_ldr"];
bool mipmaps = p_options["mipmaps/generate"];
Expand Down Expand Up @@ -441,20 +445,20 @@ Error ResourceImporterLayeredTexture::import(const String &p_source_file, const
}

if (can_bptc || can_s3tc) {
_save_tex(slices, p_save_path + ".s3tc." + extension, compress_mode, lossy, can_bptc ? Image::COMPRESS_BPTC : Image::COMPRESS_S3TC, csource, used_channels, mipmaps, false);
_save_tex(slices, p_save_path + ".s3tc." + extension, compress_mode, lossy, lossless_force_png, can_bptc ? Image::COMPRESS_BPTC : Image::COMPRESS_S3TC, csource, used_channels, mipmaps, false);
r_platform_variants->push_back("s3tc");
formats_imported.push_back("s3tc");
ok_on_pc = true;
}

if (ProjectSettings::get_singleton()->get("rendering/textures/vram_compression/import_etc2")) {
_save_tex(slices, p_save_path + ".etc2." + extension, compress_mode, lossy, Image::COMPRESS_ETC2, csource, used_channels, mipmaps, true);
_save_tex(slices, p_save_path + ".etc2." + extension, compress_mode, lossy, lossless_force_png, Image::COMPRESS_ETC2, csource, used_channels, mipmaps, true);
r_platform_variants->push_back("etc2");
formats_imported.push_back("etc2");
}

if (ProjectSettings::get_singleton()->get("rendering/textures/vram_compression/import_pvrtc")) {
_save_tex(slices, p_save_path + ".etc2." + extension, compress_mode, lossy, Image::COMPRESS_ETC2, csource, used_channels, mipmaps, true);
_save_tex(slices, p_save_path + ".etc2." + extension, compress_mode, lossy, lossless_force_png, Image::COMPRESS_ETC2, csource, used_channels, mipmaps, true);
r_platform_variants->push_back("pvrtc");
formats_imported.push_back("pvrtc");
}
Expand All @@ -464,7 +468,7 @@ Error ResourceImporterLayeredTexture::import(const String &p_source_file, const
}
} else {
//import normally
_save_tex(slices, p_save_path + "." + extension, compress_mode, lossy, Image::COMPRESS_S3TC /* IGNORED */, csource, used_channels, mipmaps, false);
_save_tex(slices, p_save_path + "." + extension, compress_mode, lossy, lossless_force_png, Image::COMPRESS_S3TC /* IGNORED */, csource, used_channels, mipmaps, false);
}

if (r_metadata) {
Expand Down
2 changes: 1 addition & 1 deletion editor/import/resource_importer_layered_texture.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ class ResourceImporterLayeredTexture : public ResourceImporter {
virtual void get_import_options(List<ImportOption> *r_options, int p_preset = 0) const override;
virtual bool get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const override;

void _save_tex(Vector<Ref<Image>> p_images, const String &p_to_path, int p_compress_mode, float p_lossy, Image::CompressMode p_vram_compression, Image::CompressSource p_csource, Image::UsedChannels used_channels, bool p_mipmaps, bool p_force_po2);
void _save_tex(Vector<Ref<Image>> p_images, const String &p_to_path, int p_compress_mode, float p_lossy, bool p_lossless_force_png, Image::CompressMode p_vram_compression, Image::CompressSource p_csource, Image::UsedChannels used_channels, bool p_mipmaps, bool p_force_po2);

virtual Error import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;

Expand Down
34 changes: 22 additions & 12 deletions editor/import/resource_importer_texture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ bool ResourceImporterTexture::get_option_visibility(const String &p_option, cons
if (compress_mode != COMPRESS_LOSSY && compress_mode != COMPRESS_VRAM_COMPRESSED) {
return false;
}
} else if (p_option == "compress/lossless_force_png") {
return int(p_options["compress/mode"]) == COMPRESS_LOSSLESS;
} else if (p_option == "compress/hdr_mode") {
int compress_mode = int(p_options["compress/mode"]);
if (compress_mode < COMPRESS_VRAM_COMPRESSED) {
Expand Down Expand Up @@ -201,6 +203,7 @@ void ResourceImporterTexture::get_import_options(List<ImportOption> *r_options,
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/bptc_ldr", PROPERTY_HINT_ENUM, "Disabled,Enabled,RGBA Only"), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/normal_map", PROPERTY_HINT_ENUM, "Detect,Enable,Disabled"), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/channel_pack", PROPERTY_HINT_ENUM, "sRGB Friendly,Optimized"), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "compress/lossless_force_png"), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "compress/streamed"), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "mipmaps/generate"), (p_preset == PRESET_3D ? true : false)));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "mipmaps/limit", PROPERTY_HINT_RANGE, "-1,256"), -1));
Expand All @@ -215,17 +218,23 @@ void ResourceImporterTexture::get_import_options(List<ImportOption> *r_options,
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "svg/scale", PROPERTY_HINT_RANGE, "0.001,100,0.001"), 1.0));
}

void ResourceImporterTexture::save_to_stex_format(FileAccess *f, const Ref<Image> &p_image, CompressMode p_compress_mode, Image::UsedChannels p_channels, Image::CompressMode p_compress_format, float p_lossy_quality) {
void ResourceImporterTexture::save_to_stex_format(FileAccess *f, const Ref<Image> &p_image, CompressMode p_compress_mode, Image::UsedChannels p_channels, Image::CompressMode p_compress_format, float p_lossy_quality, bool p_lossless_force_png) {
switch (p_compress_mode) {
case COMPRESS_LOSSLESS: {
f->store_32(StreamTexture2D::DATA_FORMAT_LOSSLESS);
bool use_webp = !p_lossless_force_png && p_image->get_width() <= 16383 && p_image->get_height() <= 16383; // WebP has a size limit
f->store_32(use_webp ? StreamTexture2D::DATA_FORMAT_WEBP : StreamTexture2D::DATA_FORMAT_PNG);
f->store_16(p_image->get_width());
f->store_16(p_image->get_height());
f->store_32(p_image->get_mipmap_count());
f->store_32(p_image->get_format());

for (int i = 0; i < p_image->get_mipmap_count() + 1; i++) {
Vector<uint8_t> data = Image::lossless_packer(p_image->get_image_from_mipmap(i));
Vector<uint8_t> data;
if (use_webp) {
data = Image::webp_lossless_packer(p_image->get_image_from_mipmap(i));
} else {
data = Image::png_packer(p_image->get_image_from_mipmap(i));
}
int data_len = data.size();
f->store_32(data_len);

Expand All @@ -235,14 +244,14 @@ void ResourceImporterTexture::save_to_stex_format(FileAccess *f, const Ref<Image

} break;
case COMPRESS_LOSSY: {
f->store_32(StreamTexture2D::DATA_FORMAT_LOSSY);
f->store_32(StreamTexture2D::DATA_FORMAT_WEBP);
f->store_16(p_image->get_width());
f->store_16(p_image->get_height());
f->store_32(p_image->get_mipmap_count());
f->store_32(p_image->get_format());

for (int i = 0; i < p_image->get_mipmap_count() + 1; i++) {
Vector<uint8_t> data = Image::lossy_packer(p_image->get_image_from_mipmap(i), p_lossy_quality);
Vector<uint8_t> data = Image::webp_lossy_packer(p_image->get_image_from_mipmap(i), p_lossy_quality);
int data_len = data.size();
f->store_32(data_len);

Expand Down Expand Up @@ -299,7 +308,7 @@ void ResourceImporterTexture::save_to_stex_format(FileAccess *f, const Ref<Image
}
}

void ResourceImporterTexture::_save_stex(const Ref<Image> &p_image, const String &p_to_path, CompressMode p_compress_mode, float p_lossy_quality, Image::CompressMode p_vram_compression, bool p_mipmaps, bool p_streamable, bool p_detect_3d, bool p_detect_roughness, bool p_detect_normal, bool p_force_normal, bool p_srgb_friendly, bool p_force_po2_for_compressed, uint32_t p_limit_mipmap, const Ref<Image> &p_normal, Image::RoughnessChannel p_roughness_channel) {
void ResourceImporterTexture::_save_stex(const Ref<Image> &p_image, const String &p_to_path, CompressMode p_compress_mode, float p_lossy_quality, bool p_lossless_force_webp, Image::CompressMode p_vram_compression, bool p_mipmaps, bool p_streamable, bool p_detect_3d, bool p_detect_roughness, bool p_detect_normal, bool p_force_normal, bool p_srgb_friendly, bool p_force_po2_for_compressed, uint32_t p_limit_mipmap, const Ref<Image> &p_normal, Image::RoughnessChannel p_roughness_channel) {
FileAccess *f = FileAccess::open(p_to_path, FileAccess::WRITE);
f->store_8('G');
f->store_8('S');
Expand Down Expand Up @@ -375,14 +384,15 @@ void ResourceImporterTexture::_save_stex(const Ref<Image> &p_image, const String

Image::UsedChannels used_channels = image->detect_used_channels(csource);

save_to_stex_format(f, image, p_compress_mode, used_channels, p_vram_compression, p_lossy_quality);
save_to_stex_format(f, image, p_compress_mode, used_channels, p_vram_compression, p_lossy_quality, p_lossless_force_webp);

memdelete(f);
}

Error ResourceImporterTexture::import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
CompressMode compress_mode = CompressMode(int(p_options["compress/mode"]));
float lossy = p_options["compress/lossy_quality"];
bool lossless_force_png = p_options["compress/lossless_force_png"];
int pack_channels = p_options["compress/channel_pack"];
bool mipmaps = p_options["mipmaps/generate"];
uint32_t mipmap_limit = int(mipmaps ? int(p_options["mipmaps/limit"]) : int(-1));
Expand Down Expand Up @@ -518,26 +528,26 @@ Error ResourceImporterTexture::import(const String &p_source_file, const String
}

if (can_bptc || can_s3tc) {
_save_stex(image, p_save_path + ".s3tc.stex", compress_mode, lossy, can_bptc ? Image::COMPRESS_BPTC : Image::COMPRESS_S3TC, mipmaps, stream, detect_3d, detect_roughness, detect_normal, force_normal, srgb_friendly_pack, false, mipmap_limit, normal_image, roughness_channel);
_save_stex(image, p_save_path + ".s3tc.stex", compress_mode, lossy, lossless_force_png, can_bptc ? Image::COMPRESS_BPTC : Image::COMPRESS_S3TC, mipmaps, stream, detect_3d, detect_roughness, detect_normal, force_normal, srgb_friendly_pack, false, mipmap_limit, normal_image, roughness_channel);
r_platform_variants->push_back("s3tc");
formats_imported.push_back("s3tc");
ok_on_pc = true;
}

if (ProjectSettings::get_singleton()->get("rendering/textures/vram_compression/import_etc2")) {
_save_stex(image, p_save_path + ".etc2.stex", compress_mode, lossy, Image::COMPRESS_ETC2, mipmaps, stream, detect_3d, detect_roughness, detect_normal, force_normal, srgb_friendly_pack, true, mipmap_limit, normal_image, roughness_channel);
_save_stex(image, p_save_path + ".etc2.stex", compress_mode, lossy, lossless_force_png, Image::COMPRESS_ETC2, mipmaps, stream, detect_3d, detect_roughness, detect_normal, force_normal, srgb_friendly_pack, true, mipmap_limit, normal_image, roughness_channel);
r_platform_variants->push_back("etc2");
formats_imported.push_back("etc2");
}

if (ProjectSettings::get_singleton()->get("rendering/textures/vram_compression/import_etc")) {
_save_stex(image, p_save_path + ".etc.stex", compress_mode, lossy, Image::COMPRESS_ETC, mipmaps, stream, detect_3d, detect_roughness, detect_normal, force_normal, srgb_friendly_pack, true, mipmap_limit, normal_image, roughness_channel);
_save_stex(image, p_save_path + ".etc.stex", compress_mode, lossy, lossless_force_png, Image::COMPRESS_ETC, mipmaps, stream, detect_3d, detect_roughness, detect_normal, force_normal, srgb_friendly_pack, true, mipmap_limit, normal_image, roughness_channel);
r_platform_variants->push_back("etc");
formats_imported.push_back("etc");
}

if (ProjectSettings::get_singleton()->get("rendering/textures/vram_compression/import_pvrtc")) {
_save_stex(image, p_save_path + ".pvrtc.stex", compress_mode, lossy, Image::COMPRESS_PVRTC1_4, mipmaps, stream, detect_3d, detect_roughness, detect_normal, force_normal, srgb_friendly_pack, true, mipmap_limit, normal_image, roughness_channel);
_save_stex(image, p_save_path + ".pvrtc.stex", compress_mode, lossy, lossless_force_png, Image::COMPRESS_PVRTC1_4, mipmaps, stream, detect_3d, detect_roughness, detect_normal, force_normal, srgb_friendly_pack, true, mipmap_limit, normal_image, roughness_channel);
r_platform_variants->push_back("pvrtc");
formats_imported.push_back("pvrtc");
}
Expand All @@ -547,7 +557,7 @@ Error ResourceImporterTexture::import(const String &p_source_file, const String
}
} else {
//import normally
_save_stex(image, p_save_path + ".stex", compress_mode, lossy, Image::COMPRESS_S3TC /*this is ignored */, mipmaps, stream, detect_3d, detect_roughness, detect_normal, force_normal, srgb_friendly_pack, false, mipmap_limit, normal_image, roughness_channel);
_save_stex(image, p_save_path + ".stex", compress_mode, lossy, lossless_force_png, Image::COMPRESS_S3TC /*this is ignored */, mipmaps, stream, detect_3d, detect_roughness, detect_normal, force_normal, srgb_friendly_pack, false, mipmap_limit, normal_image, roughness_channel);
}

if (r_metadata) {
Expand Down
Loading

0 comments on commit d1756eb

Please sign in to comment.