gdk-pixbuf/pixbufloader-heif.c (170 lines of code) (raw):
/*
* gdk-pixbuf loader module for libheif
* Copyright (c) 2019 Oliver Giles <ohw.giles@gmail.com>
*
* This file is part of libheif.
*
* libheif is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* libheif is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libheif. If not, see <http://www.gnu.org/licenses/>.
*/
#define GDK_PIXBUF_ENABLE_BACKEND
#include <gdk-pixbuf/gdk-pixbuf-io.h>
#include <libheif/heif.h>
G_MODULE_EXPORT void fill_vtable(GdkPixbufModule* module);
G_MODULE_EXPORT void fill_info(GdkPixbufFormat* info);
typedef struct
{
GdkPixbufModuleUpdatedFunc update_func;
GdkPixbufModulePreparedFunc prepare_func;
GdkPixbufModuleSizeFunc size_func;
gpointer user_data;
GByteArray* data;
} HeifPixbufCtx;
static gpointer begin_load(GdkPixbufModuleSizeFunc size_func,
GdkPixbufModulePreparedFunc prepare_func,
GdkPixbufModuleUpdatedFunc update_func,
gpointer user_data,
GError** error)
{
HeifPixbufCtx* hpc;
hpc = g_new0(HeifPixbufCtx, 1);
hpc->data = g_byte_array_new();
hpc->size_func = size_func;
hpc->prepare_func = prepare_func;
hpc->update_func = update_func;
hpc->user_data = user_data;
return hpc;
}
static void release_heif_image(guchar* pixels, gpointer data)
{
heif_image_release((struct heif_image*) data);
}
static gboolean stop_load(gpointer context, GError** error)
{
HeifPixbufCtx* hpc;
struct heif_error err;
struct heif_context* hc = NULL;
struct heif_image_handle* hdl = NULL;
struct heif_image* img = NULL;
int width, height, stride;
int requested_width, requested_height;
const uint8_t* data;
GdkPixbuf* pixbuf;
gboolean result;
result = FALSE;
hpc = (HeifPixbufCtx*) context;
err = heif_init(NULL);
if (err.code != heif_error_Ok) {
g_warning("%s", err.message);
goto cleanup;
}
hc = heif_context_alloc();
if (!hc) {
g_warning("cannot allocate heif_context");
goto cleanup;
}
err = heif_context_read_from_memory_without_copy(hc, hpc->data->data, hpc->data->len, NULL);
if (err.code != heif_error_Ok) {
g_warning("%s", err.message);
goto cleanup;
}
err = heif_context_get_primary_image_handle(hc, &hdl);
if (err.code != heif_error_Ok) {
g_warning("%s", err.message);
goto cleanup;
}
int has_alpha = heif_image_handle_has_alpha_channel(hdl);
err = heif_decode_image(hdl, &img, heif_colorspace_RGB,
has_alpha ? heif_chroma_interleaved_RGBA : heif_chroma_interleaved_RGB,
NULL);
if (err.code != heif_error_Ok) {
g_warning("%s", err.message);
goto cleanup;
}
width = heif_image_get_width(img, heif_channel_interleaved);
height = heif_image_get_height(img, heif_channel_interleaved);
requested_width = width;
requested_height = height;
if (hpc->size_func) {
(*hpc->size_func)(&requested_width, &requested_height, hpc->user_data);
}
if (requested_width > 0 && requested_height > 0 && (width != requested_width || height != requested_height)) {
struct heif_image* resized;
heif_image_scale_image(img, &resized, requested_width, requested_height, NULL);
heif_image_release(img);
width = requested_width;
height = requested_height;
img = resized;
}
data = heif_image_get_plane_readonly(img, heif_channel_interleaved, &stride);
pixbuf = gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, has_alpha, 8, width, height, stride, release_heif_image,
img);
size_t profile_size = heif_image_handle_get_raw_color_profile_size(hdl);
if(profile_size) {
guchar *profile_data = (guchar *)g_malloc0(profile_size);
err = heif_image_handle_get_raw_color_profile(hdl, profile_data);
if (err.code == heif_error_Ok) {
gchar *profile_base64 = g_base64_encode(profile_data, profile_size);
gdk_pixbuf_set_option(pixbuf, "icc-profile", profile_base64);
g_free(profile_base64);
}
else {
// Having no ICC profile is perfectly fine. Do not show any warning because of that.
}
g_free(profile_data);
}
if (hpc->prepare_func) {
(*hpc->prepare_func)(pixbuf, NULL, hpc->user_data);
}
if (hpc->update_func != NULL) {
(*hpc->update_func)(pixbuf, 0, 0, gdk_pixbuf_get_width(pixbuf), gdk_pixbuf_get_height(pixbuf), hpc->user_data);
}
g_clear_object(&pixbuf);
result = TRUE;
cleanup:
if (img) {
// Do not free the image here when we pass it to gdk-pixbuf, as its memory will still be used by gdk-pixbuf.
if (!result) {
heif_image_release(img);
}
}
if (hdl) {
heif_image_handle_release(hdl);
}
if (hc) {
heif_context_free(hc);
}
g_byte_array_free(hpc->data, TRUE);
g_free(hpc);
heif_deinit();
return result;
}
static gboolean load_increment(gpointer context, const guchar* buf, guint size, GError** error)
{
HeifPixbufCtx* ctx = (HeifPixbufCtx*) context;
g_byte_array_append(ctx->data, buf, size);
return TRUE;
}
void fill_vtable(GdkPixbufModule* module)
{
module->begin_load = begin_load;
module->stop_load = stop_load;
module->load_increment = load_increment;
}
void fill_info(GdkPixbufFormat* info)
{
static GdkPixbufModulePattern signature[] = {
{" ftyp", "xxxx ", 100},
{NULL, NULL, 0}
};
static gchar* mime_types[] = {
"image/heif",
"image/heic",
"image/avif",
NULL
};
static gchar* extensions[] = {
"heif",
"heic",
"avif",
NULL
};
info->name = "heif/avif";
info->signature = signature;
info->domain = "pixbufloader-heif";
info->description = "HEIF/AVIF Image";
info->mime_types = mime_types;
info->extensions = extensions;
info->flags = GDK_PIXBUF_FORMAT_THREADSAFE;
info->disabled = FALSE;
info->license = "LGPL3";
}