-
Notifications
You must be signed in to change notification settings - Fork 209
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
3 changed files
with
274 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
|