Skip to content

Commit

Permalink
Add a gdk-pixbuf module
Browse files Browse the repository at this point in the history
gdk-pixbuf[1] is a widely-used image loader on Linux, this module gives
most Linux software instant support for AVIF.

gdk-pixbuf upstream rejects any new format[2], so in order to make Linux
distributions aware that this AVIF loader exists it makes sense to ship
it alongside libavif.

For now it only supports loading still images (no sequences) and doesn’t
support saving, but this can come in a later pull request.

[1] https://developer.gnome.org/gdk-pixbuf/stable/
[2] https://gitlab.gnome.org/GNOME/gdk-pixbuf/-/merge_requests/76#note_845043
  • Loading branch information
linkmauve committed Jun 21, 2020
1 parent fa99082 commit d5ea7ed
Show file tree
Hide file tree
Showing 2 changed files with 246 additions and 0 deletions.
26 changes: 26 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,32 @@ if(AVIF_BUILD_TESTS)
)
endif()

option(AVIF_BUILD_GDK_PIXBUF "Build a gdk-pixbuf loader" ON)
if(AVIF_BUILD_GDK_PIXBUF)
find_package(PkgConfig)
if(PKG_CONFIG_FOUND)
pkg_search_module(GDK_PIXBUF gdk-pixbuf-2.0)
if(GDK_PIXBUF_FOUND)
set(GDK_PIXBUF_SRCS
gdk-pixbuf/loader.c
)
add_library(pixbufloader-avif ${GDK_PIXBUF_SRCS})

# This is required because glib stupidly uses invalid #define names, such as __G_LIB_H__…
add_definitions(-Wno-reserved-id-macro)
target_link_libraries(pixbufloader-avif PUBLIC ${GDK_PIXBUF_LIBRARIES} avif)
target_include_directories(pixbufloader-avif PUBLIC ${GDK_PIXBUF_INCLUDE_DIRS})

pkg_get_variable(GDK_PIXBUF_MODULEDIR gdk-pixbuf-2.0 gdk_pixbuf_moduledir)
install(TARGETS pixbufloader-avif DESTINATION ${GDK_PIXBUF_MODULEDIR})
else()
message(WARNING "gdk-pixbuf loader: disabled due to missing gdk-pixbuf-2.0")
endif()
else()
message(WARNING "gdk-pixbuf loader: disabled due to missing pkg-config")
endif()
endif()

configure_file(libavif.pc.cmake ${CMAKE_CURRENT_BINARY_DIR}/libavif.pc @ONLY)

if(NOT SKIP_INSTALL_LIBRARIES AND NOT SKIP_INSTALL_ALL)
Expand Down
220 changes: 220 additions & 0 deletions gdk-pixbuf/loader.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
// Copyright 2020 Emmanuel Gil Peyrot. All rights reserved.
// SPDX-License-Identifier: BSD-2-Clause

#include <avif/avif.h>

#define GDK_PIXBUF_ENABLE_BACKEND
#include <gdk-pixbuf/gdk-pixbuf-io.h>

G_MODULE_EXPORT void fill_vtable (GdkPixbufModule * module);
G_MODULE_EXPORT void fill_info (GdkPixbufFormat * info);

struct avif_context {
GdkPixbuf * pixbuf;

GdkPixbufModuleSizeFunc size_func;
GdkPixbufModuleUpdatedFunc updated_func;
GdkPixbufModulePreparedFunc prepared_func;
gpointer user_data;

avifDecoder * decoder;
GByteArray * data;
GBytes * bytes;
};

static void avif_context_free(struct avif_context * context)
{
if (!context)
return;

if (context->decoder) {
avifDecoderDestroy(context->decoder);
context->decoder = NULL;
}

if (context->data) {
g_byte_array_unref(context->data);
context->bytes = NULL;
}

if (context->bytes) {
g_bytes_unref(context->bytes);
context->bytes = NULL;
}

if (context->pixbuf) {
g_object_unref(context->pixbuf);
context->pixbuf = NULL;
}

g_free(context);
}

