/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Pix * * Copyright (C) 2021 Free Software Foundation, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include "gth-image-saver-avif.h" #include "preferences.h" #define THUMBNAIL_SIZE 256 #define GET_WIDGET(x) (_gtk_builder_get_widget (self->priv->builder, (x))) struct _GthImageSaverAvifPrivate { GtkBuilder *builder; GSettings *settings; }; G_DEFINE_TYPE_WITH_CODE (GthImageSaverAvif, gth_image_saver_avif, GTH_TYPE_IMAGE_SAVER, G_ADD_PRIVATE (GthImageSaverAvif)) static void gth_image_saver_avif_finalize (GObject *object) { GthImageSaverAvif *self = GTH_IMAGE_SAVER_AVIF (object); _g_object_unref (self->priv->settings); _g_object_unref (self->priv->builder); G_OBJECT_CLASS (gth_image_saver_avif_parent_class)->finalize (object); } static GtkWidget * gth_image_saver_avif_get_control (GthImageSaver *base) { GthImageSaverAvif *self = GTH_IMAGE_SAVER_AVIF (base); _g_object_unref (self->priv->builder); self->priv->builder = _gtk_builder_new_from_file ("avif-options.ui", "cairo_io"); gtk_adjustment_set_value (GTK_ADJUSTMENT (GET_WIDGET ("quality_adjustment")), g_settings_get_int (self->priv->settings, PREF_WEBP_QUALITY)); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("lossless_checkbutton")), g_settings_get_boolean (self->priv->settings, PREF_WEBP_LOSSLESS)); return GET_WIDGET ("avif_options"); } static void gth_image_saver_avif_save_options (GthImageSaver *base) { GthImageSaverAvif *self = GTH_IMAGE_SAVER_AVIF (base); g_settings_set_int (self->priv->settings, PREF_AVIF_QUALITY, (int) gtk_adjustment_get_value (GTK_ADJUSTMENT (GET_WIDGET ("quality_adjustment")))); g_settings_set_boolean (self->priv->settings, PREF_AVIF_LOSSLESS, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("lossless_checkbutton")))); } static gboolean gth_image_saver_avif_can_save (GthImageSaver *self, const char *mime_type) { return g_content_type_equals (mime_type, "image/avif"); } typedef struct { GthBufferData *buffer_data; GError **error; struct heif_error err; } WriterData; static struct heif_error write_fn (struct heif_context *ctx, const void *data, size_t size, void *userdata) { WriterData *writer_data = userdata; writer_data->err.code = gth_buffer_data_write (writer_data->buffer_data, data, size, writer_data->error) ? heif_error_Ok : heif_error_Encoding_error; writer_data->err.subcode = heif_suberror_Unspecified; writer_data->err.message = ""; return writer_data->err; } static gboolean _cairo_surface_write_as_avif (cairo_surface_t *surface, char **buffer, gsize *buffer_size, char **keys, char **values, GError **error) { gboolean success; gboolean lossless; int quality; int rows, columns; int in_stride; gboolean has_alpha; guchar *pixels; struct heif_context *ctx = NULL; struct heif_encoder *encoder = NULL; struct heif_image *image = NULL; struct heif_error err; uint8_t *plane = NULL; int out_stride; struct heif_image_handle *handle = NULL; struct heif_writer writer; WriterData writer_data; success = FALSE; lossless = TRUE; quality = 50; if (keys && *keys) { char **kiter = keys; char **viter = values; while (*kiter) { if (strcmp (*kiter, "lossless") == 0) { if (*viter == NULL) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Must specify a value for the 'lossless' option."); return FALSE; } lossless = atoi (*viter); if (lossless < 0 || lossless > 1) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Invalid value set for the 'lossless' option."); return FALSE; } } else if (strcmp (*kiter, "quality") == 0) { if (*viter == NULL) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Must specify a quality value."); return FALSE; } quality = atoi (*viter); if (quality < 0 || quality > 100) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Unsupported quality value passed."); return FALSE; } } else { g_warning ("Bad option name '%s' passed to the HEIF/AVIF saver.", *kiter); return FALSE; } ++kiter; ++viter; } } columns = cairo_image_surface_get_width (surface); rows = cairo_image_surface_get_height (surface); in_stride = cairo_image_surface_get_stride (surface); has_alpha = _cairo_image_surface_get_has_alpha (surface); pixels = _cairo_image_surface_flush_and_get_data (surface); ctx = heif_context_alloc (); heif_context_get_encoder_for_format (ctx, heif_compression_AV1, &encoder); heif_encoder_set_lossless (encoder, lossless); heif_encoder_set_lossy_quality (encoder, quality); err = heif_image_create (columns, rows, heif_colorspace_RGB, has_alpha ? heif_chroma_interleaved_RGBA : heif_chroma_interleaved_RGB, &image); if (err.code != heif_error_Ok) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Could not create the image: %s", err.message); goto cleanup; } err = heif_image_add_plane (image, heif_channel_interleaved, columns, rows, 8); if (err.code != heif_error_Ok) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Could not add plane to the image: %s", err.message); goto cleanup; } plane = heif_image_get_plane (image, heif_channel_interleaved, &out_stride); while (rows > 0) { _cairo_copy_line_as_rgba_big_endian (plane, pixels, columns, has_alpha); plane += out_stride; pixels += in_stride; rows -= 1; } err = heif_context_encode_image (ctx, image, encoder, NULL, &handle); if (err.code != heif_error_Ok) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Could not encode the image: %s", err.message); goto cleanup; } #if GENERATE_THUMBNAIL struct heif_image_handle *thumbnail_handle = NULL; err = heif_context_encode_thumbnail (ctx, image, handle, encoder, NULL, THUMBNAIL_SIZE, &thumbnail_handle); if (thumbnail_handle != NULL) heif_image_handle_release (thumbnail_handle); if (err.code != heif_error_Ok) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Could not generate thumbnail: %s", err.message); goto cleanup; } #endif heif_image_handle_release (handle); handle = NULL; heif_encoder_release (encoder); encoder = NULL; writer.writer_api_version = 1; writer.write = write_fn; writer_data.buffer_data = gth_buffer_data_new (); writer_data.error = error; err = heif_context_write (ctx, &writer, &writer_data); success = (err.code == heif_error_Ok); if (success) gth_buffer_data_get (writer_data.buffer_data, buffer, buffer_size); gth_buffer_data_free (writer_data.buffer_data, ! success); cleanup: if (handle != NULL) heif_image_handle_release (handle); if (image != NULL) heif_image_release (image); if (encoder != NULL) heif_encoder_release (encoder); if (ctx != NULL) heif_context_free (ctx); return success; } static gboolean gth_image_saver_avif_save_image (GthImageSaver *base, GthImage *image, char **buffer, gsize *buffer_size, const char *mime_type, GCancellable *cancellable, GError **error) { GthImageSaverAvif *self = GTH_IMAGE_SAVER_AVIF (base); char **option_keys; char **option_values; int i = -1; int i_value; cairo_surface_t *surface; gboolean result; option_keys = g_malloc (sizeof (char *) * 5); option_values = g_malloc (sizeof (char *) * 5); i++; i_value = g_settings_get_boolean (self->priv->settings, PREF_AVIF_LOSSLESS); option_keys[i] = g_strdup ("lossless");; option_values[i] = g_strdup_printf ("%d", i_value); i++; i_value = g_settings_get_int (self->priv->settings, PREF_AVIF_QUALITY); option_keys[i] = g_strdup ("quality");; option_values[i] = g_strdup_printf ("%d", i_value); i++; option_keys[i] = NULL; option_values[i] = NULL; surface = gth_image_get_cairo_surface (image); result = _cairo_surface_write_as_avif (surface, buffer, buffer_size, option_keys, option_values, error); cairo_surface_destroy (surface); g_strfreev (option_keys); g_strfreev (option_values); return result; } static void gth_image_saver_avif_class_init (GthImageSaverAvifClass *klass) { GObjectClass *object_class; GthImageSaverClass *image_saver_class; object_class = G_OBJECT_CLASS (klass); object_class->finalize = gth_image_saver_avif_finalize; image_saver_class = GTH_IMAGE_SAVER_CLASS (klass); image_saver_class->id = "avif"; image_saver_class->display_name = _("AVIF"); image_saver_class->mime_type = "image/avif"; image_saver_class->extensions = "avif"; image_saver_class->get_control = gth_image_saver_avif_get_control; image_saver_class->save_options = gth_image_saver_avif_save_options; image_saver_class->can_save = gth_image_saver_avif_can_save; image_saver_class->save_image = gth_image_saver_avif_save_image; } static void gth_image_saver_avif_init (GthImageSaverAvif *self) { self->priv = gth_image_saver_avif_get_instance_private (self); self->priv->settings = g_settings_new (PIX_IMAGE_SAVERS_AVIF_SCHEMA); self->priv->builder = NULL; }