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 a gdk-pixbuf module #182

Merged
merged 1 commit into from
Jun 22, 2020
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
27 changes: 27 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -450,3 +450,30 @@ if(WIN32)
endif()

# ---------------------------------------------------------------------------------------
# Contrib modules, enhancing other software with AVIF support

option(AVIF_BUILD_GDK_PIXBUF "Build a gdk-pixbuf loader" OFF)
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()
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;
}