static gboolean avif_context_try_load(struct avif_context * context, GError ** error)
{
avifResult ret;
avifDecoder * decoder = context->decoder;
avifImage * image;
avifRGBImage rgb;
avifROData raw;
int width, height;

raw.data = g_bytes_get_data(context->bytes, &raw.size);

ret = avifDecoderParse(decoder, &raw);
if (ret != AVIF_RESULT_OK) {
g_set_error(error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
"Couldn’t decode image: %s", avifResultToString(ret));
return FALSE;
}

if (decoder->imageCount > 1) {
g_set_error_literal(error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED,
"Image sequences not yet implemented");
return FALSE;
}

ret = avifDecoderNextImage(decoder);
if (ret == AVIF_RESULT_NO_IMAGES_REMAINING) {
// No more images, bail out. Verify that you got the expected amount of images decoded.
return TRUE;
} else if (ret != AVIF_RESULT_OK) {
g_set_error(error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED,
"Failed to decode all frames: %s", avifResultToString(ret));
return FALSE;
}

image = decoder->image;
width = image->width;
height = image->height;

(*context->size_func)(&width, &height, context->user_data);

if (width == 0 || height == 0) {
g_set_error_literal(error,
GDK_PIXBUF_ERROR,
GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
"Transformed AVIF has zero width or height");
return FALSE;
}

if (!context->pixbuf) {
int bits_per_sample = 8;

context->pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB,
!!image->alphaPlane, bits_per_sample,
width, height);
if (context->pixbuf == NULL) {
g_set_error_literal(error,
GDK_PIXBUF_ERROR,
GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
"Insufficient memory to open AVIF file");
return FALSE;
}
context->prepared_func(context->pixbuf, NULL, context->user_data);
}

avifRGBImageSetDefaults(&rgb, image);
rgb.depth = 8;
rgb.format = image->alphaPlane ? AVIF_RGB_FORMAT_RGBA : AVIF_RGB_FORMAT_RGB;
rgb.pixels = gdk_pixbuf_get_pixels(context->pixbuf);
rgb.rowBytes = gdk_pixbuf_get_rowstride(context->pixbuf);

ret = avifImageYUVToRGB(image, &rgb);
if (ret != AVIF_RESULT_OK) {
g_set_error(error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED,
"Failed to convert YUV to RGB: %s", avifResultToString(ret));
return FALSE;
}

return TRUE;
}

static gpointer begin_load(GdkPixbufModuleSizeFunc size_func,
GdkPixbufModulePreparedFunc prepared_func,
GdkPixbufModuleUpdatedFunc updated_func,
gpointer user_data, GError ** error)
{
struct avif_context * context;
avifDecoder * decoder;

g_assert(size_func != NULL);
g_assert(prepared_func != NULL);
g_assert(updated_func != NULL);

decoder = avifDecoderCreate();
if (!decoder) {
g_set_error_literal(error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
"Couldn’t allocate memory for decoder");
return NULL;
}

context = g_new0(struct avif_context, 1);
if (!context)
return NULL;

context->size_func = size_func;
context->updated_func = updated_func;
context->prepared_func = prepared_func;
context->user_data = user_data;

context->decoder = decoder;
context->data = g_byte_array_sized_new(40000);

return context;
}

static gboolean stop_load(gpointer data, GError ** error)
{
struct avif_context * context = (struct avif_context *) data;
gboolean ret;

context->bytes = g_byte_array_free_to_bytes(context->data);
context->data = NULL;
ret = avif_context_try_load(context, error);

avif_context_free(context);

return ret;
}

static gboolean load_increment(gpointer data, const guchar * buf, guint size, GError ** error)
{
struct avif_context * context = (struct avif_context *) data;
g_byte_array_append(context->data, buf, size);
*error = NULL;
return TRUE;
}

G_MODULE_EXPORT void fill_vtable(GdkPixbufModule * module)
{
module->begin_load = begin_load;
module->stop_load = stop_load;
module->load_increment = load_increment;
}

G_MODULE_EXPORT void fill_info(GdkPixbufFormat * info)
{
static const GdkPixbufModulePattern signature[] = {
{ " ftypavif", "zzz ", 100 }, /* file begins with 'ftypavif' at offset 4 */
{ NULL, NULL, 0 }
};
static const gchar * mime_types[] = {
"image/avif",
NULL
};
static const gchar * extensions[] = {
"avif",
NULL
};

info->name = "avif";
info->signature = (GdkPixbufModulePattern *)signature;
info->description = "AV1 Image File Format";
info->mime_types = (gchar **)mime_types;
info->extensions = (gchar **)extensions;
info->flags = GDK_PIXBUF_FORMAT_THREADSAFE;
info->license = "BSD";
info->disabled = FALSE;
}

0 comments on commit d5ea7ed

Please sign in to comment.