blob: b197b2089b3a2e2eccdc988358a030b52d76ac67 [file] [log] [blame]
// 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;
}