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 22, 2020
1 parent a2401e7 commit 16494e9
Show file tree
Hide file tree
Showing 3 changed files with 273 additions and 0 deletions.
26 changes: 26 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,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
contrib/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
27 changes: 27 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,30 @@ We are required to state that
"The Graphics Interchange Format(c) is the Copyright property of
CompuServe Incorporated. GIF(sm) is a Service Mark property of
CompuServe Incorporated."

------------------------------------------------------------------------------

Files: contrib/gdk-pixbuf/*

Copyright 2020 Emmanuel Gil Peyrot. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
220 changes: 220 additions & 0 deletions contrib/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 GdkPixbufModulePattern signature[] = {
{ " ftypavif", "zzz ", 100 }, /* file begins with 'ftypavif' at offset 4 */
{ NULL, NULL, 0 }
};
static gchar * mime_types[] = {
"image/avif",
NULL
};
static 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 16494e9

Please sign in to comment.