Skip to content

Commit

Permalink
io-webp.c: discard incremental decoding libwebp is problematic with l…
Browse files Browse the repository at this point in the history
…arge files and accumulates encoded buffer anyway
  • Loading branch information
Alberto Ruiz committed Mar 30, 2023
1 parent a3bd991 commit 2119e3f
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 94 deletions.
139 changes: 50 additions & 89 deletions io-webp.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,10 @@ typedef struct
GdkPixbufModuleUpdatedFunc update_func;
GdkPixbufModulePreparedFunc prepare_func;
gpointer user_data;
WebPDecoderConfig deccfg;
gboolean got_header;
gboolean is_animation;
gboolean has_alpha;
GByteArray *buffer;
WebPIDecoder *idec;
GdkPixbuf *pb;
gint width;
gint height;
} WebPContext;
Expand All @@ -51,13 +48,10 @@ begin_load (GdkPixbufModuleSizeFunc size_func,
context->prepare_func = prepare_func;
context->update_func = update_func;
context->user_data = user_data;
context->deccfg = (WebPDecoderConfig){ 0 };
context->got_header = FALSE;
context->is_animation = FALSE;
context->has_alpha = FALSE;
context->buffer = NULL;
context->idec = NULL;
context->pb = NULL;
context->width = 0;
context->height = 0;

Expand Down Expand Up @@ -116,88 +110,11 @@ load_increment (gpointer data, const guchar *buf, guint size, GError **error)
context->has_alpha = features.has_alpha;
context->is_animation = features.has_animation;

if (! context->is_animation)
{
gchar *icc_data = NULL;
WebPData wp_data = { .bytes = buf, .size = size };
WebPMux *mux = WebPMuxCreate (&wp_data, FALSE);
if (mux)
{
WebPData icc_profile = { 0 };
if (WebPMuxGetChunk (mux, "ICCP", &icc_profile) == WEBP_MUX_OK
&& icc_profile.bytes)
icc_data = g_base64_encode (icc_profile.bytes, icc_profile.size);
g_clear_pointer (&mux, WebPMuxDelete);
}

context->pb = gdk_pixbuf_new (GDK_COLORSPACE_RGB, context->has_alpha,
8, context->width, context->height);

if (! context->pb)
{
g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED,
"Could not allocate GdkPixbuf");
return FALSE;
}

if (icc_data)
{
gdk_pixbuf_set_option (context->pb, "icc-profile", icc_data);
g_clear_pointer (&icc_data, g_free);
}

WebPDecoderConfig config;
init_dec_config (&config, context->pb);

context->idec = WebPIDecode (NULL, 0, &config);

if (! context->idec)
{
g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED,
"Could not allocate WebP incremental decoder");
return FALSE;
}

if (context->prepare_func && context->pb)
{
context->prepare_func (context->pb, NULL, context->user_data);
}
}
else
{
context->buffer = g_byte_array_new ();
}
context->buffer = g_byte_array_new ();
}

if (context->buffer)
{
g_byte_array_append (context->buffer, buf, size);
}
else if (context->pb && context->idec)
{
VP8StatusCode status = WebPIAppend (context->idec, buf, (size_t) size);
if (status != VP8_STATUS_OK && status != VP8_STATUS_SUSPENDED)
{
g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED,
"Could not incrementally decode WebP stream chunk");
return FALSE;
}

gint last_y = 0;
gint w = 0;
if (WebPIDecGetRGB (context->idec, &last_y, &w, NULL, NULL) == NULL
&& status != VP8_STATUS_SUSPENDED)
{
g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED,
"Bad inputs to WebP decoder.");
return FALSE;
}

if (status != VP8_STATUS_SUSPENDED && context->update_func)
{
context->update_func (context->pb, 0, 0, w, last_y, context->user_data);
}
}
g_byte_array_append (context->buffer, buf, size);

