From ba149f17592cc47d15b26430e270a9626f137c4f Mon Sep 17 00:00:00 2001 From: Michael Natterer Date: Fri, 4 May 2018 20:30:30 +0200 Subject: [PATCH] plug-ins: add HEIF loading/saving plug-in written by Dirk Farin Thanks a lot to Dirk for contributing this, added him to AUTHORS. Import the code from https://github.com/strukturag/heif-gimp-plugin.git as of today. Merged the files into a single-file plug-in. Changed the code a lot to match our coding style, but only formatting, no logic changes. Still uses deprecated GimpDrawable API and no GIO, but I wanted to do actual code changes separately from the initial import. Also disabled metadata support because updating that to GimpMetadata was too much for the initial import. --- AUTHORS | 1 + authors.xml | 1 + configure.ac | 26 + plug-ins/common/.gitignore | 2 + plug-ins/common/Makefile.am | 23 + plug-ins/common/file-heif.c | 969 +++++++++++++++++++++++++++++++++ plug-ins/common/gimprc.common | 1 + plug-ins/common/plugin-defs.pl | 1 + po-plug-ins/POTFILES.in | 1 + 9 files changed, 1025 insertions(+) create mode 100644 plug-ins/common/file-heif.c diff --git a/AUTHORS b/AUTHORS index fd8aa0d3b9..cbe34ae0cd 100644 --- a/AUTHORS +++ b/AUTHORS @@ -76,6 +76,7 @@ The following people have contributed code to GIMP: Ell Morton Eriksen Larry Ewing + Dirk Farin Pedro Alonso Ferrer Nick Fetchak Piotr Filiciak diff --git a/authors.xml b/authors.xml index 040dda8932..4ae3efdfc7 100644 --- a/authors.xml +++ b/authors.xml @@ -84,6 +84,7 @@ Morton Eriksen Larry Ewing Alessandro Falappa + Dirk Farin Pedro Alonso Ferrer Nick Fetchak Piotr Filiciak diff --git a/configure.ac b/configure.ac index a4f258171a..38b0dab757 100644 --- a/configure.ac +++ b/configure.ac @@ -81,6 +81,7 @@ m4_define([intltool_required_version], [0.40.1]) m4_define([perl_required_version], [5.10.0]) m4_define([python2_required_version], [2.5.0]) m4_define([webp_required_version], [0.6.0]) +m4_define([libheif_required_version], [1.1.0]) # Current test considers only 2 version numbers. If we update the recommended # version of gettext with more version numbers, please update the tests. @@ -167,6 +168,7 @@ INTLTOOL_REQUIRED_VERSION=intltool_required_version PERL_REQUIRED_VERSION=perl_required_version PYTHON2_REQUIRED_VERSION=python2_required_version WEBP_REQUIRED_VERSION=webp_required_version +LIBHEIF_REQUIRED_VERSION=libheif_required_version XGETTEXT_RECOMMENDED_VERSION=xgettext_recommended_version AC_SUBST(GLIB_REQUIRED_VERSION) AC_SUBST(GDK_PIXBUF_REQUIRED_VERSION) @@ -197,6 +199,7 @@ AC_SUBST(INTLTOOL_REQUIRED_VERSION) AC_SUBST(PERL_REQUIRED_VERSION) AC_SUBST(PYTHON2_REQUIRED_VERSION) AC_SUBST(WEBP_REQUIRED_VERSION) +AC_SUBST(LIBHEIF_REQUIRED_VERSION) AC_SUBST(XGETTEXT_RECOMMENDED_VERSION) # The symbol GIMP_UNSTABLE is defined above for substitution in @@ -1664,6 +1667,28 @@ fi AM_CONDITIONAL(HAVE_WEBP, test "x$have_webp" = xyes) +################### +# Check for libheif +################### + +AC_ARG_WITH(libheif, [ --without-libheif build without libheif support]) + +have_libheif=no +if test "x$with_libheif" != xno; then + have_libheif=yes + PKG_CHECK_MODULES(LIBHEIF, libheif >= libheif_required_version, + FILE_HEIF='file-heif$(EXEEXT)', + [have_libheif="no (libheif not found)"]) +fi + +if test "x$have_libheif" = xyes; then + MIME_TYPES="$MIME_TYPES;image/heif;image/heic" +fi + +AC_SUBST(FILE_HEIF) +AM_CONDITIONAL(HAVE_LIBHEIF, test "x$have_libheif" = xyes) + + ###################### # Check for libmypaint ###################### @@ -2762,6 +2787,7 @@ Optional Plug-Ins: MNG: $have_libmng OpenEXR: $have_openexr WebP: $have_webp + Heif: $have_libheif PDF (export): $have_cairo_pdf Print: $enable_print Python 2: $enable_python diff --git a/plug-ins/common/.gitignore b/plug-ins/common/.gitignore index a5e50cf7fa..13a727efb6 100644 --- a/plug-ins/common/.gitignore +++ b/plug-ins/common/.gitignore @@ -78,6 +78,8 @@ /file-glob.exe /file-header /file-header.exe +/file-heif +/file-heif.exe /file-html-table /file-html-table.exe /file-jp2-load diff --git a/plug-ins/common/Makefile.am b/plug-ins/common/Makefile.am index ef8132314d..07762c9786 100644 --- a/plug-ins/common/Makefile.am +++ b/plug-ins/common/Makefile.am @@ -87,6 +87,7 @@ libexec_PROGRAMS = \ file-gih \ file-glob \ file-header \ + $(FILE_HEIF) \ file-html-table \ $(FILE_JP2_LOAD) \ $(FILE_MNG) \ @@ -144,6 +145,7 @@ libexec_PROGRAMS = \ EXTRA_PROGRAMS = \ file-aa \ + file-heif \ file-jp2-load \ file-mng \ file-pdf-save \ @@ -821,6 +823,27 @@ file_header_LDADD = \ $(INTLLIBS) \ $(file_header_RC) +file_heif_CFLAGS = $(LIBHEIF_CFLAGS) + +file_heif_SOURCES = \ + file-heif.c + +file_heif_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(LIBHEIF_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_heif_RC) + file_html_table_SOURCES = \ file-html-table.c diff --git a/plug-ins/common/file-heif.c b/plug-ins/common/file-heif.c new file mode 100644 index 0000000000..2da6e46dc7 --- /dev/null +++ b/plug-ins/common/file-heif.c @@ -0,0 +1,969 @@ +/* + * GIMP HEIF loader / write plugin. + * Copyright (c) 2018 struktur AG, Dirk Farin + * + * 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 3 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 "config.h" + +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define LOAD_PROC "file-heif-load" +#define SAVE_PROC "file-heif-save" +#define PLUG_IN_BINARY "file-heif" + + +typedef struct _SaveParams SaveParams; + +struct _SaveParams +{ + gint quality; + gboolean lossless; +}; + + +/* local function prototypes */ + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gint32 load_image (const gchar *filename, + gboolean interactive, + GError **error); +static gboolean save_image (const gchar *filename, + gint32 image_ID, + gint32 drawable_ID, + const SaveParams *params, + GError **error); + +static gboolean load_dialog (struct heif_context *heif, + uint32_t *selected_image); +static gboolean save_dialog (SaveParams *params); + + +GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +MAIN () + + +static void +query (void) +{ + static const GimpParamDef load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_STRING, "raw-filename", "The name entered" } + }; + + static const GimpParamDef load_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" } + }; + + + static const GimpParamDef save_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to export" }, + { GIMP_PDB_STRING, "filename", "The name of the file to export the image in" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to export the image in" }, + { GIMP_PDB_INT32, "quality", "Quality factor (range: 0-100. 0 = worst, 100 = best)" }, + { GIMP_PDB_INT32, "lossless", "Use lossless compression (0 = lossy, 1 = lossless)" } + }; + + gimp_install_procedure (LOAD_PROC, + _("Loads HEIF images"), + _("Load image stored in HEIF format (High " + "Efficiency Image File Format). Typical " + "suffices for HEIF files are .heif, .heic."), + "Dirk Farin ", + "Dirk Farin ", + "2018", + _("HEIF/HEIC"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_args), + G_N_ELEMENTS (load_return_vals), + load_args, load_return_vals); + + gimp_register_load_handler (LOAD_PROC, "heic,heif", ""); + gimp_register_file_handler_mime (LOAD_PROC, "image/heif"); + + gimp_install_procedure (SAVE_PROC, + _("Exports HEIF images"), + _("Save image in HEIF format (High Efficiency " + "Image File Format)."), + "Dirk Farin ", + "Dirk Farin ", + "2018", + _("HEIF/HEIC"), + "RGB*", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_register_save_handler (SAVE_PROC, "heic,heif", ""); + gimp_register_file_handler_mime (SAVE_PROC, "image/heif"); +} + +#define LOAD_HEIF_ERROR -1 +#define LOAD_HEIF_CANCEL -2 + +static void +run (const gchar *name, + gint n_params, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + GError *error = NULL; + + INIT_I18N (); + + run_mode = param[0].data.d_int32; + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (run_mode == GIMP_RUN_INTERACTIVE) + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + if (strcmp (name, LOAD_PROC) == 0) + { + const gchar *filename; + gboolean interactive; + + if (n_params != 3) + status = GIMP_PDB_CALLING_ERROR; + + filename = param[1].data.d_string; + interactive = (run_mode == GIMP_RUN_INTERACTIVE); + + if (status == GIMP_PDB_SUCCESS) + { + gint32 image_ID; + + image_ID = load_image (filename, interactive, &error); + + if (image_ID >= 0) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + } + else if (image_ID == LOAD_HEIF_CANCEL) + { + status = GIMP_PDB_CANCEL; + } + } + } + else if (strcmp(name, SAVE_PROC) == 0) + { + gint32 image_ID = param[1].data.d_int32; + gint32 drawable_ID = param[2].data.d_int32; + GimpExportReturn export = GIMP_EXPORT_CANCEL; + SaveParams params; + + params.lossless = FALSE; + params.quality = 50; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + export = gimp_export_image (&image_ID, &drawable_ID, "HEIF", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_ALPHA); + + if (export == GIMP_EXPORT_CANCEL) + { + values[0].data.d_status = GIMP_PDB_CANCEL; + return; + } + break; + + default: + break; + } + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + gimp_get_data (SAVE_PROC, ¶ms); + + if (! save_dialog (¶ms)) + status = GIMP_PDB_CANCEL; + break; + + case GIMP_RUN_WITH_LAST_VALS: + gimp_get_data (SAVE_PROC, ¶ms); + break; + + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (n_params != 7) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + params.quality = (param[5].data.d_int32); + params.lossless = (param[6].data.d_int32); + } + break; + } + + if (status == GIMP_PDB_SUCCESS) + { + + if (save_image (param[3].data.d_string, image_ID, drawable_ID, + ¶ms, + &error)) + { + gimp_set_data (SAVE_PROC, ¶ms, sizeof (params)); + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; +} + +gint32 +load_image (const gchar *filename, + gboolean interactive, + GError **error) +{ + struct heif_context *ctx; + struct heif_error err; + struct heif_image_handle *handle = NULL; + struct heif_image *img = NULL; + gint n_images; + heif_item_id primary; + heif_item_id selected_image; + gboolean has_alpha; + gint width; + gint height; + gint32 image_ID; + gint32 layer_ID; + GimpDrawable *drawable; + GimpPixelRgn rgn_out; + const guint8 *data; + gint stride; + gint bpp; + + ctx = heif_context_alloc (); + + err = heif_context_read_from_file (ctx, filename, NULL); + if (err.code) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Loading HEIF image failed: %s"), + err.message); + heif_context_free (ctx); + + return -1; + } + + /* analyze image content + * Is there more than one image? Which image is the primary image? + */ + + n_images = heif_context_get_number_of_top_level_images (ctx); + if (n_images == 0) + { + g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Loading HEIF image failed: " + "Input file contains no readable images")); + heif_context_free (ctx); + + return -1; + } + + err = heif_context_get_primary_image_ID (ctx, &primary); + if (err.code) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Loading HEIF image failed: %s"), + err.message); + heif_context_free (ctx); + + return -1; + } + + /* if primary image is no top level image or not present (invalid + * file), just take the first image + */ + + if (! heif_context_is_top_level_image_ID (ctx, primary)) + { + gint n = heif_context_get_list_of_top_level_image_IDs (ctx, &primary, 1); + g_assert (n == 1); + } + + selected_image = primary; + + /* if there are several images in the file and we are running + * interactive, let the user choose a picture + */ + + if (interactive && n_images > 1) + { + if (! load_dialog (ctx, &selected_image)) + { + heif_context_free (ctx); + + return LOAD_HEIF_CANCEL; + } + } + + /* load the picture */ + + err = heif_context_get_image_handle (ctx, selected_image, &handle); + if (err.code) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Loading HEIF image failed: %s"), + err.message); + heif_context_free (ctx); + + return -1; + } + + has_alpha = heif_image_handle_has_alpha_channel (handle); + + err = heif_decode_image (handle, + &img, + heif_colorspace_RGB, + has_alpha ? heif_chroma_interleaved_32bit : + heif_chroma_interleaved_24bit, + NULL); + if (err.code) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Loading HEIF image failed: %s"), + err.message); + heif_image_handle_release (handle); + heif_context_free (ctx); + + return -1; + } + + width = heif_image_get_width (img, heif_channel_interleaved); + height = heif_image_get_height (img, heif_channel_interleaved); + + /* create GIMP image and copy HEIF image into the GIMP image + * (converting it to RGB) + */ + + image_ID = gimp_image_new (width, height, GIMP_RGB); + gimp_image_set_filename (image_ID, filename); + + layer_ID = gimp_layer_new (image_ID, + _("image content"), + width, height, + has_alpha ? GIMP_RGBA_IMAGE : GIMP_RGB_IMAGE, + 100.0, + gimp_image_get_default_new_layer_mode (image_ID)); + + gimp_image_insert_layer (image_ID, layer_ID, -1, 0); + + drawable = gimp_drawable_get (layer_ID); + + gimp_pixel_rgn_init (&rgn_out, + drawable, + 0,0, + width, height, + TRUE, TRUE); + + data = heif_image_get_plane_readonly (img, heif_channel_interleaved, + &stride); + + bpp = heif_image_get_bits_per_pixel (img, heif_channel_interleaved) / 8; + + if (stride == width * bpp) + { + /* we can transfer the whole image at once */ + + gimp_pixel_rgn_set_rect (&rgn_out, + data, + 0, 0, width, height); + } + else + { + gint y; + + for (y = 0; y < height; y++) + { + /* stride has some padding, we have to send the image line by line */ + + gimp_pixel_rgn_set_row (&rgn_out, + data + y * stride, + 0, y, width); + } + } + + if (FALSE) + { + gint n_metadata; + heif_item_id metadata_id; + + n_metadata = + heif_image_handle_get_list_of_metadata_block_IDs (handle, + "Exif", + &metadata_id, 1); + + if (n_metadata > 0) + { + size_t data_size; + uint8_t *data; + const gint heif_exif_skip = 4; + + data_size = heif_image_handle_get_metadata_size (handle, + metadata_id); + data = g_alloca (data_size); + + err = heif_image_handle_get_metadata (handle, metadata_id, data); + + + gimp_image_attach_new_parasite (image_ID, + "exif-data", + 0, + data_size - heif_exif_skip, + data + heif_exif_skip); + } + } + + gimp_drawable_flush (drawable); + gimp_drawable_merge_shadow (drawable->drawable_id, TRUE); + gimp_drawable_update (drawable->drawable_id, + 0, 0, width, height); + + gimp_drawable_detach (drawable); + + heif_image_handle_release (handle); + heif_context_free (ctx); + heif_image_release (img); + + return image_ID; +} + +static gboolean +save_image (const gchar *filename, + gint32 image_ID, + gint32 drawable_ID, + const SaveParams *params, + GError **error) +{ + struct heif_image *image = NULL; + struct heif_context *context = heif_context_alloc (); + struct heif_encoder *encoder; + struct heif_image_handle *handle; + struct heif_error err; + guint8 *data; + gint stride; + GimpPixelRgn rgn_in; + GimpDrawable *drawable; + gint width; + gint height; + gboolean has_alpha; + gint y; + + width = gimp_drawable_width (drawable_ID); + height = gimp_drawable_height (drawable_ID); + + has_alpha = gimp_drawable_has_alpha (drawable_ID); + + err = heif_image_create (width, height, + heif_colorspace_RGB, + has_alpha ? + heif_chroma_interleaved_32bit : + heif_chroma_interleaved_24bit, + &image); + + heif_image_add_plane (image, heif_channel_interleaved, + width, height, has_alpha ? 32 : 24); + + data = heif_image_get_plane (image, heif_channel_interleaved, &stride); + + drawable = gimp_drawable_get (drawable_ID); + + gimp_pixel_rgn_init (&rgn_in, drawable, + 0, 0, width, height, FALSE, FALSE); + + for (y = 0; y < height; y++) + { + gimp_pixel_rgn_get_row (&rgn_in, + data + y * stride, 0, y, width); + } + + gimp_drawable_detach (drawable); + + /* encode to HEIF file */ + + context = heif_context_alloc (); + + err = heif_context_get_encoder_for_format (context, + heif_compression_HEVC, + &encoder); + + heif_encoder_set_lossy_quality (encoder, params->quality); + heif_encoder_set_lossless (encoder, params->lossless); + /* heif_encoder_set_logging_level (encoder, logging_level); */ + + err = heif_context_encode_image (context, + image, + encoder, + NULL, + &handle); + if (err.code != 0) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Encoding HEIF image failed: %s"), + err.message); + return FALSE; + } + + heif_image_handle_release (handle); + + err = heif_context_write_to_file (context, filename); + + if (err.code != 0) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Writing HEIF image failed: %s"), + err.message); + return FALSE; + } + + heif_context_free (context); + heif_image_release (image); + + heif_encoder_release (encoder); + + return TRUE; +} + + +/* the dialogs */ + +#define MAX_THUMBNAIL_SIZE 320 + +typedef struct _HeifImage HeifImage; + +struct _HeifImage +{ + uint32_t ID; + gchar caption[100]; + struct heif_image *thumbnail; + gint width; + gint height; +}; + +static gboolean +load_thumbnails (struct heif_context *heif, + HeifImage *images) +{ + guint32 *IDs; + gint n_images; + gint i; + + n_images = heif_context_get_number_of_top_level_images (heif); + + /* get list of all (top level) image IDs */ + + IDs = g_alloca (n_images * sizeof (guint32)); + + heif_context_get_list_of_top_level_image_IDs (heif, IDs, n_images); + + + /* Load a thumbnail for each image. */ + + for (i = 0; i < n_images; i++) + { + struct heif_image_handle *handle; + struct heif_error err; + gint width; + gint height; + struct heif_image_handle *thumbnail_handle; + heif_item_id thumbnail_ID; + gint n_thumbnails; + struct heif_image *thumbnail_img; + gint thumbnail_width; + gint thumbnail_height; + + images[i].caption[0] = 0; + images[i].thumbnail = NULL; + + /* get image handle */ + + err = heif_context_get_image_handle (heif, IDs[i], &handle); + if (err.code) + { + gimp_message (err.message); + continue; + } + + /* generate image caption */ + + width = heif_image_handle_get_width (handle); + height = heif_image_handle_get_height (handle); + + if (heif_image_handle_is_primary_image (handle)) + { + g_snprintf (images[i].caption, sizeof (images[i].caption), + "%dx%d (%s)", width, height, _("primary")); + } + else + { + g_snprintf (images[i].caption, sizeof (images[i].caption), + "%dx%d", width, height); + } + + /* get handle to thumbnail image + * + * if there is no thumbnail image, just the the image itself + * (will be scaled down later) + */ + + n_thumbnails = heif_image_handle_get_list_of_thumbnail_IDs (handle, + &thumbnail_ID, + 1); + + if (n_thumbnails > 0) + { + err = heif_image_handle_get_thumbnail (handle, thumbnail_ID, + &thumbnail_handle); + if (err.code) + { + gimp_message (err.message); + continue; + } + } + else + { + err = heif_context_get_image_handle (heif, IDs[i], &thumbnail_handle); + if (err.code) + { + gimp_message (err.message); + continue; + } + } + + /* decode the thumbnail image */ + + err = heif_decode_image (thumbnail_handle, + &thumbnail_img, + heif_colorspace_RGB, + heif_chroma_interleaved_24bit, + NULL); + if (err.code) + { + gimp_message (err.message); + continue; + } + + /* if thumbnail image size exceeds the maximum, scale it down */ + + thumbnail_width = heif_image_handle_get_width (thumbnail_handle); + thumbnail_height = heif_image_handle_get_height (thumbnail_handle); + + if (thumbnail_width > MAX_THUMBNAIL_SIZE || + thumbnail_height > MAX_THUMBNAIL_SIZE) + { + /* compute scaling factor to fit into a max sized box */ + + gfloat factor_h = thumbnail_width / (gfloat) MAX_THUMBNAIL_SIZE; + gfloat factor_v = thumbnail_height / (gfloat) MAX_THUMBNAIL_SIZE; + gint new_width, new_height; + struct heif_image *scaled_img = NULL; + + if (factor_v > factor_h) + { + new_height = MAX_THUMBNAIL_SIZE; + new_width = thumbnail_width / factor_v; + } + else + { + new_height = thumbnail_height / factor_h; + new_width = MAX_THUMBNAIL_SIZE; + } + + /* scale the image */ + + err = heif_image_scale_image (thumbnail_img, + &scaled_img, + new_width, new_height, + NULL); + if (err.code) + { + gimp_message (err.message); + continue; + } + + /* release the old image and only keep the scaled down version */ + + heif_image_release (thumbnail_img); + thumbnail_img = scaled_img; + + thumbnail_width = new_width; + thumbnail_height = new_height; + } + + heif_image_handle_release (thumbnail_handle); + heif_image_handle_release (handle); + + /* remember the HEIF thumbnail image (we need it for the GdkPixbuf) */ + + images[i].thumbnail = thumbnail_img; + + images[i].width = thumbnail_width; + images[i].height = thumbnail_height; + } + + return TRUE; +} + + +static gboolean +load_dialog (struct heif_context *heif, + uint32_t *selected_image) +{ + GtkWidget *dialog; + GtkWidget *main_vbox; + GtkWidget *frame; + HeifImage *heif_images; + GtkListStore *list_store; + GtkTreeIter iter; + GtkWidget *icon_view; + gint n_images; + gint i; + gint selected_idx = -1; + gboolean run = FALSE; + + n_images = heif_context_get_number_of_top_level_images (heif); + + heif_images = g_alloca (n_images * sizeof (HeifImage)); + + if (! load_thumbnails (heif, heif_images)) + return FALSE; + + dialog = gimp_dialog_new (_("Load HEIF Image"), PLUG_IN_BINARY, + NULL, 0, + gimp_standard_help_func, LOAD_PROC, + + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OK, GTK_RESPONSE_OK, + + NULL); + + main_vbox = gtk_vbox_new (FALSE, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + main_vbox, TRUE, TRUE, 0); + + frame = gimp_frame_new (_("Select Image")); + gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + /* prepare list store with all thumbnails and caption */ + + list_store = gtk_list_store_new (2, G_TYPE_STRING, GDK_TYPE_PIXBUF); + + for (i = 0; i < n_images; i++) + { + GdkPixbuf *pixbuf; + const guint8 *data; + gint stride; + + gtk_list_store_append (list_store, &iter); + gtk_list_store_set (list_store, &iter, 0, heif_images[i].caption, -1); + + data = heif_image_get_plane_readonly (heif_images[i].thumbnail, + heif_channel_interleaved, + &stride); + + pixbuf = gdk_pixbuf_new_from_data (data, + GDK_COLORSPACE_RGB, + FALSE, + 8, + heif_images[i].width, + heif_images[i].height, + stride, + NULL, + NULL); + + gtk_list_store_set (list_store, &iter, 1, pixbuf, -1); + } + + icon_view = gtk_icon_view_new (); + gtk_icon_view_set_model (GTK_ICON_VIEW (icon_view), + GTK_TREE_MODEL (list_store)); + gtk_icon_view_set_text_column (GTK_ICON_VIEW (icon_view), 0); + gtk_icon_view_set_pixbuf_column (GTK_ICON_VIEW (icon_view), 1); + gtk_container_add (GTK_CONTAINER (frame), icon_view); + gtk_widget_show (icon_view); + + /* pre-select the primary image */ + + for (i = 0; i < n_images; i++) + { + if (heif_images[i].ID == *selected_image) + { + selected_idx = i; + break; + } + } + + if (selected_idx != -1) + { + GtkTreePath *path = gtk_tree_path_new_from_indices (selected_idx, -1); + + gtk_icon_view_select_path (GTK_ICON_VIEW (icon_view), path); + gtk_tree_path_free (path); + } + + gtk_widget_show (main_vbox); + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + if (run) + { + GList *selected_items = + gtk_icon_view_get_selected_items (GTK_ICON_VIEW (icon_view)); + + if (selected_items) + { + GtkTreePath *path = selected_items->data; + gint *indices = gtk_tree_path_get_indices (path); + + *selected_image = heif_images[indices[0]].ID; + + g_list_free_full (selected_items, + (GDestroyNotify) gtk_tree_path_free); + } + } + + gtk_widget_destroy (dialog); + + /* release thumbnail images */ + + for (i = 0 ; i < n_images; i++) + heif_image_release (heif_images[i].thumbnail); + + return run; +} + +static void +lossless_button_toggled (GtkToggleButton *source, + GtkWidget *slider) +{ + gboolean lossless = gtk_toggle_button_get_active (source); + + gtk_widget_set_sensitive (slider, ! lossless); +} + +gboolean +save_dialog (SaveParams *params) +{ + GtkWidget *dialog; + GtkWidget *main_vbox; + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *lossless_button; + GtkWidget *quality_slider; + gboolean run = FALSE; + + dialog = gimp_export_dialog_new (_("HEIF"), PLUG_IN_BINARY, SAVE_PROC); + + main_vbox = gtk_vbox_new (FALSE, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)), + main_vbox, TRUE, TRUE, 0); + + lossless_button = gtk_check_button_new_with_label (_("Lossless")); + gtk_box_pack_start (GTK_BOX (main_vbox), lossless_button, FALSE, FALSE, 0); + + hbox = gtk_hbox_new (FALSE, 6); + label = gtk_label_new (_("Quality:")); + quality_slider = gtk_hscale_new_with_range (0, 100, 5); + gtk_scale_set_value_pos (GTK_SCALE(quality_slider), GTK_POS_RIGHT); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (hbox), quality_slider, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (main_vbox), hbox, TRUE, TRUE, 0); + + gtk_range_set_value (GTK_RANGE (quality_slider), params->quality); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(lossless_button), + params->lossless); + gtk_widget_set_sensitive (quality_slider, !params->lossless); + + g_signal_connect (lossless_button, "toggled", + G_CALLBACK (lossless_button_toggled), + quality_slider); + + gtk_widget_show_all (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + if (run) + { + params->quality = gtk_range_get_value (GTK_RANGE (quality_slider)); + params->lossless = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (lossless_button)); + } + + gtk_widget_destroy (dialog); + + return run; +} diff --git a/plug-ins/common/gimprc.common b/plug-ins/common/gimprc.common index 3cddb157c3..2c7307c4bb 100644 --- a/plug-ins/common/gimprc.common +++ b/plug-ins/common/gimprc.common @@ -36,6 +36,7 @@ file_gif_save_RC = file-gif-save.rc.o file_gih_RC = file-gih.rc.o file_glob_RC = file-glob.rc.o file_header_RC = file-header.rc.o +file_heif_RC = file-heif.rc.o file_html_table_RC = file-html-table.rc.o file_jp2_load_RC = file-jp2-load.rc.o file_mng_RC = file-mng.rc.o diff --git a/plug-ins/common/plugin-defs.pl b/plug-ins/common/plugin-defs.pl index 11a188596a..a8b2151d27 100644 --- a/plug-ins/common/plugin-defs.pl +++ b/plug-ins/common/plugin-defs.pl @@ -37,6 +37,7 @@ 'file-gih' => { ui => 1, gegl => 1 }, 'file-glob' => {}, 'file-header' => { ui => 1, gegl => 1 }, + 'file-heif' => { ui => 1, optional => 1, gegl => 1, libs => 'LIBHEIF_LIBS', cflags => 'LIBHEIF_CFLAGS' }, 'file-html-table' => { ui => 1, gegl => 1 }, 'file-jp2-load' => { ui => 1, optional => 1, gegl => 1, libs => 'OPENJPEG_LIBS', cflags => 'OPENJPEG_CFLAGS' }, 'file-mng' => { ui => 1, gegl => 1, optional => 1, libs => 'MNG_LIBS', cflags => 'MNG_CFLAGS' }, diff --git a/po-plug-ins/POTFILES.in b/po-plug-ins/POTFILES.in index 378c05ba4a..df491f5cb8 100644 --- a/po-plug-ins/POTFILES.in +++ b/po-plug-ins/POTFILES.in @@ -41,6 +41,7 @@ plug-ins/common/file-gif-save.c plug-ins/common/file-gih.c plug-ins/common/file-glob.c plug-ins/common/file-header.c +plug-ins/common/file-heif.c plug-ins/common/file-html-table.c plug-ins/common/file-jp2-load.c plug-ins/common/file-mng.c