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

Add support for saving WebP images #61770

Merged
merged 1 commit into from
Jun 21, 2022
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
23 changes: 23 additions & 0 deletions core/io/image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ SaveEXRFunc Image::save_exr_func = nullptr;
SavePNGBufferFunc Image::save_png_buffer_func = nullptr;
SaveJPGBufferFunc Image::save_jpg_buffer_func = nullptr;

SaveWebPFunc Image::save_webp_func = nullptr;
SaveWebPBufferFunc Image::save_webp_buffer_func = nullptr;

void Image::_put_pixelb(int p_x, int p_y, uint32_t p_pixel_size, uint8_t *p_data, const uint8_t *p_pixel) {
uint32_t ofs = (p_y * width + p_x) * p_pixel_size;
memcpy(p_data + ofs, p_pixel, p_pixel_size);
Expand Down Expand Up @@ -2320,6 +2323,24 @@ Error Image::save_exr(const String &p_path, bool p_grayscale) const {
return save_exr_func(p_path, Ref<Image>((Image *)this), p_grayscale);
}

Error Image::save_webp(const String &p_path, const bool p_lossy, const float p_quality) const {
if (save_webp_func == nullptr) {
return ERR_UNAVAILABLE;
}
ERR_FAIL_COND_V_MSG(p_lossy && !(0.0f <= p_quality && p_quality <= 1.0f), ERR_INVALID_PARAMETER, "The WebP lossy quality was set to " + rtos(p_quality) + ", which is not valid. WebP lossy quality must be between 0.0 and 1.0 (inclusive).");

return save_webp_func(p_path, Ref<Image>((Image *)this), p_lossy, p_quality);
}

Vector<uint8_t> Image::save_webp_to_buffer(const bool p_lossy, const float p_quality) const {
if (save_webp_buffer_func == nullptr) {
return Vector<uint8_t>();
}
ERR_FAIL_COND_V_MSG(p_lossy && !(0.0f <= p_quality && p_quality <= 1.0f), Vector<uint8_t>(), "The WebP lossy quality was set to " + rtos(p_quality) + ", which is not valid. WebP lossy quality must be between 0.0 and 1.0 (inclusive).");

return save_webp_buffer_func(Ref<Image>((Image *)this), p_lossy, p_quality);
}

int Image::get_image_data_size(int p_width, int p_height, Format p_format, bool p_mipmaps) {
int mm;
return _get_dst_image_size(p_width, p_height, p_format, mm, p_mipmaps ? -1 : 0);
Expand Down Expand Up @@ -3159,6 +3180,8 @@ void Image::_bind_methods() {
ClassDB::bind_method(D_METHOD("save_jpg", "path", "quality"), &Image::save_jpg, DEFVAL(0.75));
ClassDB::bind_method(D_METHOD("save_jpg_to_buffer", "quality"), &Image::save_jpg_to_buffer, DEFVAL(0.75));
ClassDB::bind_method(D_METHOD("save_exr", "path", "grayscale"), &Image::save_exr, DEFVAL(false));
ClassDB::bind_method(D_METHOD("save_webp", "path", "lossy", "quality"), &Image::save_webp, DEFVAL(false), DEFVAL(0.75f));
ClassDB::bind_method(D_METHOD("save_webp_to_buffer", "lossy", "quality"), &Image::save_webp_to_buffer, DEFVAL(false), DEFVAL(0.75f));

ClassDB::bind_method(D_METHOD("detect_alpha"), &Image::detect_alpha);
ClassDB::bind_method(D_METHOD("is_invisible"), &Image::is_invisible);
Expand Down
6 changes: 6 additions & 0 deletions core/io/image.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ typedef Vector<uint8_t> (*SavePNGBufferFunc)(const Ref<Image> &p_img);
typedef Error (*SaveJPGFunc)(const String &p_path, const Ref<Image> &p_img, float p_quality);
typedef Vector<uint8_t> (*SaveJPGBufferFunc)(const Ref<Image> &p_img, float p_quality);
typedef Ref<Image> (*ImageMemLoadFunc)(const uint8_t *p_png, int p_size);
typedef Error (*SaveWebPFunc)(const String &p_path, const Ref<Image> &p_img, const bool p_lossy, const float p_quality);
typedef Vector<uint8_t> (*SaveWebPBufferFunc)(const Ref<Image> &p_img, const bool p_lossy, const float p_quality);

typedef Error (*SaveEXRFunc)(const String &p_path, const Ref<Image> &p_img, bool p_grayscale);

Expand All @@ -60,6 +62,8 @@ class Image : public Resource {
static SaveEXRFunc save_exr_func;
static SavePNGBufferFunc save_png_buffer_func;
static SaveJPGBufferFunc save_jpg_buffer_func;
static SaveWebPFunc save_webp_func;
static SaveWebPBufferFunc save_webp_buffer_func;

enum {
MAX_WIDTH = (1 << 24), // force a limit somehow
Expand Down Expand Up @@ -289,6 +293,8 @@ class Image : public Resource {
Vector<uint8_t> save_png_to_buffer() const;
Vector<uint8_t> save_jpg_to_buffer(float p_quality = 0.75) const;
Error save_exr(const String &p_path, bool p_grayscale) const;
Error save_webp(const String &p_path, const bool p_lossy = false, const float p_quality = 0.75f) const;
Vector<uint8_t> save_webp_to_buffer(const bool p_lossy = false, const float p_quality = 0.75f) const;

void create_empty(int p_width, int p_height, bool p_use_mipmaps, Format p_format) {
create(p_width, p_height, p_use_mipmaps, p_format);
Expand Down
20 changes: 19 additions & 1 deletion doc/classes/Image.xml
Original file line number Diff line number Diff line change
Expand Up @@ -397,12 +397,30 @@
<return type="int" enum="Error" />
<argument index="0" name="path" type="String" />
<description>
Saves the image as a PNG file to [code]path[/code].
Saves the image as a PNG file to the file at [code]path[/code].
</description>
</method>
<method name="save_png_to_buffer" qualifiers="const">
<return type="PackedByteArray" />
<description>
Saves the image as a PNG file to a byte array.
</description>
</method>
<method name="save_webp" qualifiers="const">
<return type="int" enum="Error" />
<argument index="0" name="path" type="String" />
<argument index="1" name="lossy" type="bool" default="false" />
<argument index="2" name="quality" type="float" default="0.75" />
<description>
Saves the image as a WebP (Web Picture) file to the file at [code]path[/code]. By default it will save lossless. If [code]lossy[/code] is true, the image will be saved lossy, using the [code]quality[/code] setting between 0.0 and 1.0 (inclusive).
</description>
</method>
<method name="save_webp_to_buffer" qualifiers="const">
<return type="PackedByteArray" />
<argument index="0" name="lossy" type="bool" default="false" />
<argument index="1" name="quality" type="float" default="0.75" />
<description>
Saves the image as a WebP (Web Picture) file to a byte array. By default it will save lossless. If [code]lossy[/code] is true, the image will be saved lossy, using the [code]quality[/code] setting between 0.0 and 1.0 (inclusive).
</description>
</method>
<method name="set_pixel">
Expand Down
181 changes: 9 additions & 172 deletions modules/webp/image_loader_webp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,184 +34,21 @@
#include "core/io/marshalls.h"
#include "core/os/os.h"
#include "core/string/print_string.h"
#include "webp_common.h"

#include <stdlib.h>
#include <webp/decode.h>
#include <webp/encode.h>

static Vector<uint8_t> _webp_lossy_pack(const Ref<Image> &p_image, float p_quality) {
ERR_FAIL_COND_V(p_image.is_null() || p_image->is_empty(), Vector<uint8_t>());

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());
Vector<uint8_t> data = img->get_data();
const uint8_t *r = data.ptr();

uint8_t *dst_buff = nullptr;
size_t dst_size = 0;
if (img->get_format() == Image::FORMAT_RGB8) {
dst_size = WebPEncodeRGB(r, s.width, s.height, 3 * s.width, CLAMP(p_quality * 100.0, 0, 100.0), &dst_buff);
} else {
dst_size = WebPEncodeRGBA(r, s.width, s.height, 4 * s.width, CLAMP(p_quality * 100.0, 0, 100.0), &dst_buff);
}

ERR_FAIL_COND_V(dst_size == 0, Vector<uint8_t>());
Vector<uint8_t> dst;
dst.resize(4 + dst_size);
uint8_t *w = dst.ptrw();
w[0] = 'W';
w[1] = 'E';
w[2] = 'B';
w[3] = 'P';
memcpy(&w[4], dst_buff, dst_size);
WebPFree(dst_buff);

return dst;
}

static Vector<uint8_t> _webp_lossless_pack(const Ref<Image> &p_image) {
ERR_FAIL_COND_V(p_image.is_null() || p_image->is_empty(), Vector<uint8_t>());

int compression_level = ProjectSettings::get_singleton()->get("rendering/textures/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());
Vector<uint8_t> data = img->get_data();
const uint8_t *r = data.ptr();

// 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(Vector<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, 3 * s.width);
} else {
success_import = WebPPictureImportRGBA(&pic, r, 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(Vector<uint8_t>(), "WebP packing failed.");
}

// copy from wrt
Vector<uint8_t> dst;
dst.resize(4 + wrt.size);
uint8_t *w = dst.ptrw();
w[0] = 'W';
w[1] = 'E';
w[2] = 'B';
w[3] = 'P';
memcpy(&w[4], wrt.mem, wrt.size);
WebPMemoryWriterClear(&wrt);

return dst;
}

static Ref<Image> _webp_unpack(const Vector<uint8_t> &p_buffer) {
int size = p_buffer.size() - 4;
ERR_FAIL_COND_V(size <= 0, Ref<Image>());
const uint8_t *r = p_buffer.ptr();

ERR_FAIL_COND_V(r[0] != 'W' || r[1] != 'E' || r[2] != 'B' || r[3] != 'P', Ref<Image>());
WebPBitstreamFeatures features;
if (WebPGetFeatures(&r[4], size, &features) != VP8_STATUS_OK) {
ERR_FAIL_V_MSG(Ref<Image>(), "Error unpacking WEBP image.");
}

/*
print_line("width: "+itos(features.width));
print_line("height: "+itos(features.height));
print_line("alpha: "+itos(features.has_alpha));
*/

Vector<uint8_t> dst_image;
int datasize = features.width * features.height * (features.has_alpha ? 4 : 3);
dst_image.resize(datasize);

uint8_t *dst_w = dst_image.ptrw();

bool errdec = false;
if (features.has_alpha) {
errdec = WebPDecodeRGBAInto(&r[4], size, dst_w, datasize, 4 * features.width) == nullptr;
} else {
errdec = WebPDecodeRGBInto(&r[4], size, dst_w, datasize, 3 * features.width) == nullptr;
}

ERR_FAIL_COND_V_MSG(errdec, Ref<Image>(), "Failed decoding WebP image.");

Ref<Image> img = memnew(Image(features.width, features.height, 0, features.has_alpha ? Image::FORMAT_RGBA8 : Image::FORMAT_RGB8, dst_image));
return img;
}

Error webp_load_image_from_buffer(Image *p_image, const uint8_t *p_buffer, int p_buffer_len) {
ERR_FAIL_NULL_V(p_image, ERR_INVALID_PARAMETER);

WebPBitstreamFeatures features;
if (WebPGetFeatures(p_buffer, p_buffer_len, &features) != VP8_STATUS_OK) {
ERR_FAIL_V(ERR_FILE_CORRUPT);
}

Vector<uint8_t> dst_image;
int datasize = features.width * features.height * (features.has_alpha ? 4 : 3);
dst_image.resize(datasize);
uint8_t *dst_w = dst_image.ptrw();

bool errdec = false;
if (features.has_alpha) {
errdec = WebPDecodeRGBAInto(p_buffer, p_buffer_len, dst_w, datasize, 4 * features.width) == nullptr;
} else {
errdec = WebPDecodeRGBInto(p_buffer, p_buffer_len, dst_w, datasize, 3 * features.width) == nullptr;
}

ERR_FAIL_COND_V_MSG(errdec, ERR_FILE_CORRUPT, "Failed decoding WebP image.");

p_image->create(features.width, features.height, false, features.has_alpha ? Image::FORMAT_RGBA8 : Image::FORMAT_RGB8, dst_image);

return OK;
}

static Ref<Image> _webp_mem_loader_func(const uint8_t *p_png, int p_size) {
Ref<Image> img;
img.instantiate();
Error err = webp_load_image_from_buffer(img.ptr(), p_png, p_size);
Error err = WebPCommon::webp_load_image_from_buffer(img.ptr(), p_png, p_size);
ERR_FAIL_COND_V(err, Ref<Image>());
return img;
}

Error ImageLoaderWEBP::load_image(Ref<Image> p_image, Ref<FileAccess> f, bool p_force_linear, float p_scale) {
Error ImageLoaderWebP::load_image(Ref<Image> p_image, Ref<FileAccess> f, bool p_force_linear, float p_scale) {
Vector<uint8_t> src_image;
uint64_t src_image_len = f->get_length();
ERR_FAIL_COND_V(src_image_len == 0, ERR_FILE_CORRUPT);
Expand All @@ -221,18 +58,18 @@ Error ImageLoaderWEBP::load_image(Ref<Image> p_image, Ref<FileAccess> f, bool p_

f->get_buffer(&w[0], src_image_len);

Error err = webp_load_image_from_buffer(p_image.ptr(), w, src_image_len);
Error err = WebPCommon::webp_load_image_from_buffer(p_image.ptr(), w, src_image_len);

return err;
}

void ImageLoaderWEBP::get_recognized_extensions(List<String> *p_extensions) const {
void ImageLoaderWebP::get_recognized_extensions(List<String> *p_extensions) const {
p_extensions->push_back("webp");
}

ImageLoaderWEBP::ImageLoaderWEBP() {
ImageLoaderWebP::ImageLoaderWebP() {
Image::_webp_mem_loader_func = _webp_mem_loader_func;
Image::webp_lossy_packer = _webp_lossy_pack;
Image::webp_lossless_packer = _webp_lossless_pack;
Image::webp_unpacker = _webp_unpack;
Image::webp_lossy_packer = WebPCommon::_webp_lossy_pack;
Image::webp_lossless_packer = WebPCommon::_webp_lossless_pack;
Image::webp_unpacker = WebPCommon::_webp_unpack;
}
4 changes: 2 additions & 2 deletions modules/webp/image_loader_webp.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@

#include "core/io/image_loader.h"

class ImageLoaderWEBP : public ImageFormatLoader {
class ImageLoaderWebP : public ImageFormatLoader {
public:
virtual Error load_image(Ref<Image> p_image, Ref<FileAccess> f, bool p_force_linear, float p_scale);
virtual void get_recognized_extensions(List<String> *p_extensions) const;
ImageLoaderWEBP();
ImageLoaderWebP();
};

#endif
10 changes: 8 additions & 2 deletions modules/webp/register_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,20 @@
#include "register_types.h"

#include "image_loader_webp.h"
#include "resource_saver_webp.h"

static ImageLoaderWEBP *image_loader_webp = nullptr;
static ImageLoaderWebP *image_loader_webp = nullptr;
static Ref<ResourceSaverWebP> resource_saver_webp;

void initialize_webp_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}

image_loader_webp = memnew(ImageLoaderWEBP);
image_loader_webp = memnew(ImageLoaderWebP);
resource_saver_webp.instantiate();
ImageLoader::add_image_format_loader(image_loader_webp);
ResourceSaver::add_resource_format_saver(resource_saver_webp);
}

void uninitialize_webp_module(ModuleInitializationLevel p_level) {
Expand All @@ -49,4 +53,6 @@ void uninitialize_webp_module(ModuleInitializationLevel p_level) {
}

memdelete(image_loader_webp);
ResourceSaver::remove_resource_format_saver(resource_saver_webp);
resource_saver_webp.unref();
}
Loading