return TRUE;
}
Expand Down Expand Up @@ -232,9 +149,55 @@ stop_load (gpointer data, GError **error)
g_clear_object (&iter);
g_clear_object (&anim);
}
else if (context->got_header && context->pb)
else if (context->got_header && context->buffer)
{
ret = TRUE;
gchar *icc_data = NULL;
WebPData wp_data = { .bytes = context->buffer->data,
.size = context->buffer->len };
WebPMux *mux = WebPMuxCreate (&wp_data, FALSE);
if (mux)
{
WebPData icc_profile = { 0 };
if (WebPMuxGetChunk (mux, "ICCP", &icc_profile) == WEBP_MUX_OK
&& icc_profile.bytes)
icc_data = g_base64_encode (icc_profile.bytes, icc_profile.size);
g_clear_pointer (&mux, WebPMuxDelete);
}

GdkPixbuf *pb = gdk_pixbuf_new (GDK_COLORSPACE_RGB, context->has_alpha, 8,
context->width, context->height);

if (! pb)
{
g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED,
"Could not allocate GdkPixbuf");
return FALSE;
}

if (icc_data)
{
gdk_pixbuf_set_option (pb, "icc-profile", icc_data);
g_clear_pointer (&icc_data, g_free);
}

WebPDecoderConfig config;
init_dec_config (&config, pb);

VP8StatusCode status = WebPDecode (context->buffer->data,
context->buffer->len, &config);
if (status == VP8_STATUS_OK)
{
if (context->prepare_func)
context->prepare_func (pb, NULL, context->user_data);

g_clear_object (&pb);

ret = TRUE;
}
else {
g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED,
"WebP decoder failed with VP8 status code: %d", status);
}
}

if (context->buffer)
Expand All @@ -243,8 +206,6 @@ stop_load (gpointer data, GError **error)
context->buffer = NULL;
}

g_clear_object (&context->pb);
g_clear_pointer (&context->idec, WebPIDelete);
g_clear_pointer (&context, g_free);
return ret;
}
Expand Down
1 change: 1 addition & 0 deletions meson.build
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
project('webp-pixbuf-loader', 'c', meson_version: '>=0.54')
cc = meson.get_compiler('c')
gio = dependency('gio-2.0', method: 'pkg-config')
gdkpb = dependency('gdk-pixbuf-2.0', version: '>2.22.0', method: 'pkg-config')
gdk_pb_moddir = get_option('gdk_pixbuf_moduledir')
if gdk_pb_moddir == ''
Expand Down
2 changes: 1 addition & 1 deletion tests/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ t_icc = executable('t_icc', 't_icc.c', dependencies : [gdk
t_null_error = executable('t_null_error', 't_null_error.c', dependencies : [gdkpb, webp, webpdemux])
t_scaled = executable('t_scaled', 't_scaled.c', dependencies : [gdkpb, webp, webpdemux])
t_jpeg = executable('t_jpeg', 't_jpeg.c', dependencies : [gdkpb, webp, webpdemux])
t_large = executable('t_large', 't_large.c', dependencies : [gdkpb, webp, webpdemux])
t_large = executable('t_large', 't_large.c', dependencies : [gdkpb, webp, webpdemux, gio])

loaders_data = configuration_data()
loaders_data.set('MODULE_PATH', fs.as_posix(pbl_webp.full_path()))
Expand Down
8 changes: 4 additions & 4 deletions tests/t_large.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include <gdk-pixbuf/gdk-pixbuf.h>

#define READ_BUFFER_SIZE 65535

gint
main (gint argc, gchar **argv)
{
Expand All @@ -10,14 +12,12 @@ main (gint argc, gchar **argv)
= gdk_pixbuf_new_from_file (g_environ_getenv (env, "TEST_FILE"), &error);

if (error)
g_error ("%s", error->message);

g_assert (error == NULL);
g_error ("%s", error->message);

g_assert_cmpint (gdk_pixbuf_get_width (pixbuf), ==, 4096);
g_assert_cmpint (gdk_pixbuf_get_height (pixbuf), ==, 4096);

g_clear_object (&pixbuf);
g_strfreev (env);
g_object_unref (pixbuf);
return 0;
}

0 comments on commit 2119e3f

Please sign in to comment.