2006-12-09 22:12:23 +00:00
|
|
|
/* GIMP - The GNU Image Manipulation Program
|
|
|
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
1997-11-24 22:05:25 +00:00
|
|
|
*
|
2006-12-09 22:12:23 +00:00
|
|
|
* Portable Network Graphics (PNG) plug-in
|
1997-11-24 22:05:25 +00:00
|
|
|
*
|
1998-05-31 06:49:20 +00:00
|
|
|
* Copyright 1997-1998 Michael Sweet (mike@easysw.com) and
|
1997-11-24 22:05:25 +00:00
|
|
|
* Daniel Skarda (0rfelyus@atrey.karlin.mff.cuni.cz).
|
2000-04-14 15:21:14 +00:00
|
|
|
* and 1999-2000 Nick Lamb (njl195@zepler.org.uk)
|
1997-11-24 22:05:25 +00:00
|
|
|
*
|
2009-01-17 22:28:01 +00:00
|
|
|
* This program is free software: you can redistribute it and/or modify
|
1997-11-24 22:05:25 +00:00
|
|
|
* it under the terms of the GNU General Public License as published by
|
2009-01-17 22:28:01 +00:00
|
|
|
* the Free Software Foundation; either version 3 of the License, or
|
1997-11-24 22:05:25 +00:00
|
|
|
* (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
|
2018-07-11 23:27:07 +02:00
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
1997-11-24 22:05:25 +00:00
|
|
|
*/
|
|
|
|
|
2000-01-08 15:23:28 +00:00
|
|
|
#include "config.h"
|
|
|
|
|
1997-11-24 22:05:25 +00:00
|
|
|
#include <stdlib.h>
|
2005-03-04 15:12:29 +00:00
|
|
|
#include <errno.h>
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2005-03-04 15:12:29 +00:00
|
|
|
#include <glib/gstdio.h>
|
2021-01-17 15:28:08 +01:00
|
|
|
#include "lcms2.h"
|
2000-01-08 15:23:28 +00:00
|
|
|
|
1997-11-24 22:05:25 +00:00
|
|
|
#include <libgimp/gimp.h>
|
1999-10-03 18:54:54 +00:00
|
|
|
#include <libgimp/gimpui.h>
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2019-09-25 01:06:16 +02:00
|
|
|
#include <png.h>
|
2000-01-25 17:46:56 +00:00
|
|
|
|
1999-05-29 16:35:47 +00:00
|
|
|
#include "libgimp/stdplugins-intl.h"
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2000-05-01 17:01:18 +00:00
|
|
|
|
2019-09-25 01:06:16 +02:00
|
|
|
#define LOAD_PROC "file-png-load"
|
2024-04-13 15:10:25 +00:00
|
|
|
#define EXPORT_PROC "file-png-export"
|
2019-09-25 01:06:16 +02:00
|
|
|
#define PLUG_IN_BINARY "file-png"
|
|
|
|
#define PLUG_IN_ROLE "gimp-file-png"
|
2005-08-15 11:07:27 +00:00
|
|
|
|
2019-09-25 01:06:16 +02:00
|
|
|
#define PLUG_IN_VERSION "1.3.4 - 03 September 2002"
|
|
|
|
#define SCALE_WIDTH 125
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2019-09-25 01:06:16 +02:00
|
|
|
#define DEFAULT_GAMMA 2.20
|
2004-01-06 09:53:35 +00:00
|
|
|
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2019-09-25 01:06:16 +02:00
|
|
|
typedef enum _PngExportformat
|
|
|
|
{
|
2016-12-30 14:07:05 +01:00
|
|
|
PNG_FORMAT_AUTO = 0,
|
|
|
|
PNG_FORMAT_RGB8,
|
|
|
|
PNG_FORMAT_GRAY8,
|
|
|
|
PNG_FORMAT_RGBA8,
|
|
|
|
PNG_FORMAT_GRAYA8,
|
|
|
|
PNG_FORMAT_RGB16,
|
|
|
|
PNG_FORMAT_GRAY16,
|
|
|
|
PNG_FORMAT_RGBA16,
|
|
|
|
PNG_FORMAT_GRAYA16
|
|
|
|
} PngExportFormat;
|
|
|
|
|
2023-11-08 17:27:13 +00:00
|
|
|
static GSList *safe_to_copy_chunks;
|
2013-10-19 18:38:01 +02:00
|
|
|
|
2019-08-16 21:49:19 +02:00
|
|
|
typedef struct _Png Png;
|
|
|
|
typedef struct _PngClass PngClass;
|
|
|
|
|
|
|
|
struct _Png
|
|
|
|
{
|
|
|
|
GimpPlugIn parent_instance;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct _PngClass
|
|
|
|
{
|
|
|
|
GimpPlugInClass parent_class;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2023-10-18 18:29:37 +02:00
|
|
|
#define PNG_TYPE (png_get_type ())
|
|
|
|
#define PNG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PNG_TYPE, Png))
|
2019-08-16 21:49:19 +02:00
|
|
|
|
|
|
|
GType png_get_type (void) G_GNUC_CONST;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2023-08-06 00:59:19 +02:00
|
|
|
static GList * png_query_procedures (GimpPlugIn *plug_in);
|
|
|
|
static GimpProcedure * png_create_procedure (GimpPlugIn *plug_in,
|
|
|
|
const gchar *name);
|
|
|
|
|
|
|
|
static GimpValueArray * png_load (GimpProcedure *procedure,
|
|
|
|
GimpRunMode run_mode,
|
|
|
|
GFile *file,
|
|
|
|
GimpMetadata *metadata,
|
|
|
|
GimpMetadataLoadFlags *flags,
|
|
|
|
GimpProcedureConfig *config,
|
|
|
|
gpointer run_data);
|
2024-04-13 15:10:25 +00:00
|
|
|
static GimpValueArray * png_export (GimpProcedure *procedure,
|
2023-08-06 00:59:19 +02:00
|
|
|
GimpRunMode run_mode,
|
|
|
|
GimpImage *image,
|
|
|
|
gint n_drawables,
|
|
|
|
GimpDrawable **drawables,
|
|
|
|
GFile *file,
|
|
|
|
GimpMetadata *metadata,
|
|
|
|
GimpProcedureConfig *config,
|
|
|
|
gpointer run_data);
|
|
|
|
|
|
|
|
static GimpImage * load_image (GFile *file,
|
|
|
|
gboolean report_progress,
|
|
|
|
gboolean *resolution_loaded,
|
|
|
|
gboolean *profile_loaded,
|
|
|
|
GError **error);
|
2024-04-13 15:10:25 +00:00
|
|
|
static gboolean export_image (GFile *file,
|
2023-08-06 00:59:19 +02:00
|
|
|
GimpImage *image,
|
|
|
|
GimpDrawable *drawable,
|
|
|
|
GimpImage *orig_image,
|
|
|
|
GObject *config,
|
|
|
|
gint *bits_per_sample,
|
|
|
|
gboolean report_progress,
|
|
|
|
GError **error);
|
|
|
|
|
|
|
|
static int respin_cmap (png_structp pp,
|
|
|
|
png_infop info,
|
|
|
|
guchar *remap,
|
|
|
|
GimpImage *image,
|
|
|
|
GimpDrawable *drawable);
|
|
|
|
|
2024-04-13 15:10:25 +00:00
|
|
|
static gboolean export_dialog (GimpImage *image,
|
2023-08-06 00:59:19 +02:00
|
|
|
GimpProcedure *procedure,
|
|
|
|
GObject *config,
|
|
|
|
gboolean alpha);
|
|
|
|
|
|
|
|
static gboolean offsets_dialog (gint offset_x,
|
|
|
|
gint offset_y);
|
|
|
|
|
|
|
|
static gboolean ia_has_transparent_pixels (GeglBuffer *buffer);
|
|
|
|
|
|
|
|
static gint find_unused_ia_color (GeglBuffer *buffer,
|
|
|
|
gint *colors);
|
2004-01-06 09:53:35 +00:00
|
|
|
|
2023-11-08 17:27:13 +00:00
|
|
|
static gint read_unknown_chunk (png_structp png_ptr,
|
|
|
|
png_unknown_chunkp chunk);
|
|
|
|
|
2013-10-19 18:38:01 +02:00
|
|
|
|
2019-08-16 21:49:19 +02:00
|
|
|
G_DEFINE_TYPE (Png, png, GIMP_TYPE_PLUG_IN)
|
|
|
|
|
|
|
|
GIMP_MAIN (PNG_TYPE)
|
2022-05-26 00:59:36 +02:00
|
|
|
DEFINE_STD_SET_I18N
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
|
2019-08-16 21:49:19 +02:00
|
|
|
static void
|
|
|
|
png_class_init (PngClass *klass)
|
|
|
|
{
|
|
|
|
GimpPlugInClass *plug_in_class = GIMP_PLUG_IN_CLASS (klass);
|
2002-09-10 16:46:41 +00:00
|
|
|
|
2019-08-16 21:49:19 +02:00
|
|
|
plug_in_class->query_procedures = png_query_procedures;
|
|
|
|
plug_in_class->create_procedure = png_create_procedure;
|
2022-05-26 00:59:36 +02:00
|
|
|
plug_in_class->set_i18n = STD_SET_I18N;
|
2019-08-16 21:49:19 +02:00
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
static void
|
2019-08-16 21:49:19 +02:00
|
|
|
png_init (Png *png)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2019-08-16 21:49:19 +02:00
|
|
|
static GList *
|
|
|
|
png_query_procedures (GimpPlugIn *plug_in)
|
|
|
|
{
|
|
|
|
GList *list = NULL;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2019-08-16 21:49:19 +02:00
|
|
|
list = g_list_append (list, g_strdup (LOAD_PROC));
|
2024-04-13 15:10:25 +00:00
|
|
|
list = g_list_append (list, g_strdup (EXPORT_PROC));
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2019-08-16 21:49:19 +02:00
|
|
|
return list;
|
|
|
|
}
|
|
|
|
|
|
|
|
static GimpProcedure *
|
|
|
|
png_create_procedure (GimpPlugIn *plug_in,
|
|
|
|
const gchar *name)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
2019-08-16 21:49:19 +02:00
|
|
|
GimpProcedure *procedure = NULL;
|
|
|
|
|
|
|
|
if (! strcmp (name, LOAD_PROC))
|
|
|
|
{
|
2023-08-06 03:21:27 +02:00
|
|
|
procedure = gimp_load_procedure_new (plug_in, name,
|
|
|
|
GIMP_PDB_PROC_TYPE_PLUGIN,
|
|
|
|
png_load, NULL, NULL);
|
2019-08-16 21:49:19 +02:00
|
|
|
|
2022-07-04 22:50:53 +02:00
|
|
|
gimp_procedure_set_menu_label (procedure, _("PNG image"));
|
2019-08-16 21:49:19 +02:00
|
|
|
|
|
|
|
gimp_procedure_set_documentation (procedure,
|
|
|
|
"Loads files in PNG file format",
|
|
|
|
"This plug-in loads Portable Network "
|
|
|
|
"Graphics (PNG) files.",
|
|
|
|
name);
|
|
|
|
gimp_procedure_set_attribution (procedure,
|
|
|
|
"Michael Sweet <mike@easysw.com>, "
|
|
|
|
"Daniel Skarda <0rfelyus@atrey.karlin.mff.cuni.cz>",
|
|
|
|
"Michael Sweet <mike@easysw.com>, "
|
|
|
|
"Daniel Skarda <0rfelyus@atrey.karlin.mff.cuni.cz>, "
|
|
|
|
"Nick Lamb <njl195@zepler.org.uk>",
|
|
|
|
PLUG_IN_VERSION);
|
|
|
|
|
|
|
|
gimp_file_procedure_set_mime_types (GIMP_FILE_PROCEDURE (procedure),
|
|
|
|
"image/png");
|
|
|
|
gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure),
|
|
|
|
"png");
|
|
|
|
gimp_file_procedure_set_magics (GIMP_FILE_PROCEDURE (procedure),
|
|
|
|
"0,string,\211PNG\r\n\032\n");
|
|
|
|
}
|
2024-04-13 15:10:25 +00:00
|
|
|
else if (! strcmp (name, EXPORT_PROC))
|
2019-08-16 21:49:19 +02:00
|
|
|
{
|
2024-04-20 03:08:57 +00:00
|
|
|
procedure = gimp_export_procedure_new (plug_in, name,
|
|
|
|
GIMP_PDB_PROC_TYPE_PLUGIN,
|
|
|
|
TRUE, png_export, NULL, NULL);
|
2019-08-16 21:49:19 +02:00
|
|
|
|
|
|
|
gimp_procedure_set_image_types (procedure, "*");
|
|
|
|
|
2022-07-04 22:50:53 +02:00
|
|
|
gimp_procedure_set_menu_label (procedure, _("PNG image"));
|
2019-08-16 21:49:19 +02:00
|
|
|
|
|
|
|
gimp_procedure_set_documentation (procedure,
|
|
|
|
"Exports files in PNG file format",
|
|
|
|
"This plug-in exports Portable Network "
|
|
|
|
"Graphics (PNG) files.",
|
|
|
|
name);
|
|
|
|
gimp_procedure_set_attribution (procedure,
|
|
|
|
"Michael Sweet <mike@easysw.com>, "
|
|
|
|
"Daniel Skarda <0rfelyus@atrey.karlin.mff.cuni.cz>",
|
|
|
|
"Michael Sweet <mike@easysw.com>, "
|
|
|
|
"Daniel Skarda <0rfelyus@atrey.karlin.mff.cuni.cz>, "
|
|
|
|
"Nick Lamb <njl195@zepler.org.uk>",
|
|
|
|
PLUG_IN_VERSION);
|
|
|
|
|
2020-12-17 02:02:14 +01:00
|
|
|
gimp_file_procedure_set_format_name (GIMP_FILE_PROCEDURE (procedure),
|
|
|
|
_("PNG"));
|
2019-08-16 21:49:19 +02:00
|
|
|
gimp_file_procedure_set_mime_types (GIMP_FILE_PROCEDURE (procedure),
|
|
|
|
"image/png");
|
|
|
|
gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure),
|
|
|
|
"png");
|
|
|
|
|
2019-09-25 01:06:16 +02:00
|
|
|
GIMP_PROC_ARG_BOOLEAN (procedure, "interlaced",
|
2022-04-26 23:35:41 +02:00
|
|
|
_("_Interlacing (Adam7)"),
|
|
|
|
_("Use Adam7 interlacing"),
|
2019-08-19 10:02:07 +02:00
|
|
|
FALSE,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
|
|
|
GIMP_PROC_ARG_INT (procedure, "compression",
|
2022-04-26 23:35:41 +02:00
|
|
|
_("Co_mpression level"),
|
|
|
|
_("Deflate Compression factor (0..9)"),
|
2019-08-19 10:02:07 +02:00
|
|
|
0, 9, 9,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
|
|
|
GIMP_PROC_ARG_BOOLEAN (procedure, "bkgd",
|
2022-04-26 23:35:41 +02:00
|
|
|
_("Save _background color"),
|
|
|
|
_("Write bKGD chunk (PNG metadata)"),
|
2019-08-19 10:02:07 +02:00
|
|
|
TRUE,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
|
|
|
GIMP_PROC_ARG_BOOLEAN (procedure, "offs",
|
2022-04-26 23:35:41 +02:00
|
|
|
_("Save layer o_ffset"),
|
|
|
|
_("Write oFFs chunk (PNG metadata)"),
|
2019-08-19 10:02:07 +02:00
|
|
|
FALSE,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
|
|
|
GIMP_PROC_ARG_BOOLEAN (procedure, "phys",
|
2022-04-26 23:35:41 +02:00
|
|
|
_("Save resol_ution"),
|
|
|
|
_("Write pHYs chunk (PNG metadata)"),
|
2019-08-19 10:02:07 +02:00
|
|
|
TRUE,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
|
|
|
GIMP_PROC_ARG_BOOLEAN (procedure, "time",
|
2022-04-26 23:35:41 +02:00
|
|
|
_("Save creation _time"),
|
|
|
|
_("Write tIME chunk (PNG metadata)"),
|
2019-08-19 10:02:07 +02:00
|
|
|
TRUE,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
2019-09-25 01:06:16 +02:00
|
|
|
GIMP_PROC_ARG_BOOLEAN (procedure, "save-transparent",
|
2022-04-26 23:35:41 +02:00
|
|
|
_("Save color _values from transparent pixels"),
|
|
|
|
_("Preserve color of completely transparent pixels"),
|
2020-04-03 19:40:45 +02:00
|
|
|
FALSE,
|
2019-08-19 10:02:07 +02:00
|
|
|
G_PARAM_READWRITE);
|
2019-09-25 01:06:16 +02:00
|
|
|
|
2022-08-17 18:05:13 +00:00
|
|
|
GIMP_PROC_ARG_BOOLEAN (procedure, "optimize-palette",
|
2023-01-23 23:38:51 +01:00
|
|
|
_("_Optimize for smallest possible palette size"),
|
2022-08-17 18:05:13 +00:00
|
|
|
_("When checked, save as 1, 2, 4, or 8-bit depending"
|
|
|
|
" on number of colors used. When unchecked, always"
|
|
|
|
" save as 8-bit"),
|
|
|
|
FALSE,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
2023-08-03 21:20:14 +02:00
|
|
|
GIMP_PROC_AUX_ARG_CHOICE (procedure, "format",
|
|
|
|
_("_Pixel format"),
|
|
|
|
_("PNG export format"),
|
|
|
|
gimp_choice_new_with_values ("auto", PNG_FORMAT_AUTO, _("Automatic"), NULL,
|
|
|
|
"rgb8", PNG_FORMAT_RGB8, _("8 bpc RGB"), NULL,
|
|
|
|
"gray8", PNG_FORMAT_GRAY8, _("8 bpc GRAY"), NULL,
|
|
|
|
"rgba8", PNG_FORMAT_RGBA8, _("8 bpc RGBA"), NULL,
|
|
|
|
"graya8", PNG_FORMAT_GRAYA8, _("8 bpc GRAYA"), NULL,
|
|
|
|
"rgb16", PNG_FORMAT_RGB16, _("16 bpc RGB"), NULL,
|
|
|
|
"gray16", PNG_FORMAT_GRAY16, _("16 bpc GRAY"), NULL,
|
|
|
|
"rgba16", PNG_FORMAT_RGBA16, _("16 bpc RGBA"), NULL,
|
|
|
|
"graya16", PNG_FORMAT_GRAYA16, _("16 bpc GRAYA"), NULL,
|
|
|
|
NULL),
|
|
|
|
"auto", G_PARAM_READWRITE);
|
2019-09-25 01:06:16 +02:00
|
|
|
|
2024-04-20 03:08:57 +00:00
|
|
|
gimp_export_procedure_set_support_exif (GIMP_EXPORT_PROCEDURE (procedure), TRUE);
|
|
|
|
gimp_export_procedure_set_support_iptc (GIMP_EXPORT_PROCEDURE (procedure), TRUE);
|
|
|
|
gimp_export_procedure_set_support_xmp (GIMP_EXPORT_PROCEDURE (procedure), TRUE);
|
2020-11-21 01:55:33 +01:00
|
|
|
#if defined(PNG_iCCP_SUPPORTED)
|
2024-04-20 03:08:57 +00:00
|
|
|
gimp_export_procedure_set_support_profile (GIMP_EXPORT_PROCEDURE (procedure), TRUE);
|
2020-11-21 01:55:33 +01:00
|
|
|
#endif
|
2024-04-20 03:08:57 +00:00
|
|
|
gimp_export_procedure_set_support_thumbnail (GIMP_EXPORT_PROCEDURE (procedure), TRUE);
|
|
|
|
gimp_export_procedure_set_support_comment (GIMP_EXPORT_PROCEDURE (procedure), TRUE);
|
2019-08-16 21:49:19 +02:00
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2019-08-16 21:49:19 +02:00
|
|
|
return procedure;
|
|
|
|
}
|
|
|
|
|
|
|
|
static GimpValueArray *
|
2023-08-06 00:59:19 +02:00
|
|
|
png_load (GimpProcedure *procedure,
|
|
|
|
GimpRunMode run_mode,
|
|
|
|
GFile *file,
|
|
|
|
GimpMetadata *metadata,
|
|
|
|
GimpMetadataLoadFlags *flags,
|
|
|
|
GimpProcedureConfig *config,
|
|
|
|
gpointer run_data)
|
2019-08-16 21:49:19 +02:00
|
|
|
{
|
|
|
|
GimpValueArray *return_vals;
|
2023-08-06 00:59:19 +02:00
|
|
|
gboolean report_progress = FALSE;
|
2019-08-16 21:49:19 +02:00
|
|
|
gboolean resolution_loaded = FALSE;
|
|
|
|
gboolean profile_loaded = FALSE;
|
2019-08-16 23:48:56 +02:00
|
|
|
GimpImage *image;
|
2019-08-16 21:49:19 +02:00
|
|
|
GError *error = NULL;
|
2013-10-19 18:38:01 +02:00
|
|
|
|
2012-11-27 20:58:05 +01:00
|
|
|
gegl_init (NULL, NULL);
|
2003-03-25 16:38:19 +00:00
|
|
|
|
2023-08-06 00:59:19 +02:00
|
|
|
if (run_mode != GIMP_RUN_NONINTERACTIVE)
|
2019-08-16 21:49:19 +02:00
|
|
|
{
|
2019-09-20 19:39:00 +02:00
|
|
|
gimp_ui_init (PLUG_IN_BINARY);
|
2023-08-06 00:59:19 +02:00
|
|
|
report_progress = TRUE;
|
2019-08-16 21:49:19 +02:00
|
|
|
}
|
2001-12-31 00:21:10 +00:00
|
|
|
|
2019-09-11 21:48:34 +02:00
|
|
|
image = load_image (file,
|
2023-08-06 00:59:19 +02:00
|
|
|
report_progress,
|
2019-08-16 23:48:56 +02:00
|
|
|
&resolution_loaded,
|
|
|
|
&profile_loaded,
|
|
|
|
&error);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2019-08-16 23:48:56 +02:00
|
|
|
if (! image)
|
2019-08-16 21:49:19 +02:00
|
|
|
return gimp_procedure_new_return_values (procedure,
|
|
|
|
GIMP_PDB_EXECUTION_ERROR,
|
|
|
|
error);
|
2013-10-19 18:38:01 +02:00
|
|
|
|
2023-08-06 00:59:19 +02:00
|
|
|
if (resolution_loaded)
|
|
|
|
*flags &= ~GIMP_METADATA_LOAD_RESOLUTION;
|
2013-10-27 15:22:35 +01:00
|
|
|
|
2023-08-06 00:59:19 +02:00
|
|
|
if (profile_loaded)
|
|
|
|
*flags &= ~GIMP_METADATA_LOAD_COLORSPACE;
|
2013-10-27 18:36:06 +01:00
|
|
|
|
2019-08-16 21:49:19 +02:00
|
|
|
return_vals = gimp_procedure_new_return_values (procedure,
|
|
|
|
GIMP_PDB_SUCCESS,
|
|
|
|
NULL);
|
2019-06-20 18:08:59 +02:00
|
|
|
|
2019-08-16 23:48:56 +02:00
|
|
|
GIMP_VALUES_SET_IMAGE (return_vals, 1, image);
|
2013-10-27 15:22:35 +01:00
|
|
|
|
2019-08-16 21:49:19 +02:00
|
|
|
return return_vals;
|
|
|
|
}
|
2013-10-19 18:38:01 +02:00
|
|
|
|
2019-08-16 21:49:19 +02:00
|
|
|
static GimpValueArray *
|
2024-04-13 15:10:25 +00:00
|
|
|
png_export (GimpProcedure *procedure,
|
|
|
|
GimpRunMode run_mode,
|
|
|
|
GimpImage *image,
|
|
|
|
gint n_drawables,
|
|
|
|
GimpDrawable **drawables,
|
|
|
|
GFile *file,
|
|
|
|
GimpMetadata *metadata,
|
|
|
|
GimpProcedureConfig *config,
|
|
|
|
gpointer run_data)
|
2019-08-16 21:49:19 +02:00
|
|
|
{
|
2023-07-20 23:16:27 +02:00
|
|
|
GimpPDBStatusType status = GIMP_PDB_SUCCESS;
|
|
|
|
GimpExportReturn export = GIMP_EXPORT_CANCEL;
|
|
|
|
GimpImage *orig_image;
|
|
|
|
gboolean alpha;
|
|
|
|
GError *error = NULL;
|
2013-10-19 18:38:01 +02:00
|
|
|
|
2019-08-16 21:49:19 +02:00
|
|
|
gegl_init (NULL, NULL);
|
2004-01-05 00:57:43 +00:00
|
|
|
|
2019-08-16 23:48:56 +02:00
|
|
|
orig_image = image;
|
2002-10-14 15:32:47 +00:00
|
|
|
|
2019-08-16 21:49:19 +02:00
|
|
|
switch (run_mode)
|
|
|
|
{
|
|
|
|
case GIMP_RUN_INTERACTIVE:
|
|
|
|
case GIMP_RUN_WITH_LAST_VALS:
|
2019-09-20 19:39:00 +02:00
|
|
|
gimp_ui_init (PLUG_IN_BINARY);
|
2019-08-16 21:49:19 +02:00
|
|
|
|
2020-04-14 11:46:17 +02:00
|
|
|
export = gimp_export_image (&image, &n_drawables, &drawables, "PNG",
|
2019-08-16 21:49:19 +02:00
|
|
|
GIMP_EXPORT_CAN_HANDLE_RGB |
|
|
|
|
GIMP_EXPORT_CAN_HANDLE_GRAY |
|
|
|
|
GIMP_EXPORT_CAN_HANDLE_INDEXED |
|
|
|
|
GIMP_EXPORT_CAN_HANDLE_ALPHA);
|
|
|
|
|
|
|
|
if (export == GIMP_EXPORT_CANCEL)
|
|
|
|
return gimp_procedure_new_return_values (procedure, GIMP_PDB_CANCEL,
|
|
|
|
NULL);
|
|
|
|
break;
|
2014-06-05 21:33:40 +02:00
|
|
|
|
2019-08-16 21:49:19 +02:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2013-11-10 00:18:48 +01:00
|
|
|
|
2020-04-14 11:46:17 +02:00
|
|
|
if (n_drawables != 1)
|
|
|
|
{
|
|
|
|
/* PNG images have no layer concept. Export image should have a
|
|
|
|
* single drawable selected.
|
|
|
|
*/
|
|
|
|
g_set_error (&error, G_FILE_ERROR, 0,
|
|
|
|
_("PNG format does not support multiple layers."));
|
|
|
|
|
|
|
|
return gimp_procedure_new_return_values (procedure,
|
|
|
|
GIMP_PDB_CALLING_ERROR,
|
|
|
|
error);
|
|
|
|
}
|
|
|
|
|
|
|
|
alpha = gimp_drawable_has_alpha (drawables[0]);
|
2019-08-16 21:49:19 +02:00
|
|
|
|
2019-09-25 01:06:16 +02:00
|
|
|
/* If the image has no transparency, then there is usually no need
|
|
|
|
* to save a bKGD chunk. For more information, see:
|
|
|
|
* http://bugzilla.gnome.org/show_bug.cgi?id=92395
|
2019-08-16 21:49:19 +02:00
|
|
|
*/
|
2019-09-25 01:06:16 +02:00
|
|
|
if (! alpha)
|
|
|
|
g_object_set (config,
|
|
|
|
"bkgd", FALSE,
|
|
|
|
NULL);
|
2008-08-17 12:13:46 +00:00
|
|
|
|
2019-09-25 01:06:16 +02:00
|
|
|
if (run_mode == GIMP_RUN_INTERACTIVE)
|
2019-08-16 21:49:19 +02:00
|
|
|
{
|
2024-04-13 15:10:25 +00:00
|
|
|
if (! export_dialog (orig_image, procedure, G_OBJECT (config), alpha))
|
2019-08-16 21:49:19 +02:00
|
|
|
status = GIMP_PDB_CANCEL;
|
|
|
|
}
|
2014-06-05 02:07:13 +02:00
|
|
|
|
2019-08-16 21:49:19 +02:00
|
|
|
if (status == GIMP_PDB_SUCCESS)
|
|
|
|
{
|
2019-10-09 22:51:34 +02:00
|
|
|
gint bits_per_sample;
|
2014-06-05 02:07:13 +02:00
|
|
|
|
2024-04-13 15:10:25 +00:00
|
|
|
if (export_image (file, image, drawables[0], orig_image, G_OBJECT (config),
|
|
|
|
&bits_per_sample, run_mode != GIMP_RUN_NONINTERACTIVE,
|
|
|
|
&error))
|
2002-10-14 15:32:47 +00:00
|
|
|
{
|
2019-08-16 21:49:19 +02:00
|
|
|
if (metadata)
|
2019-10-09 22:51:34 +02:00
|
|
|
gimp_metadata_set_bits_per_sample (metadata, bits_per_sample);
|
2004-01-06 09:53:35 +00:00
|
|
|
}
|
|
|
|
else
|
2008-08-18 06:31:03 +00:00
|
|
|
{
|
2019-08-16 21:49:19 +02:00
|
|
|
status = GIMP_PDB_EXECUTION_ERROR;
|
2008-08-18 06:31:03 +00:00
|
|
|
}
|
2004-01-06 09:53:35 +00:00
|
|
|
}
|
2008-08-18 06:31:03 +00:00
|
|
|
|
2019-08-16 21:49:19 +02:00
|
|
|
if (export == GIMP_EXPORT_EXPORT)
|
2020-04-14 11:46:17 +02:00
|
|
|
{
|
|
|
|
gimp_image_delete (image);
|
|
|
|
g_free (drawables);
|
|
|
|
}
|
2000-01-25 17:46:56 +00:00
|
|
|
|
2019-08-16 21:49:19 +02:00
|
|
|
return gimp_procedure_new_return_values (procedure, status, error);
|
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2007-02-16 13:14:20 +00:00
|
|
|
struct read_error_data
|
|
|
|
{
|
|
|
|
guchar *pixel; /* Pixel data */
|
2012-04-26 03:16:19 +02:00
|
|
|
GeglBuffer *buffer; /* GEGL buffer for layer */
|
|
|
|
const Babl *file_format;
|
2007-02-16 13:14:20 +00:00
|
|
|
guint32 width; /* png_infop->width */
|
|
|
|
guint32 height; /* png_infop->height */
|
|
|
|
gint bpp; /* Bytes per pixel */
|
|
|
|
gint tile_height; /* Height of tile in GIMP */
|
|
|
|
gint begin; /* Beginning tile row */
|
|
|
|
gint end; /* Ending tile row */
|
|
|
|
gint num; /* Number of rows to load */
|
|
|
|
};
|
|
|
|
|
|
|
|
static void
|
2014-06-05 21:33:40 +02:00
|
|
|
on_read_error (png_structp png_ptr,
|
|
|
|
png_const_charp error_msg)
|
2007-02-16 13:14:20 +00:00
|
|
|
{
|
|
|
|
struct read_error_data *error_data = png_get_error_ptr (png_ptr);
|
|
|
|
gint begin;
|
|
|
|
gint end;
|
|
|
|
gint num;
|
|
|
|
|
2018-06-13 19:06:27 +02:00
|
|
|
g_printerr (_("Error loading PNG file: %s\n"), error_msg);
|
2007-02-16 13:14:20 +00:00
|
|
|
|
|
|
|
/* Flush the current half-read row of tiles */
|
|
|
|
|
2012-04-26 23:07:35 +02:00
|
|
|
gegl_buffer_set (error_data->buffer,
|
|
|
|
GEGL_RECTANGLE (0, error_data->begin,
|
2012-04-27 03:35:07 +02:00
|
|
|
error_data->width,
|
2012-04-26 23:07:35 +02:00
|
|
|
error_data->num),
|
|
|
|
0,
|
|
|
|
error_data->file_format,
|
|
|
|
error_data->pixel,
|
|
|
|
GEGL_AUTO_ROWSTRIDE);
|
2007-02-16 13:14:20 +00:00
|
|
|
|
2012-04-27 03:35:07 +02:00
|
|
|
begin = error_data->begin + error_data->tile_height;
|
2007-02-16 13:14:20 +00:00
|
|
|
|
2012-04-27 03:35:07 +02:00
|
|
|
if (begin < error_data->height)
|
2007-02-16 13:14:20 +00:00
|
|
|
{
|
2012-04-27 03:35:07 +02:00
|
|
|
end = MIN (error_data->end + error_data->tile_height, error_data->height);
|
2007-02-16 13:14:20 +00:00
|
|
|
num = end - begin;
|
|
|
|
|
2012-04-27 03:35:07 +02:00
|
|
|
gegl_buffer_clear (error_data->buffer,
|
|
|
|
GEGL_RECTANGLE (0, begin, error_data->width, num));
|
2007-02-16 13:14:20 +00:00
|
|
|
}
|
|
|
|
|
2015-06-30 19:41:41 +02:00
|
|
|
g_object_unref (error_data->buffer);
|
2011-04-25 20:09:15 +05:30
|
|
|
longjmp (png_jmpbuf (png_ptr), 1);
|
2007-02-16 13:14:20 +00:00
|
|
|
}
|
|
|
|
|
2011-04-26 08:06:27 +05:30
|
|
|
static int
|
|
|
|
get_bit_depth_for_palette (int num_palette)
|
|
|
|
{
|
|
|
|
if (num_palette <= 2)
|
|
|
|
return 1;
|
|
|
|
else if (num_palette <= 4)
|
|
|
|
return 2;
|
|
|
|
else if (num_palette <= 16)
|
|
|
|
return 4;
|
|
|
|
else
|
|
|
|
return 8;
|
|
|
|
}
|
|
|
|
|
2015-11-23 18:44:17 +01:00
|
|
|
static GimpColorProfile *
|
|
|
|
load_color_profile (png_structp pp,
|
|
|
|
png_infop info,
|
|
|
|
gchar **profile_name)
|
|
|
|
{
|
|
|
|
GimpColorProfile *profile = NULL;
|
|
|
|
|
|
|
|
#if defined(PNG_iCCP_SUPPORTED)
|
|
|
|
png_uint_32 proflen;
|
|
|
|
png_charp profname;
|
2016-09-04 22:12:30 +02:00
|
|
|
png_bytep prof;
|
2015-11-23 18:44:17 +01:00
|
|
|
int profcomp;
|
|
|
|
|
|
|
|
if (png_get_iCCP (pp, info, &profname, &profcomp, &prof, &proflen))
|
|
|
|
{
|
|
|
|
profile = gimp_color_profile_new_from_icc_profile ((guint8 *) prof,
|
|
|
|
proflen, NULL);
|
|
|
|
if (profile && profname)
|
|
|
|
{
|
|
|
|
*profile_name = g_convert (profname, strlen (profname),
|
|
|
|
"ISO-8859-1", "UTF-8", NULL, NULL, NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return profile;
|
|
|
|
}
|
|
|
|
|
2021-01-17 15:28:08 +01:00
|
|
|
/* Copied from src/cmsvirt.c in Little-CMS. */
|
|
|
|
static cmsToneCurve *
|
|
|
|
Build_sRGBGamma (cmsContext ContextID)
|
|
|
|
{
|
|
|
|
cmsFloat64Number Parameters[5];
|
|
|
|
|
|
|
|
Parameters[0] = 2.4;
|
|
|
|
Parameters[1] = 1. / 1.055;
|
|
|
|
Parameters[2] = 0.055 / 1.055;
|
|
|
|
Parameters[3] = 1. / 12.92;
|
|
|
|
Parameters[4] = 0.04045;
|
|
|
|
|
|
|
|
return cmsBuildParametricToneCurve (ContextID, 4, Parameters);
|
|
|
|
}
|
|
|
|
|
2011-10-11 10:22:00 +05:30
|
|
|
/*
|
|
|
|
* 'load_image()' - Load a PNG image into a new image window.
|
|
|
|
*/
|
2019-08-16 23:48:56 +02:00
|
|
|
static GimpImage *
|
2019-09-11 21:48:34 +02:00
|
|
|
load_image (GFile *file,
|
2023-08-06 00:59:19 +02:00
|
|
|
gboolean report_progress,
|
2013-10-27 18:36:06 +01:00
|
|
|
gboolean *resolution_loaded,
|
2019-06-20 18:08:59 +02:00
|
|
|
gboolean *profile_loaded,
|
2008-08-17 12:13:46 +00:00
|
|
|
GError **error)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
2015-11-23 18:44:17 +01:00
|
|
|
gint i; /* Looping var */
|
|
|
|
gint trns; /* Transparency present */
|
|
|
|
gint bpp; /* Bytes per pixel */
|
|
|
|
gint width; /* image width */
|
|
|
|
gint height; /* image height */
|
|
|
|
gint num_passes; /* Number of interlace passes in file */
|
|
|
|
gint pass; /* Current pass in file */
|
|
|
|
gint tile_height; /* Height of tile in GIMP */
|
|
|
|
gint begin; /* Beginning tile row */
|
|
|
|
gint end; /* Ending tile row */
|
|
|
|
gint num; /* Number of rows to load */
|
|
|
|
GimpImageBaseType image_type; /* Type of image */
|
|
|
|
GimpPrecision image_precision; /* Precision of image */
|
|
|
|
GimpImageType layer_type; /* Type of drawable/layer */
|
|
|
|
GimpColorProfile *profile = NULL; /* Color profile */
|
|
|
|
gchar *profile_name = NULL; /* Profile's name */
|
|
|
|
FILE *fp; /* File pointer */
|
2019-08-16 23:48:56 +02:00
|
|
|
volatile GimpImage *image = NULL; /* Image -- protected for setjmp() */
|
|
|
|
GimpLayer *layer; /* Layer */
|
2015-11-23 18:44:17 +01:00
|
|
|
GeglBuffer *buffer; /* GEGL buffer for layer */
|
|
|
|
const Babl *file_format; /* BABL format for layer */
|
|
|
|
png_structp pp; /* PNG read pointer */
|
|
|
|
png_infop info; /* PNG info pointers */
|
2023-11-08 17:27:13 +00:00
|
|
|
png_voidp user_chunkp; /* PNG unknown chunk pointer */
|
2015-11-23 18:44:17 +01:00
|
|
|
guchar **pixels; /* Pixel rows */
|
|
|
|
guchar *pixel; /* Pixel data */
|
|
|
|
guchar alpha[256]; /* Index -> Alpha */
|
|
|
|
png_textp text;
|
|
|
|
gint num_texts;
|
|
|
|
struct read_error_data error_data;
|
2003-07-03 00:30:33 +00:00
|
|
|
|
2023-11-08 17:27:13 +00:00
|
|
|
safe_to_copy_chunks = NULL;
|
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
pp = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
2018-04-04 02:21:47 +02:00
|
|
|
if (! pp)
|
2011-09-21 17:08:53 +05:30
|
|
|
{
|
2011-09-27 19:22:50 +05:30
|
|
|
/* this could happen if the compile time and run-time libpng
|
|
|
|
versions do not match. */
|
|
|
|
|
2022-03-24 14:18:57 +01:00
|
|
|
g_set_error (error, G_FILE_ERROR, 0,
|
2018-04-04 02:21:47 +02:00
|
|
|
_("Error creating PNG read struct while loading '%s'."),
|
2019-09-11 21:48:34 +02:00
|
|
|
gimp_file_get_utf8_name (file));
|
2019-08-16 23:48:56 +02:00
|
|
|
return NULL;
|
2011-09-21 17:08:53 +05:30
|
|
|
}
|
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
info = png_create_info_struct (pp);
|
2018-03-26 21:25:10 +02:00
|
|
|
if (! info)
|
|
|
|
{
|
2022-03-24 14:18:57 +01:00
|
|
|
g_set_error (error, G_FILE_ERROR, 0,
|
2018-03-26 21:25:10 +02:00
|
|
|
_("Error while reading '%s'. Could not create PNG header info structure."),
|
2019-09-11 21:48:34 +02:00
|
|
|
gimp_file_get_utf8_name (file));
|
2019-08-16 23:48:56 +02:00
|
|
|
return NULL;
|
2018-03-26 21:25:10 +02:00
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2011-04-25 20:09:15 +05:30
|
|
|
if (setjmp (png_jmpbuf (pp)))
|
2002-10-14 15:32:47 +00:00
|
|
|
{
|
2008-08-18 06:53:21 +00:00
|
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
2008-08-17 12:13:46 +00:00
|
|
|
_("Error while reading '%s'. File corrupted?"),
|
2019-09-11 21:48:34 +02:00
|
|
|
gimp_file_get_utf8_name (file));
|
2019-08-16 23:48:56 +02:00
|
|
|
return (GimpImage *) image;
|
2002-10-14 15:32:47 +00:00
|
|
|
}
|
1999-09-13 00:57:52 +00:00
|
|
|
|
2014-02-16 10:41:12 -05:00
|
|
|
#ifdef PNG_BENIGN_ERRORS_SUPPORTED
|
2014-02-15 22:55:18 -05:00
|
|
|
/* Change some libpng errors to warnings (e.g. bug 721135) */
|
|
|
|
png_set_benign_errors (pp, TRUE);
|
2016-08-26 15:11:12 -04:00
|
|
|
|
|
|
|
/* bug 765850 */
|
|
|
|
png_set_option (pp, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON);
|
2014-02-16 10:41:12 -05:00
|
|
|
#endif
|
2014-02-15 22:55:18 -05:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
/*
|
|
|
|
* Open the file and initialize the PNG read "engine"...
|
|
|
|
*/
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2023-08-06 00:59:19 +02:00
|
|
|
if (report_progress)
|
2021-01-27 19:06:57 -05:00
|
|
|
gimp_progress_init_printf (_("Opening '%s'"),
|
|
|
|
gimp_file_get_utf8_name (file));
|
2014-07-23 16:39:00 +02:00
|
|
|
|
2021-10-01 18:14:14 +02:00
|
|
|
fp = g_fopen (g_file_peek_path (file), "rb");
|
1999-09-13 00:57:52 +00:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
if (fp == NULL)
|
1999-10-03 18:54:54 +00:00
|
|
|
{
|
2008-08-17 12:13:46 +00:00
|
|
|
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
|
|
|
|
_("Could not open '%s' for reading: %s"),
|
2019-09-11 21:48:34 +02:00
|
|
|
gimp_file_get_utf8_name (file), g_strerror (errno));
|
2019-08-16 23:48:56 +02:00
|
|
|
return NULL;
|
2002-10-14 15:32:47 +00:00
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
png_init_io (pp, fp);
|
2015-06-30 19:41:41 +02:00
|
|
|
png_set_compression_buffer_size (pp, 512);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2023-11-08 17:27:13 +00:00
|
|
|
/* Set up callback to save "safe to copy" chunks */
|
|
|
|
png_set_keep_unknown_chunks (pp, PNG_HANDLE_CHUNK_IF_SAFE, NULL, 0);
|
|
|
|
user_chunkp = png_get_user_chunk_ptr (pp);
|
|
|
|
png_set_read_user_chunk_fn (pp, user_chunkp, read_unknown_chunk);
|
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
/*
|
2015-11-23 18:44:17 +01:00
|
|
|
* Get the image info
|
2002-10-14 15:32:47 +00:00
|
|
|
*/
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
png_read_info (pp, info);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2015-11-23 18:44:17 +01:00
|
|
|
if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
|
|
|
|
png_set_swap (pp);
|
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
/*
|
2022-03-24 22:17:18 +01:00
|
|
|
* Get the iCCP (color profile) chunk, if any.
|
2015-11-23 18:44:17 +01:00
|
|
|
*/
|
|
|
|
profile = load_color_profile (pp, info, &profile_name);
|
|
|
|
|
2021-01-17 15:28:08 +01:00
|
|
|
if (! profile && ! png_get_valid (pp, info, PNG_INFO_sRGB) &&
|
|
|
|
(png_get_valid (pp, info, PNG_INFO_gAMA) ||
|
|
|
|
png_get_valid (pp, info, PNG_INFO_cHRM)))
|
|
|
|
{
|
|
|
|
/* This is kind of a special case for PNG. If an image has no
|
|
|
|
* profile, and the sRGB chunk is not set, and either gAMA or cHRM
|
|
|
|
* (or ideally both) are set, then we generate a profile from
|
|
|
|
* these data on import. See #3265.
|
|
|
|
*/
|
|
|
|
cmsToneCurve *gamma_curve[3];
|
|
|
|
cmsCIExyY whitepoint;
|
|
|
|
cmsCIExyYTRIPLE primaries;
|
|
|
|
cmsHPROFILE cms_profile = NULL;
|
|
|
|
gdouble gamma = 1.0 / DEFAULT_GAMMA;
|
|
|
|
|
|
|
|
if (png_get_valid (pp, info, PNG_INFO_gAMA) &&
|
|
|
|
png_get_gAMA (pp, info, &gamma) == PNG_INFO_gAMA)
|
|
|
|
{
|
|
|
|
gamma_curve[0] = gamma_curve[1] = gamma_curve[2] = cmsBuildGamma (NULL, 1.0 / gamma);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Use the sRGB gamma curve. */
|
|
|
|
gamma_curve[0] = gamma_curve[1] = gamma_curve[2] = Build_sRGBGamma (NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (png_get_valid (pp, info, PNG_INFO_cHRM) &&
|
|
|
|
png_get_cHRM (pp, info, &whitepoint.x, &whitepoint.y,
|
|
|
|
&primaries.Red.x, &primaries.Red.y,
|
|
|
|
&primaries.Green.x, &primaries.Green.y,
|
|
|
|
&primaries.Blue.x, &primaries.Blue.y) == PNG_INFO_cHRM)
|
|
|
|
{
|
|
|
|
whitepoint.Y = primaries.Red.Y = primaries.Green.Y = primaries.Blue.Y = 1.0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Rec709 primaries and D65 whitepoint as copied from
|
|
|
|
* cmsCreate_sRGBProfileTHR() in Little-CMS.
|
|
|
|
*/
|
|
|
|
cmsCIExyY d65_whitepoint = { 0.3127, 0.3290, 1.0 };
|
|
|
|
cmsCIExyYTRIPLE rec709_primaries =
|
|
|
|
{
|
|
|
|
{0.6400, 0.3300, 1.0},
|
|
|
|
{0.3000, 0.6000, 1.0},
|
|
|
|
{0.1500, 0.0600, 1.0}
|
|
|
|
};
|
|
|
|
|
|
|
|
memcpy (&whitepoint, &d65_whitepoint, sizeof whitepoint);
|
|
|
|
memcpy (&primaries, &rec709_primaries, sizeof primaries);
|
|
|
|
}
|
2021-03-02 12:02:23 +01:00
|
|
|
|
|
|
|
if (png_get_color_type (pp, info) == PNG_COLOR_TYPE_GRAY ||
|
|
|
|
png_get_color_type (pp, info) == PNG_COLOR_TYPE_GRAY_ALPHA)
|
|
|
|
cms_profile = cmsCreateGrayProfile (&whitepoint, gamma_curve[0]);
|
|
|
|
else /* RGB, RGB with Alpha and Indexed. */
|
|
|
|
cms_profile = cmsCreateRGBProfile (&whitepoint, &primaries, gamma_curve);
|
|
|
|
|
2021-01-17 15:28:08 +01:00
|
|
|
cmsFreeToneCurve (gamma_curve[0]);
|
|
|
|
g_warn_if_fail (cms_profile != NULL);
|
|
|
|
|
|
|
|
if (cms_profile != NULL)
|
|
|
|
{
|
|
|
|
/* Customize the profile description to show it is generated
|
|
|
|
* from PNG metadata.
|
|
|
|
*/
|
|
|
|
gchar *profile_desc;
|
|
|
|
cmsMLU *description_mlu;
|
|
|
|
cmsContext context_id = cmsGetProfileContextID (cms_profile);
|
|
|
|
|
|
|
|
/* Note that I am not trying to localize these strings on purpose
|
|
|
|
* because cmsMLUsetASCII() expects ASCII. Maybe we should move to
|
|
|
|
* using cmsMLUsetWide() if we want the generated profile
|
|
|
|
* descriptions to be localized. XXX
|
|
|
|
*/
|
|
|
|
if ((png_get_valid (pp, info, PNG_INFO_gAMA) && png_get_valid (pp, info, PNG_INFO_cHRM)))
|
2021-03-02 12:02:23 +01:00
|
|
|
profile_desc = g_strdup_printf ("Generated %s profile from PNG's gAMA (gamma %.4f) and cHRM chunks",
|
|
|
|
(png_get_color_type (pp, info) == PNG_COLOR_TYPE_GRAY ||
|
|
|
|
png_get_color_type (pp, info) == PNG_COLOR_TYPE_GRAY_ALPHA) ?
|
|
|
|
"grayscale" : "RGB", 1.0 / gamma);
|
2021-01-17 15:28:08 +01:00
|
|
|
else if (png_get_valid (pp, info, PNG_INFO_gAMA))
|
2021-03-02 12:02:23 +01:00
|
|
|
profile_desc = g_strdup_printf ("Generated %s profile from PNG's gAMA chunk (gamma %.4f)",
|
|
|
|
(png_get_color_type (pp, info) == PNG_COLOR_TYPE_GRAY ||
|
|
|
|
png_get_color_type (pp, info) == PNG_COLOR_TYPE_GRAY_ALPHA) ?
|
|
|
|
"grayscale" : "RGB", 1.0 / gamma);
|
2021-01-17 15:28:08 +01:00
|
|
|
else
|
2021-03-02 12:02:23 +01:00
|
|
|
profile_desc = g_strdup_printf ("Generated %s profile from PNG's cHRM chunk",
|
|
|
|
(png_get_color_type (pp, info) == PNG_COLOR_TYPE_GRAY ||
|
|
|
|
png_get_color_type (pp, info) == PNG_COLOR_TYPE_GRAY_ALPHA) ?
|
|
|
|
"grayscale" : "RGB");
|
2021-01-17 15:28:08 +01:00
|
|
|
|
|
|
|
description_mlu = cmsMLUalloc (context_id, 1);
|
|
|
|
|
|
|
|
cmsMLUsetASCII (description_mlu, "en", "US", profile_desc);
|
|
|
|
cmsWriteTag (cms_profile, cmsSigProfileDescriptionTag, description_mlu);
|
|
|
|
|
|
|
|
profile = gimp_color_profile_new_from_lcms_profile (cms_profile, NULL);
|
|
|
|
|
|
|
|
g_free (profile_desc);
|
|
|
|
cmsMLUfree (description_mlu);
|
|
|
|
cmsCloseProfile (cms_profile);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-23 18:44:17 +01:00
|
|
|
if (profile)
|
2022-03-24 22:17:18 +01:00
|
|
|
*profile_loaded = TRUE;
|
2015-11-23 18:44:17 +01:00
|
|
|
|
|
|
|
/*
|
2022-03-24 22:17:18 +01:00
|
|
|
* Get image precision and color model.
|
|
|
|
* Note that we always import PNG as non-linear. The data might be
|
|
|
|
* actually linear because of a linear profile, or because of a gAMA
|
|
|
|
* chunk with 1.0 value (which we convert to a profile above). But
|
|
|
|
* then we'll just set the right profile and that's it. Other than
|
|
|
|
* this, PNG doesn't have (that I can see in the spec) any kind of
|
|
|
|
* flag saying that data is linear, bypassing the profile's TRC so
|
|
|
|
* there is basically no reason to explicitly set a linear precision.
|
2002-10-14 15:32:47 +00:00
|
|
|
*/
|
1998-11-09 02:05:24 +00:00
|
|
|
|
2011-04-25 20:09:15 +05:30
|
|
|
if (png_get_bit_depth (pp, info) == 16)
|
2022-03-24 22:17:18 +01:00
|
|
|
image_precision = GIMP_PRECISION_U16_NON_LINEAR;
|
2012-04-30 02:25:08 +02:00
|
|
|
else
|
2022-03-24 22:17:18 +01:00
|
|
|
image_precision = GIMP_PRECISION_U8_NON_LINEAR;
|
2012-04-26 23:07:35 +02:00
|
|
|
|
2012-04-30 02:25:08 +02:00
|
|
|
if (png_get_bit_depth (pp, info) < 8)
|
2002-10-14 15:32:47 +00:00
|
|
|
{
|
2012-04-30 02:25:08 +02:00
|
|
|
if (png_get_color_type (pp, info) == PNG_COLOR_TYPE_GRAY)
|
|
|
|
png_set_expand (pp);
|
2000-04-25 03:57:03 +00:00
|
|
|
|
2012-04-30 02:25:08 +02:00
|
|
|
if (png_get_color_type (pp, info) == PNG_COLOR_TYPE_PALETTE)
|
|
|
|
png_set_packing (pp);
|
2002-10-14 15:32:47 +00:00
|
|
|
}
|
2000-04-25 03:57:03 +00:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
/*
|
|
|
|
* Expand G+tRNS to GA, RGB+tRNS to RGBA
|
|
|
|
*/
|
2000-05-09 01:46:13 +00:00
|
|
|
|
2011-04-25 20:09:15 +05:30
|
|
|
if (png_get_color_type (pp, info) != PNG_COLOR_TYPE_PALETTE &&
|
|
|
|
png_get_valid (pp, info, PNG_INFO_tRNS))
|
2012-04-30 02:25:08 +02:00
|
|
|
png_set_expand (pp);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
/*
|
|
|
|
* Turn on interlace handling... libpng returns just 1 (ie single pass)
|
|
|
|
* if the image is not interlaced
|
|
|
|
*/
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
num_passes = png_set_interlace_handling (pp);
|
1999-09-13 00:57:52 +00:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
/*
|
|
|
|
* Special handling for INDEXED + tRNS (transparency palette)
|
|
|
|
*/
|
1999-10-02 22:48:44 +00:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
if (png_get_valid (pp, info, PNG_INFO_tRNS) &&
|
2011-04-25 20:09:15 +05:30
|
|
|
png_get_color_type (pp, info) == PNG_COLOR_TYPE_PALETTE)
|
2002-10-14 15:32:47 +00:00
|
|
|
{
|
2015-11-23 18:44:17 +01:00
|
|
|
guchar *alpha_ptr;
|
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
png_get_tRNS (pp, info, &alpha_ptr, &num, NULL);
|
2015-11-23 18:44:17 +01:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
/* Copy the existing alpha values from the tRNS chunk */
|
|
|
|
for (i = 0; i < num; ++i)
|
|
|
|
alpha[i] = alpha_ptr[i];
|
2015-11-23 18:44:17 +01:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
/* And set any others to fully opaque (255) */
|
|
|
|
for (i = num; i < 256; ++i)
|
|
|
|
alpha[i] = 255;
|
2015-11-23 18:44:17 +01:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
trns = 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
trns = 0;
|
|
|
|
}
|
1999-10-02 22:48:44 +00:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
/*
|
|
|
|
* Update the info structures after the transformations take effect
|
|
|
|
*/
|
|
|
|
|
|
|
|
png_read_update_info (pp, info);
|
1999-09-13 00:57:52 +00:00
|
|
|
|
2011-04-25 20:09:15 +05:30
|
|
|
switch (png_get_color_type (pp, info))
|
2002-10-14 15:32:47 +00:00
|
|
|
{
|
2015-11-23 18:44:17 +01:00
|
|
|
case PNG_COLOR_TYPE_RGB:
|
2002-10-14 15:32:47 +00:00
|
|
|
image_type = GIMP_RGB;
|
|
|
|
layer_type = GIMP_RGB_IMAGE;
|
|
|
|
break;
|
|
|
|
|
2015-11-23 18:44:17 +01:00
|
|
|
case PNG_COLOR_TYPE_RGB_ALPHA:
|
2002-10-14 15:32:47 +00:00
|
|
|
image_type = GIMP_RGB;
|
|
|
|
layer_type = GIMP_RGBA_IMAGE;
|
|
|
|
break;
|
|
|
|
|
2015-11-23 18:44:17 +01:00
|
|
|
case PNG_COLOR_TYPE_GRAY:
|
2006-08-22 17:40:04 +00:00
|
|
|
image_type = GIMP_GRAY;
|
|
|
|
layer_type = GIMP_GRAY_IMAGE;
|
2002-10-14 15:32:47 +00:00
|
|
|
break;
|
|
|
|
|
2015-11-23 18:44:17 +01:00
|
|
|
case PNG_COLOR_TYPE_GRAY_ALPHA:
|
2002-10-14 15:32:47 +00:00
|
|
|
image_type = GIMP_GRAY;
|
|
|
|
layer_type = GIMP_GRAYA_IMAGE;
|
|
|
|
break;
|
|
|
|
|
2015-11-23 18:44:17 +01:00
|
|
|
case PNG_COLOR_TYPE_PALETTE:
|
2002-10-14 15:32:47 +00:00
|
|
|
image_type = GIMP_INDEXED;
|
|
|
|
layer_type = GIMP_INDEXED_IMAGE;
|
|
|
|
break;
|
2008-08-17 12:13:46 +00:00
|
|
|
|
2015-11-23 18:44:17 +01:00
|
|
|
default:
|
2022-03-24 14:18:57 +01:00
|
|
|
g_set_error (error, G_FILE_ERROR, 0,
|
2008-08-17 12:13:46 +00:00
|
|
|
_("Unknown color model in PNG file '%s'."),
|
2019-09-11 21:48:34 +02:00
|
|
|
gimp_file_get_utf8_name (file));
|
2019-08-16 23:48:56 +02:00
|
|
|
return NULL;
|
2005-02-26 21:44:23 +00:00
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2012-04-27 03:35:07 +02:00
|
|
|
width = png_get_image_width (pp, info);
|
|
|
|
height = png_get_image_height (pp, info);
|
|
|
|
|
2012-04-30 02:25:08 +02:00
|
|
|
image = gimp_image_new_with_precision (width, height,
|
|
|
|
image_type, image_precision);
|
2019-08-16 23:48:56 +02:00
|
|
|
if (! image)
|
2002-10-14 15:32:47 +00:00
|
|
|
{
|
2022-03-24 14:18:57 +01:00
|
|
|
g_set_error (error, G_FILE_ERROR, 0,
|
2011-12-06 20:47:37 +05:30
|
|
|
_("Could not create new image for '%s': %s"),
|
2019-09-11 21:48:34 +02:00
|
|
|
gimp_file_get_utf8_name (file),
|
2019-08-16 21:49:19 +02:00
|
|
|
gimp_pdb_get_last_error (gimp_get_pdb ()));
|
2019-08-16 23:48:56 +02:00
|
|
|
return NULL;
|
2005-02-26 21:44:23 +00:00
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2019-06-16 14:52:48 +02:00
|
|
|
/*
|
|
|
|
* Attach the color profile, if any
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (profile)
|
|
|
|
{
|
2019-08-16 23:48:56 +02:00
|
|
|
gimp_image_set_color_profile ((GimpImage *) image, profile);
|
2019-06-16 14:52:48 +02:00
|
|
|
g_object_unref (profile);
|
|
|
|
|
|
|
|
if (profile_name)
|
|
|
|
{
|
|
|
|
GimpParasite *parasite;
|
|
|
|
|
|
|
|
parasite = gimp_parasite_new ("icc-profile-name",
|
|
|
|
GIMP_PARASITE_PERSISTENT |
|
|
|
|
GIMP_PARASITE_UNDOABLE,
|
|
|
|
strlen (profile_name),
|
|
|
|
profile_name);
|
2019-08-16 23:48:56 +02:00
|
|
|
gimp_image_attach_parasite ((GimpImage *) image, parasite);
|
2019-06-16 14:52:48 +02:00
|
|
|
gimp_parasite_free (parasite);
|
|
|
|
|
|
|
|
g_free (profile_name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
/*
|
|
|
|
* Create the "background" layer to hold the image...
|
|
|
|
*/
|
2000-06-30 02:51:35 +00:00
|
|
|
|
2019-08-16 23:48:56 +02:00
|
|
|
layer = gimp_layer_new ((GimpImage *) image, _("Background"), width, height,
|
2017-01-08 23:00:19 +01:00
|
|
|
layer_type,
|
2017-08-21 20:04:25 +02:00
|
|
|
100,
|
2019-08-16 23:48:56 +02:00
|
|
|
gimp_image_get_default_new_layer_mode ((GimpImage *) image));
|
|
|
|
gimp_image_insert_layer ((GimpImage *) image, layer, NULL, 0);
|
2000-06-30 02:51:35 +00:00
|
|
|
|
2019-08-16 23:48:56 +02:00
|
|
|
file_format = gimp_drawable_get_format (GIMP_DRAWABLE (layer));
|
2012-04-30 02:25:08 +02:00
|
|
|
|
1999-04-19 00:17:49 +00:00
|
|
|
/*
|
|
|
|
* Find out everything we can about the image resolution
|
2000-04-25 03:57:03 +00:00
|
|
|
* This is only practical with the new 1.0 APIs, I'm afraid
|
|
|
|
* due to a bug in libpng-1.0.6, see png-implement for details
|
1999-04-19 00:17:49 +00:00
|
|
|
*/
|
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
if (png_get_valid (pp, info, PNG_INFO_oFFs))
|
|
|
|
{
|
2003-11-18 16:46:56 +00:00
|
|
|
gint offset_x = png_get_x_offset_pixels (pp, info);
|
|
|
|
gint offset_y = png_get_y_offset_pixels (pp, info);
|
|
|
|
|
2020-12-02 20:48:48 +00:00
|
|
|
if (offset_x != 0 ||
|
|
|
|
offset_y != 0)
|
2012-02-09 20:09:14 +02:00
|
|
|
{
|
2023-08-06 00:59:19 +02:00
|
|
|
if (! report_progress)
|
2020-12-02 20:48:48 +00:00
|
|
|
{
|
|
|
|
gimp_layer_set_offsets (layer, offset_x, offset_y);
|
|
|
|
}
|
|
|
|
else if (offsets_dialog (offset_x, offset_y))
|
2012-02-09 20:09:14 +02:00
|
|
|
{
|
2020-12-02 20:48:48 +00:00
|
|
|
gimp_layer_set_offsets (layer, offset_x, offset_y);
|
|
|
|
|
|
|
|
if (abs (offset_x) > width ||
|
|
|
|
abs (offset_y) > height)
|
|
|
|
{
|
|
|
|
g_message (_("The PNG file specifies an offset that caused "
|
|
|
|
"the layer to be positioned outside the image."));
|
|
|
|
}
|
2012-02-09 20:09:14 +02:00
|
|
|
}
|
2003-11-18 16:46:56 +00:00
|
|
|
}
|
2002-10-14 15:32:47 +00:00
|
|
|
}
|
2003-11-18 16:46:56 +00:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
if (png_get_valid (pp, info, PNG_INFO_pHYs))
|
|
|
|
{
|
2003-09-30 23:19:48 +00:00
|
|
|
png_uint_32 xres;
|
|
|
|
png_uint_32 yres;
|
|
|
|
gint unit_type;
|
|
|
|
|
2010-07-13 22:20:15 +02:00
|
|
|
if (png_get_pHYs (pp, info,
|
|
|
|
&xres, &yres, &unit_type) && xres > 0 && yres > 0)
|
2003-09-30 23:19:48 +00:00
|
|
|
{
|
|
|
|
switch (unit_type)
|
|
|
|
{
|
|
|
|
case PNG_RESOLUTION_UNKNOWN:
|
|
|
|
{
|
|
|
|
gdouble image_xres, image_yres;
|
|
|
|
|
2019-08-16 23:48:56 +02:00
|
|
|
gimp_image_get_resolution ((GimpImage *) image, &image_xres, &image_yres);
|
2003-09-30 23:19:48 +00:00
|
|
|
|
|
|
|
if (xres > yres)
|
|
|
|
image_xres = image_yres * (gdouble) xres / (gdouble) yres;
|
|
|
|
else
|
|
|
|
image_yres = image_xres * (gdouble) yres / (gdouble) xres;
|
|
|
|
|
2019-08-16 23:48:56 +02:00
|
|
|
gimp_image_set_resolution ((GimpImage *) image, image_xres, image_yres);
|
2013-10-27 18:36:06 +01:00
|
|
|
|
|
|
|
*resolution_loaded = TRUE;
|
2003-09-30 23:19:48 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PNG_RESOLUTION_METER:
|
2019-08-16 23:48:56 +02:00
|
|
|
gimp_image_set_resolution ((GimpImage *) image,
|
2003-10-10 14:26:34 +00:00
|
|
|
(gdouble) xres * 0.0254,
|
|
|
|
(gdouble) yres * 0.0254);
|
2019-08-16 23:48:56 +02:00
|
|
|
gimp_image_set_unit ((GimpImage *) image, GIMP_UNIT_MM);
|
2013-10-27 18:36:06 +01:00
|
|
|
|
|
|
|
*resolution_loaded = TRUE;
|
2003-09-30 23:19:48 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
}
|
1999-04-19 00:17:49 +00:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
/*
|
|
|
|
* Load the colormap as necessary...
|
|
|
|
*/
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2011-04-25 20:09:15 +05:30
|
|
|
if (png_get_color_type (pp, info) & PNG_COLOR_MASK_PALETTE)
|
2002-10-14 15:32:47 +00:00
|
|
|
{
|
2011-04-25 20:09:15 +05:30
|
|
|
png_colorp palette;
|
|
|
|
int num_palette;
|
|
|
|
|
|
|
|
png_get_PLTE (pp, info, &palette, &num_palette);
|
2019-08-16 23:48:56 +02:00
|
|
|
gimp_image_set_colormap ((GimpImage *) image, (guchar *) palette,
|
2019-03-08 15:12:04 +01:00
|
|
|
num_palette);
|
2002-10-14 15:32:47 +00:00
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2012-04-28 17:46:32 +02:00
|
|
|
bpp = babl_format_get_bytes_per_pixel (file_format);
|
|
|
|
|
2019-08-16 23:48:56 +02:00
|
|
|
buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (layer));
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
/*
|
|
|
|
* Temporary buffer...
|
|
|
|
*/
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
tile_height = gimp_tile_height ();
|
2012-04-27 03:35:07 +02:00
|
|
|
pixel = g_new0 (guchar, tile_height * width * bpp);
|
2002-10-14 15:32:47 +00:00
|
|
|
pixels = g_new (guchar *, tile_height);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
for (i = 0; i < tile_height; i++)
|
2012-04-27 03:35:07 +02:00
|
|
|
pixels[i] = pixel + width * bpp * i;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2007-02-16 13:14:20 +00:00
|
|
|
/* Install our own error handler to handle incomplete PNG files better */
|
2012-04-26 03:16:19 +02:00
|
|
|
error_data.buffer = buffer;
|
2008-03-17 14:44:23 +00:00
|
|
|
error_data.pixel = pixel;
|
2012-04-26 03:16:19 +02:00
|
|
|
error_data.file_format = file_format;
|
2007-02-16 13:14:20 +00:00
|
|
|
error_data.tile_height = tile_height;
|
2012-04-27 03:35:07 +02:00
|
|
|
error_data.width = width;
|
|
|
|
error_data.height = height;
|
2008-03-17 14:44:23 +00:00
|
|
|
error_data.bpp = bpp;
|
2007-08-07 14:25:40 +00:00
|
|
|
|
2007-02-16 13:14:20 +00:00
|
|
|
png_set_error_fn (pp, &error_data, on_read_error, NULL);
|
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
for (pass = 0; pass < num_passes; pass++)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
2002-10-14 15:32:47 +00:00
|
|
|
/*
|
|
|
|
* This works if you are only reading one row at a time...
|
|
|
|
*/
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2012-04-27 03:35:07 +02:00
|
|
|
for (begin = 0; begin < height; begin += tile_height)
|
2002-10-14 15:32:47 +00:00
|
|
|
{
|
2012-04-27 03:35:07 +02:00
|
|
|
end = MIN (begin + tile_height, height);
|
2002-10-14 15:32:47 +00:00
|
|
|
num = end - begin;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
if (pass != 0) /* to handle interlaced PiNGs */
|
2012-04-26 23:07:35 +02:00
|
|
|
gegl_buffer_get (buffer,
|
2012-04-27 03:35:07 +02:00
|
|
|
GEGL_RECTANGLE (0, begin, width, num),
|
2012-04-26 23:07:35 +02:00
|
|
|
1.0,
|
|
|
|
file_format,
|
|
|
|
pixel,
|
|
|
|
GEGL_AUTO_ROWSTRIDE,
|
|
|
|
GEGL_ABYSS_NONE);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2007-02-16 13:14:20 +00:00
|
|
|
error_data.begin = begin;
|
2008-03-17 14:44:23 +00:00
|
|
|
error_data.end = end;
|
|
|
|
error_data.num = num;
|
2007-08-07 14:25:40 +00:00
|
|
|
|
2006-08-22 17:40:04 +00:00
|
|
|
png_read_rows (pp, pixels, NULL, num);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2012-04-26 23:07:35 +02:00
|
|
|
gegl_buffer_set (buffer,
|
2012-04-27 03:35:07 +02:00
|
|
|
GEGL_RECTANGLE (0, begin, width, num),
|
2012-04-26 23:07:35 +02:00
|
|
|
0,
|
|
|
|
file_format,
|
|
|
|
pixel,
|
|
|
|
GEGL_AUTO_ROWSTRIDE);
|
2007-02-16 13:14:20 +00:00
|
|
|
|
2023-08-06 00:59:19 +02:00
|
|
|
if (report_progress)
|
2021-01-27 19:06:57 -05:00
|
|
|
gimp_progress_update (((gdouble) pass +
|
|
|
|
(gdouble) end / (gdouble) height) /
|
|
|
|
(gdouble) num_passes);
|
2005-02-26 21:44:23 +00:00
|
|
|
}
|
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2018-06-13 19:06:27 +02:00
|
|
|
png_read_end (pp, info);
|
|
|
|
|
2007-02-16 13:14:20 +00:00
|
|
|
/* Switch back to default error handler */
|
|
|
|
png_set_error_fn (pp, NULL, NULL, NULL);
|
|
|
|
|
2003-07-03 00:30:33 +00:00
|
|
|
if (png_get_text (pp, info, &text, &num_texts))
|
|
|
|
{
|
|
|
|
gchar *comment = NULL;
|
|
|
|
|
2009-07-16 22:24:59 +02:00
|
|
|
for (i = 0; i < num_texts && !comment; i++, text++)
|
2005-02-26 21:44:23 +00:00
|
|
|
{
|
|
|
|
if (text->key == NULL || strcmp (text->key, "Comment"))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (text->text_length > 0) /* tEXt */
|
|
|
|
{
|
|
|
|
comment = g_convert (text->text, -1,
|
|
|
|
"UTF-8", "ISO-8859-1",
|
|
|
|
NULL, NULL, NULL);
|
|
|
|
}
|
|
|
|
else if (g_utf8_validate (text->text, -1, NULL))
|
|
|
|
{ /* iTXt */
|
|
|
|
comment = g_strdup (text->text);
|
|
|
|
}
|
|
|
|
}
|
2003-07-03 00:30:33 +00:00
|
|
|
|
|
|
|
if (comment && *comment)
|
2005-02-26 21:44:23 +00:00
|
|
|
{
|
|
|
|
GimpParasite *parasite;
|
2003-07-03 00:30:33 +00:00
|
|
|
|
2005-02-26 21:44:23 +00:00
|
|
|
parasite = gimp_parasite_new ("gimp-comment",
|
|
|
|
GIMP_PARASITE_PERSISTENT,
|
|
|
|
strlen (comment) + 1, comment);
|
2019-08-16 23:48:56 +02:00
|
|
|
gimp_image_attach_parasite ((GimpImage *) image, parasite);
|
2005-02-26 21:44:23 +00:00
|
|
|
gimp_parasite_free (parasite);
|
|
|
|
}
|
2003-07-03 00:30:33 +00:00
|
|
|
|
|
|
|
g_free (comment);
|
|
|
|
}
|
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
/*
|
|
|
|
* Done with the file...
|
|
|
|
*/
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2006-04-23 07:19:41 +00:00
|
|
|
png_destroy_read_struct (&pp, &info, NULL);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
g_free (pixel);
|
|
|
|
g_free (pixels);
|
2012-04-26 03:16:19 +02:00
|
|
|
g_object_unref (buffer);
|
2002-10-14 15:32:47 +00:00
|
|
|
free (pp);
|
|
|
|
free (info);
|
1999-10-02 22:48:44 +00:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
fclose (fp);
|
1999-10-02 22:48:44 +00:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
if (trns)
|
1999-10-02 22:48:44 +00:00
|
|
|
{
|
2012-04-29 03:34:43 +02:00
|
|
|
GeglBufferIterator *iter;
|
|
|
|
gint n_components;
|
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
gimp_layer_add_alpha (layer);
|
2019-08-16 23:48:56 +02:00
|
|
|
buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (layer));
|
2012-04-29 03:34:43 +02:00
|
|
|
file_format = gegl_buffer_get_format (buffer);
|
2002-10-14 15:32:47 +00:00
|
|
|
|
2012-04-29 03:34:43 +02:00
|
|
|
iter = gegl_buffer_iterator_new (buffer, NULL, 0, file_format,
|
2018-09-11 02:01:47 +02:00
|
|
|
GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE, 1);
|
2012-04-29 03:34:43 +02:00
|
|
|
n_components = babl_format_get_n_components (file_format);
|
|
|
|
g_warn_if_fail (n_components == 2);
|
2002-10-14 15:32:47 +00:00
|
|
|
|
2012-04-29 03:34:43 +02:00
|
|
|
while (gegl_buffer_iterator_next (iter))
|
2002-10-14 15:32:47 +00:00
|
|
|
{
|
2018-09-11 02:01:47 +02:00
|
|
|
guchar *data = iter->items[0].data;
|
2013-09-12 17:39:59 -07:00
|
|
|
gint length = iter->length;
|
1999-10-02 22:48:44 +00:00
|
|
|
|
2013-09-12 17:39:59 -07:00
|
|
|
while (length--)
|
2002-10-14 15:32:47 +00:00
|
|
|
{
|
2012-07-09 18:04:08 +02:00
|
|
|
data[1] = alpha[data[0]];
|
1999-10-02 22:48:44 +00:00
|
|
|
|
2012-04-29 03:34:43 +02:00
|
|
|
data += n_components;
|
|
|
|
}
|
2002-10-14 15:32:47 +00:00
|
|
|
}
|
2008-08-17 12:13:46 +00:00
|
|
|
|
2012-04-28 17:46:32 +02:00
|
|
|
g_object_unref (buffer);
|
1999-10-02 22:48:44 +00:00
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2023-11-08 17:27:13 +00:00
|
|
|
/* If any safe-to-copy chunks were saved,
|
|
|
|
* store them in the image as parasite */
|
|
|
|
if (safe_to_copy_chunks)
|
|
|
|
{
|
|
|
|
GSList *iter;
|
|
|
|
|
|
|
|
for (iter = safe_to_copy_chunks; iter; iter = iter->next)
|
|
|
|
{
|
|
|
|
GimpParasite *parasite = iter->data;
|
|
|
|
|
|
|
|
gimp_image_attach_parasite ((GimpImage *) image, parasite);
|
|
|
|
gimp_parasite_free (parasite);
|
|
|
|
}
|
|
|
|
|
|
|
|
g_slist_free (safe_to_copy_chunks);
|
|
|
|
}
|
|
|
|
|
2019-08-16 23:48:56 +02:00
|
|
|
return (GimpImage *) image;
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|
|
|
|
|
2012-02-09 20:09:14 +02:00
|
|
|
/*
|
|
|
|
* 'offsets_dialog ()' - Asks the user about offsets when loading.
|
|
|
|
*/
|
|
|
|
static gboolean
|
|
|
|
offsets_dialog (gint offset_x,
|
|
|
|
gint offset_y)
|
|
|
|
{
|
|
|
|
GtkWidget *dialog;
|
|
|
|
GtkWidget *hbox;
|
|
|
|
GtkWidget *image;
|
|
|
|
GtkWidget *label;
|
|
|
|
gchar *message;
|
|
|
|
gboolean run;
|
|
|
|
|
2019-09-20 19:39:00 +02:00
|
|
|
gimp_ui_init (PLUG_IN_BINARY);
|
2012-02-09 20:09:14 +02:00
|
|
|
|
|
|
|
dialog = gimp_dialog_new (_("Apply PNG Offset"), PLUG_IN_ROLE,
|
|
|
|
NULL, 0,
|
|
|
|
gimp_standard_help_func, LOAD_PROC,
|
|
|
|
|
|
|
|
_("Ignore PNG offset"), GTK_RESPONSE_NO,
|
|
|
|
_("Apply PNG offset to layer"), GTK_RESPONSE_YES,
|
|
|
|
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_YES);
|
2018-05-10 17:04:37 +02:00
|
|
|
gimp_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
|
2012-02-09 20:09:14 +02:00
|
|
|
GTK_RESPONSE_YES,
|
|
|
|
GTK_RESPONSE_NO,
|
|
|
|
-1);
|
|
|
|
|
|
|
|
gimp_window_set_transient (GTK_WINDOW (dialog));
|
|
|
|
gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
|
|
|
|
|
|
|
|
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
|
|
|
|
gtk_container_set_border_width (GTK_CONTAINER (hbox), 12);
|
|
|
|
gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
|
|
|
|
hbox, FALSE, FALSE, 0);
|
|
|
|
gtk_widget_show (hbox);
|
|
|
|
|
2017-03-05 16:01:59 +01:00
|
|
|
image = gtk_image_new_from_icon_name (GIMP_ICON_DIALOG_QUESTION,
|
2014-05-12 00:20:48 +02:00
|
|
|
GTK_ICON_SIZE_DIALOG);
|
2018-04-29 01:51:36 +02:00
|
|
|
gtk_widget_set_valign (image, GTK_ALIGN_START);
|
2012-02-09 20:09:14 +02:00
|
|
|
gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
|
|
|
|
gtk_widget_show (image);
|
|
|
|
|
|
|
|
message = g_strdup_printf (_("The PNG image you are importing specifies an "
|
|
|
|
"offset of %d, %d. Do you want to apply "
|
|
|
|
"this offset to the layer?"),
|
|
|
|
offset_x, offset_y);
|
|
|
|
label = gtk_label_new (message);
|
2016-09-08 19:11:20 +02:00
|
|
|
gtk_label_set_yalign (GTK_LABEL (label), 0.0);
|
2012-02-09 20:09:14 +02:00
|
|
|
gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
|
|
|
|
gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
|
|
|
|
gtk_widget_show (label);
|
|
|
|
|
|
|
|
gtk_widget_show (dialog);
|
|
|
|
|
|
|
|
run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_YES);
|
|
|
|
|
|
|
|
gtk_widget_destroy (dialog);
|
|
|
|
|
|
|
|
return run;
|
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2006-08-22 17:40:04 +00:00
|
|
|
/*
|
2024-04-13 15:10:25 +00:00
|
|
|
* 'export_image ()' - Export the specified image to a PNG file.
|
1997-11-24 22:05:25 +00:00
|
|
|
*/
|
|
|
|
|
2019-09-25 01:06:16 +02:00
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
gboolean has_trns;
|
|
|
|
png_bytep trans;
|
|
|
|
int num_trans;
|
|
|
|
gboolean has_plte;
|
|
|
|
png_colorp palette;
|
|
|
|
int num_palette;
|
|
|
|
}
|
|
|
|
PngGlobals;
|
|
|
|
|
|
|
|
static PngGlobals pngg;
|
|
|
|
|
2005-06-24 11:31:49 +00:00
|
|
|
static gboolean
|
2024-04-13 15:10:25 +00:00
|
|
|
export_image (GFile *file,
|
|
|
|
GimpImage *image,
|
|
|
|
GimpDrawable *drawable,
|
|
|
|
GimpImage *orig_image,
|
|
|
|
GObject *config,
|
|
|
|
gint *bits_per_sample,
|
|
|
|
gboolean report_progress,
|
|
|
|
GError **error)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
2016-09-04 22:12:30 +02:00
|
|
|
gint i, k; /* Looping vars */
|
|
|
|
gint bpp = 0; /* Bytes per pixel */
|
|
|
|
gint type; /* Type of drawable/layer */
|
|
|
|
gint num_passes; /* Number of interlace passes in file */
|
|
|
|
gint pass; /* Current pass in file */
|
|
|
|
gint tile_height; /* Height of tile in GIMP */
|
|
|
|
gint width; /* image width */
|
|
|
|
gint height; /* image height */
|
|
|
|
gint begin; /* Beginning tile row */
|
|
|
|
gint end; /* Ending tile row */
|
|
|
|
gint num; /* Number of rows to load */
|
|
|
|
FILE *fp; /* File pointer */
|
|
|
|
GimpColorProfile *profile = NULL; /* Color profile */
|
2023-11-08 17:27:13 +00:00
|
|
|
gchar **parasites; /* Safe-to-copy chunks */
|
2019-05-25 14:01:55 +02:00
|
|
|
gboolean out_linear; /* Save linear RGB */
|
2016-09-04 22:12:30 +02:00
|
|
|
GeglBuffer *buffer; /* GEGL buffer for layer */
|
2019-07-16 22:50:55 +02:00
|
|
|
const Babl *file_format = NULL; /* BABL format of file */
|
|
|
|
const gchar *encoding;
|
2019-06-27 16:20:53 +02:00
|
|
|
const Babl *space;
|
2016-09-04 22:12:30 +02:00
|
|
|
png_structp pp; /* PNG read pointer */
|
|
|
|
png_infop info; /* PNG info pointer */
|
|
|
|
gint offx, offy; /* Drawable offsets from origin */
|
|
|
|
guchar **pixels; /* Pixel rows */
|
|
|
|
guchar *fixed; /* Fixed-up pixel data */
|
|
|
|
guchar *pixel; /* Pixel data */
|
|
|
|
gdouble xres, yres; /* GIMP resolution (dpi) */
|
|
|
|
png_time mod_time; /* Modification time (ie NOW) */
|
|
|
|
time_t cutime; /* Time since epoch */
|
|
|
|
struct tm *gmt; /* GMT broken down */
|
|
|
|
gint color_type; /* PNG color type */
|
2019-05-25 14:01:55 +02:00
|
|
|
gint bit_depth; /* Default to bit depth 16 */
|
2016-09-04 22:12:30 +02:00
|
|
|
|
|
|
|
guchar remap[256]; /* Re-mapping for the palette */
|
|
|
|
|
|
|
|
png_textp text = NULL;
|
|
|
|
|
2019-09-25 01:06:16 +02:00
|
|
|
gboolean save_interlaced;
|
|
|
|
gboolean save_bkgd;
|
|
|
|
gboolean save_offs;
|
|
|
|
gboolean save_phys;
|
|
|
|
gboolean save_time;
|
|
|
|
gboolean save_comment;
|
2019-10-22 12:24:12 +02:00
|
|
|
gchar *comment;
|
2019-09-25 01:06:16 +02:00
|
|
|
gboolean save_transp_pixels;
|
2022-08-17 18:05:13 +00:00
|
|
|
gboolean optimize_palette;
|
2019-09-25 01:06:16 +02:00
|
|
|
gint compression_level;
|
|
|
|
PngExportFormat export_format;
|
|
|
|
gboolean save_profile;
|
|
|
|
|
2019-10-09 22:51:34 +02:00
|
|
|
#if !defined(PNG_iCCP_SUPPORTED)
|
|
|
|
g_object_set (config,
|
|
|
|
"save-color-profile", FALSE,
|
|
|
|
NULL);
|
|
|
|
#endif
|
|
|
|
|
2019-09-25 01:06:16 +02:00
|
|
|
g_object_get (config,
|
2019-09-25 12:50:29 +02:00
|
|
|
"interlaced", &save_interlaced,
|
|
|
|
"bkgd", &save_bkgd,
|
|
|
|
"offs", &save_offs,
|
|
|
|
"phys", &save_phys,
|
|
|
|
"time", &save_time,
|
2019-10-10 01:32:28 +02:00
|
|
|
"save-comment", &save_comment,
|
2020-06-15 23:54:09 +02:00
|
|
|
"gimp-comment", &comment,
|
2019-09-25 12:50:29 +02:00
|
|
|
"save-transparent", &save_transp_pixels,
|
2022-08-17 18:05:13 +00:00
|
|
|
"optimize-palette", &optimize_palette,
|
2019-09-25 12:50:29 +02:00
|
|
|
"compression", &compression_level,
|
|
|
|
"save-color-profile", &save_profile,
|
2019-09-25 01:06:16 +02:00
|
|
|
NULL);
|
|
|
|
|
2023-08-03 21:20:14 +02:00
|
|
|
export_format = gimp_procedure_config_get_choice_id (GIMP_PROCEDURE_CONFIG (config), "format");
|
|
|
|
|
2019-05-25 14:01:55 +02:00
|
|
|
out_linear = FALSE;
|
2019-08-16 23:48:56 +02:00
|
|
|
space = gimp_drawable_get_format (drawable);
|
2019-10-09 22:51:34 +02:00
|
|
|
|
2016-09-04 22:12:30 +02:00
|
|
|
#if defined(PNG_iCCP_SUPPORTED)
|
2019-06-07 18:18:33 +02:00
|
|
|
/* If no profile is written: export as sRGB.
|
|
|
|
* If manually assigned profile written: follow its TRC.
|
|
|
|
* If default profile written:
|
|
|
|
* - when export as auto or 16-bit: follow the storage TRC.
|
|
|
|
* - when export from 8-bit storage: follow the storage TRC.
|
|
|
|
* - when converting high bit depth to 8-bit: export as sRGB.
|
|
|
|
*/
|
2019-09-25 01:06:16 +02:00
|
|
|
if (save_profile)
|
2019-05-25 14:01:55 +02:00
|
|
|
{
|
2019-08-16 23:48:56 +02:00
|
|
|
profile = gimp_image_get_color_profile (orig_image);
|
2019-06-07 18:18:33 +02:00
|
|
|
|
2019-10-09 22:51:34 +02:00
|
|
|
if (profile ||
|
2019-09-25 01:06:16 +02:00
|
|
|
export_format == PNG_FORMAT_AUTO ||
|
|
|
|
export_format == PNG_FORMAT_RGB16 ||
|
|
|
|
export_format == PNG_FORMAT_RGBA16 ||
|
|
|
|
export_format == PNG_FORMAT_GRAY16 ||
|
|
|
|
export_format == PNG_FORMAT_GRAYA16 ||
|
2019-08-16 23:48:56 +02:00
|
|
|
gimp_image_get_precision (image) == GIMP_PRECISION_U8_LINEAR ||
|
|
|
|
gimp_image_get_precision (image) == GIMP_PRECISION_U8_NON_LINEAR ||
|
|
|
|
gimp_image_get_precision (image) == GIMP_PRECISION_U8_PERCEPTUAL)
|
2019-06-07 18:18:33 +02:00
|
|
|
{
|
|
|
|
if (! profile)
|
2019-08-16 23:48:56 +02:00
|
|
|
profile = gimp_image_get_effective_color_profile (orig_image);
|
2019-06-07 18:18:33 +02:00
|
|
|
out_linear = (gimp_color_profile_is_linear (profile));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* When converting higher bit depth work image into 8-bit,
|
2019-06-26 17:26:06 +02:00
|
|
|
* with no manually assigned profile, make sure the result is
|
|
|
|
* sRGB.
|
|
|
|
*/
|
2019-08-16 23:48:56 +02:00
|
|
|
profile = gimp_image_get_effective_color_profile (orig_image);
|
2019-05-25 14:01:55 +02:00
|
|
|
|
2019-06-07 18:18:33 +02:00
|
|
|
if (gimp_color_profile_is_linear (profile))
|
|
|
|
{
|
|
|
|
GimpColorProfile *saved_profile;
|
2019-05-25 14:01:55 +02:00
|
|
|
|
2019-06-07 18:18:33 +02:00
|
|
|
saved_profile = gimp_color_profile_new_srgb_trc_from_color_profile (profile);
|
|
|
|
g_object_unref (profile);
|
|
|
|
profile = saved_profile;
|
|
|
|
}
|
2019-05-25 14:01:55 +02:00
|
|
|
}
|
2019-06-27 16:20:53 +02:00
|
|
|
|
|
|
|
space = gimp_color_profile_get_space (profile,
|
|
|
|
GIMP_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC,
|
|
|
|
error);
|
|
|
|
if (error && *error)
|
|
|
|
{
|
|
|
|
/* XXX: the profile space should normally be the same one as
|
|
|
|
* the drawable's so let's continue with it. We were mostly
|
|
|
|
* getting the profile space to be complete. Still let's
|
|
|
|
* display the error to standard error channel because if the
|
|
|
|
* space could not be extracted, there is a problem somewhere!
|
|
|
|
*/
|
|
|
|
g_printerr ("%s: error getting the profile space: %s",
|
|
|
|
G_STRFUNC, (*error)->message);
|
|
|
|
g_clear_error (error);
|
2019-08-16 23:48:56 +02:00
|
|
|
space = gimp_drawable_get_format (drawable);
|
2019-06-27 16:20:53 +02:00
|
|
|
}
|
2019-05-25 14:01:55 +02:00
|
|
|
}
|
2016-09-04 22:12:30 +02:00
|
|
|
#endif
|
|
|
|
|
2019-05-25 14:01:55 +02:00
|
|
|
/* We save as 8-bit PNG only if:
|
|
|
|
* (1) Work image is 8-bit linear with linear profile to be saved.
|
|
|
|
* (2) Work image is 8-bit non-linear or perceptual with or without
|
|
|
|
* profile.
|
|
|
|
*/
|
|
|
|
bit_depth = 16;
|
2019-08-16 23:48:56 +02:00
|
|
|
switch (gimp_image_get_precision (image))
|
2016-09-04 22:12:30 +02:00
|
|
|
{
|
|
|
|
case GIMP_PRECISION_U8_LINEAR:
|
2019-05-25 14:01:55 +02:00
|
|
|
if (out_linear)
|
2016-09-04 22:12:30 +02:00
|
|
|
bit_depth = 8;
|
|
|
|
break;
|
|
|
|
|
Initial space invasion commit in GIMP
All babl formats now have a space equivalent to a color profile,
determining the format's primaries and TRCs. This commit makes GIMP
aware of this.
libgimp:
- enum GimpPrecision: rename GAMMA values to NON_LINEAR and keep GAMMA
as deprecated aliases, add PERCEPTUAL values so we now have LINEAR,
NON_LINEAR and PERCPTUAL for each encoding, matching the babl
encoding variants RGB, R'G'B' and R~G~B~.
- gimp_color_transform_can_gegl_copy() now returns TRUE if both
profiles can return a babl space, increasing the amount of fast babl
color conversions significantly.
- TODO: no solution yet for getting libgimp drawable proxy buffers in
the right format with space.
plug-ins:
- follow the GimpPrecision change.
- TODO: everything else unchanged and partly broken or sub-optimal,
like setting a new image's color profile too late.
app:
- add enum GimpTRCType { LINEAR, NON_LINEAR, PERCEPTUAL } as
replacement for all "linear" booleans.
- change gimp-babl functions to take babl spaces and GimpTRCType
parameters and support all sorts of new perceptual ~ formats.
- a lot of places changed in the early days of goat invasion didn't
take advantage of gimp-babl utility functions and constructed
formats manually. They all needed revisiting and many now use much
simpler code calling gimp-babl API.
- change gimp_babl_format_get_color_profile() to really extract a
newly allocated color profile from the format, and add
gimp_babl_get_builtin_color_profile() which does the same as
gimp_babl_format_get_color_profile() did before. Visited all callers
to decide whether they are looking for the format's actual profile,
or for one of the builtin profiles, simplifying code that only needs
builtin profiles.
- drawables have a new get_space_api(), get_linear() is now get_trc().
- images now have a "layer space" and an API to get it,
gimp_image_get_layer_format() returns formats in that space.
- an image's layer space is created from the image's color profile,
change gimpimage-color-profile to deal with that correctly
- change many babl_format() calls to babl_format_with_space() and take
the space from passed formats or drawables
- add function gimp_layer_fix_format_space() which replaces the
layer's buffer with one that has the image's layer format, but
doesn't change pixel values
- use gimp_layer_fix_format_space() to make sure layers loaded from
XCF and created by plug-ins have the right space when added to the
image, because it's impossible to always assign the right space upon
layer creation
- "assign color profile" and "discard color profile" now require use
of gimp_layer_fix_format_space() too because the profile is now
embedded in all formats via the space. Add
gimp_image_assign_color_profile() which does all that and call it
instead of a simple gimp_image_set_color_profile(), also from the
PDB set-color-profile functions, which are essentially "assign" and
"discard" calls.
- generally, make sure a new image's color profile is set before
adding layers to it, gimp_image_set_color_profile() is more than
before considered know-what-you-are-doing API.
- take special precaution in all places that call
gimp_drawable_convert_type(), we now must pass a new_profile from
all callers that convert layers within the same image (such as
image_convert_type, image_convert_precision), because the layer's
new space can't be determined from the image's layer format during
the call.
- change all "linear" properties to "trc", in all config objects like
for levels and curves, in the histogram, in the widgets. This results
in some GUI that now has three choices instead of two.
TODO: we might want to reduce that back to two later.
- keep "linear" boolean properties around as compat if needed for file
pasring, but always convert the parsed parsed boolean to
GimpTRCType.
- TODO: the image's "enable color management" switch is currently
broken, will fix that in another commit.
2018-07-21 14:23:01 +02:00
|
|
|
case GIMP_PRECISION_U8_NON_LINEAR:
|
|
|
|
case GIMP_PRECISION_U8_PERCEPTUAL:
|
2019-06-07 18:18:33 +02:00
|
|
|
if (! out_linear)
|
|
|
|
bit_depth = 8;
|
2019-05-25 14:01:55 +02:00
|
|
|
break;
|
2016-09-04 22:12:30 +02:00
|
|
|
|
Initial space invasion commit in GIMP
All babl formats now have a space equivalent to a color profile,
determining the format's primaries and TRCs. This commit makes GIMP
aware of this.
libgimp:
- enum GimpPrecision: rename GAMMA values to NON_LINEAR and keep GAMMA
as deprecated aliases, add PERCEPTUAL values so we now have LINEAR,
NON_LINEAR and PERCPTUAL for each encoding, matching the babl
encoding variants RGB, R'G'B' and R~G~B~.
- gimp_color_transform_can_gegl_copy() now returns TRUE if both
profiles can return a babl space, increasing the amount of fast babl
color conversions significantly.
- TODO: no solution yet for getting libgimp drawable proxy buffers in
the right format with space.
plug-ins:
- follow the GimpPrecision change.
- TODO: everything else unchanged and partly broken or sub-optimal,
like setting a new image's color profile too late.
app:
- add enum GimpTRCType { LINEAR, NON_LINEAR, PERCEPTUAL } as
replacement for all "linear" booleans.
- change gimp-babl functions to take babl spaces and GimpTRCType
parameters and support all sorts of new perceptual ~ formats.
- a lot of places changed in the early days of goat invasion didn't
take advantage of gimp-babl utility functions and constructed
formats manually. They all needed revisiting and many now use much
simpler code calling gimp-babl API.
- change gimp_babl_format_get_color_profile() to really extract a
newly allocated color profile from the format, and add
gimp_babl_get_builtin_color_profile() which does the same as
gimp_babl_format_get_color_profile() did before. Visited all callers
to decide whether they are looking for the format's actual profile,
or for one of the builtin profiles, simplifying code that only needs
builtin profiles.
- drawables have a new get_space_api(), get_linear() is now get_trc().
- images now have a "layer space" and an API to get it,
gimp_image_get_layer_format() returns formats in that space.
- an image's layer space is created from the image's color profile,
change gimpimage-color-profile to deal with that correctly
- change many babl_format() calls to babl_format_with_space() and take
the space from passed formats or drawables
- add function gimp_layer_fix_format_space() which replaces the
layer's buffer with one that has the image's layer format, but
doesn't change pixel values
- use gimp_layer_fix_format_space() to make sure layers loaded from
XCF and created by plug-ins have the right space when added to the
image, because it's impossible to always assign the right space upon
layer creation
- "assign color profile" and "discard color profile" now require use
of gimp_layer_fix_format_space() too because the profile is now
embedded in all formats via the space. Add
gimp_image_assign_color_profile() which does all that and call it
instead of a simple gimp_image_set_color_profile(), also from the
PDB set-color-profile functions, which are essentially "assign" and
"discard" calls.
- generally, make sure a new image's color profile is set before
adding layers to it, gimp_image_set_color_profile() is more than
before considered know-what-you-are-doing API.
- take special precaution in all places that call
gimp_drawable_convert_type(), we now must pass a new_profile from
all callers that convert layers within the same image (such as
image_convert_type, image_convert_precision), because the layer's
new space can't be determined from the image's layer format during
the call.
- change all "linear" properties to "trc", in all config objects like
for levels and curves, in the histogram, in the widgets. This results
in some GUI that now has three choices instead of two.
TODO: we might want to reduce that back to two later.
- keep "linear" boolean properties around as compat if needed for file
pasring, but always convert the parsed parsed boolean to
GimpTRCType.
- TODO: the image's "enable color management" switch is currently
broken, will fix that in another commit.
2018-07-21 14:23:01 +02:00
|
|
|
default:
|
2016-09-04 22:12:30 +02:00
|
|
|
break;
|
|
|
|
}
|
2012-04-26 23:07:35 +02:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
pp = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
2011-09-21 17:08:53 +05:30
|
|
|
if (!pp)
|
|
|
|
{
|
2011-09-27 19:22:50 +05:30
|
|
|
/* this could happen if the compile time and run-time libpng
|
2016-09-04 22:12:30 +02:00
|
|
|
* versions do not match.
|
|
|
|
*/
|
2022-03-24 14:18:57 +01:00
|
|
|
g_set_error (error, G_FILE_ERROR, 0,
|
2016-02-16 02:35:43 +01:00
|
|
|
_("Error creating PNG write struct while exporting '%s'."),
|
2019-09-11 21:48:34 +02:00
|
|
|
gimp_file_get_utf8_name (file));
|
2011-09-21 17:08:53 +05:30
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
info = png_create_info_struct (pp);
|
2018-03-26 21:25:10 +02:00
|
|
|
if (! info)
|
|
|
|
{
|
2022-03-24 14:18:57 +01:00
|
|
|
g_set_error (error, G_FILE_ERROR, 0,
|
2018-03-26 21:25:10 +02:00
|
|
|
_("Error while exporting '%s'. Could not create PNG header info structure."),
|
2019-09-11 21:48:34 +02:00
|
|
|
gimp_file_get_utf8_name (file));
|
2018-03-26 21:25:10 +02:00
|
|
|
return FALSE;
|
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2011-04-25 20:09:15 +05:30
|
|
|
if (setjmp (png_jmpbuf (pp)))
|
2002-10-14 15:32:47 +00:00
|
|
|
{
|
2022-03-24 14:18:57 +01:00
|
|
|
g_set_error (error, G_FILE_ERROR, 0,
|
2016-02-16 02:35:43 +01:00
|
|
|
_("Error while exporting '%s'. Could not export image."),
|
2019-09-11 21:48:34 +02:00
|
|
|
gimp_file_get_utf8_name (file));
|
2005-06-24 11:31:49 +00:00
|
|
|
return FALSE;
|
2002-10-14 15:32:47 +00:00
|
|
|
}
|
2000-04-25 03:57:03 +00:00
|
|
|
|
2014-02-16 10:41:12 -05:00
|
|
|
#ifdef PNG_BENIGN_ERRORS_SUPPORTED
|
2014-02-15 22:55:18 -05:00
|
|
|
/* Change some libpng errors to warnings (e.g. bug 721135) */
|
|
|
|
png_set_benign_errors (pp, TRUE);
|
2016-08-26 15:11:12 -04:00
|
|
|
|
|
|
|
/* bug 765850 */
|
|
|
|
png_set_option (pp, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON);
|
2014-02-16 10:41:12 -05:00
|
|
|
#endif
|
2014-02-15 22:55:18 -05:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
/*
|
|
|
|
* Open the file and initialize the PNG write "engine"...
|
|
|
|
*/
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2023-08-06 00:59:19 +02:00
|
|
|
if (report_progress)
|
2021-01-27 19:06:57 -05:00
|
|
|
gimp_progress_init_printf (_("Exporting '%s'"),
|
|
|
|
gimp_file_get_utf8_name (file));
|
2014-07-23 16:39:00 +02:00
|
|
|
|
2021-10-01 18:14:14 +02:00
|
|
|
fp = g_fopen (g_file_peek_path (file), "wb");
|
2019-09-11 21:48:34 +02:00
|
|
|
|
|
|
|
if (! fp)
|
2002-10-14 15:32:47 +00:00
|
|
|
{
|
2008-08-17 12:13:46 +00:00
|
|
|
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
|
|
|
|
_("Could not open '%s' for writing: %s"),
|
2019-09-11 21:48:34 +02:00
|
|
|
gimp_file_get_utf8_name (file), g_strerror (errno));
|
2005-06-24 11:31:49 +00:00
|
|
|
return FALSE;
|
2002-10-14 15:32:47 +00:00
|
|
|
}
|
2003-11-02 16:52:26 +00:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
png_init_io (pp, fp);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
/*
|
2012-04-26 23:07:35 +02:00
|
|
|
* Get the buffer for the current image...
|
2002-10-14 15:32:47 +00:00
|
|
|
*/
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2019-08-16 23:48:56 +02:00
|
|
|
buffer = gimp_drawable_get_buffer (drawable);
|
2016-09-04 22:12:30 +02:00
|
|
|
width = gegl_buffer_get_width (buffer);
|
2012-04-26 23:07:35 +02:00
|
|
|
height = gegl_buffer_get_height (buffer);
|
2019-08-16 23:48:56 +02:00
|
|
|
type = gimp_drawable_type (drawable);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2003-11-02 16:52:26 +00:00
|
|
|
/*
|
2002-10-14 15:32:47 +00:00
|
|
|
* Initialise remap[]
|
|
|
|
*/
|
|
|
|
for (i = 0; i < 256; i++)
|
2003-07-03 15:13:29 +00:00
|
|
|
remap[i] = i;
|
2002-10-14 14:52:53 +00:00
|
|
|
|
2019-09-25 01:06:16 +02:00
|
|
|
if (export_format == PNG_FORMAT_AUTO)
|
2002-10-14 15:32:47 +00:00
|
|
|
{
|
2016-12-30 14:07:05 +01:00
|
|
|
/*
|
|
|
|
* Set color type and remember bytes per pixel count
|
|
|
|
*/
|
|
|
|
|
|
|
|
switch (type)
|
|
|
|
{
|
|
|
|
case GIMP_RGB_IMAGE:
|
|
|
|
color_type = PNG_COLOR_TYPE_RGB;
|
|
|
|
if (bit_depth == 8)
|
|
|
|
{
|
2019-05-25 14:01:55 +02:00
|
|
|
if (out_linear)
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "RGB u8";
|
2016-12-30 14:07:05 +01:00
|
|
|
else
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "R'G'B' u8";
|
2016-12-30 14:07:05 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-05-25 14:01:55 +02:00
|
|
|
if (out_linear)
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "RGB u16";
|
2016-12-30 14:07:05 +01:00
|
|
|
else
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "R'G'B' u16";
|
2016-12-30 14:07:05 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GIMP_RGBA_IMAGE:
|
|
|
|
color_type = PNG_COLOR_TYPE_RGB_ALPHA;
|
|
|
|
if (bit_depth == 8)
|
|
|
|
{
|
2019-05-25 14:01:55 +02:00
|
|
|
if (out_linear)
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "RGBA u8";
|
2016-12-30 14:07:05 +01:00
|
|
|
else
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "R'G'B'A u8";
|
2016-12-30 14:07:05 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-05-25 14:01:55 +02:00
|
|
|
if (out_linear)
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "RGBA u16";
|
2016-12-30 14:07:05 +01:00
|
|
|
else
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "R'G'B'A u16";
|
2016-12-30 14:07:05 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GIMP_GRAY_IMAGE:
|
|
|
|
color_type = PNG_COLOR_TYPE_GRAY;
|
|
|
|
if (bit_depth == 8)
|
|
|
|
{
|
2019-05-25 14:01:55 +02:00
|
|
|
if (out_linear)
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "Y u8";
|
2016-12-30 14:07:05 +01:00
|
|
|
else
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "Y' u8";
|
2016-12-30 14:07:05 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-05-25 14:01:55 +02:00
|
|
|
if (out_linear)
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "Y u16";
|
2016-12-30 14:07:05 +01:00
|
|
|
else
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "Y' u16";
|
2016-12-30 14:07:05 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GIMP_GRAYA_IMAGE:
|
|
|
|
color_type = PNG_COLOR_TYPE_GRAY_ALPHA;
|
|
|
|
if (bit_depth == 8)
|
|
|
|
{
|
2019-05-25 14:01:55 +02:00
|
|
|
if (out_linear)
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "YA u8";
|
2016-12-30 14:07:05 +01:00
|
|
|
else
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "Y'A u8";
|
2016-12-30 14:07:05 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-05-25 14:01:55 +02:00
|
|
|
if (out_linear)
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "YA u16";
|
2016-12-30 14:07:05 +01:00
|
|
|
else
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "Y'A u16";
|
2016-12-30 14:07:05 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GIMP_INDEXED_IMAGE:
|
|
|
|
color_type = PNG_COLOR_TYPE_PALETTE;
|
2019-08-16 23:48:56 +02:00
|
|
|
file_format = gimp_drawable_get_format (drawable);
|
2016-12-30 14:07:05 +01:00
|
|
|
pngg.has_plte = TRUE;
|
2019-08-16 23:48:56 +02:00
|
|
|
pngg.palette = (png_colorp) gimp_image_get_colormap (image,
|
2023-05-23 23:37:46 +02:00
|
|
|
NULL,
|
2016-12-30 14:07:05 +01:00
|
|
|
&pngg.num_palette);
|
2022-08-17 18:05:13 +00:00
|
|
|
if (optimize_palette)
|
|
|
|
bit_depth = get_bit_depth_for_palette (pngg.num_palette);
|
2016-12-30 14:07:05 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case GIMP_INDEXEDA_IMAGE:
|
|
|
|
color_type = PNG_COLOR_TYPE_PALETTE;
|
2019-08-16 23:48:56 +02:00
|
|
|
file_format = gimp_drawable_get_format (drawable);
|
2016-12-30 14:07:05 +01:00
|
|
|
/* fix up transparency */
|
2022-08-17 18:05:13 +00:00
|
|
|
if (optimize_palette)
|
|
|
|
bit_depth = respin_cmap (pp, info, remap, image, drawable);
|
|
|
|
else
|
|
|
|
respin_cmap (pp, info, remap, image, drawable);
|
2016-12-30 14:07:05 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2022-03-24 14:18:57 +01:00
|
|
|
g_set_error (error, G_FILE_ERROR, 0, "Image type can't be exported as PNG");
|
2016-12-30 14:07:05 +01:00
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-09-25 01:06:16 +02:00
|
|
|
switch (export_format)
|
2019-06-16 14:52:48 +02:00
|
|
|
{
|
2016-12-30 14:07:05 +01:00
|
|
|
case PNG_FORMAT_RGB8:
|
|
|
|
color_type = PNG_COLOR_TYPE_RGB;
|
2019-06-07 18:18:33 +02:00
|
|
|
if (out_linear)
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "RGB u8";
|
2019-06-07 18:18:33 +02:00
|
|
|
else
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "R'G'B' u8";
|
2016-12-30 14:07:05 +01:00
|
|
|
bit_depth = 8;
|
|
|
|
break;
|
|
|
|
case PNG_FORMAT_GRAY8:
|
|
|
|
color_type = PNG_COLOR_TYPE_GRAY;
|
2019-06-07 18:18:33 +02:00
|
|
|
if (out_linear)
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "Y u8";
|
2019-06-07 18:18:33 +02:00
|
|
|
else
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "Y' u8";
|
2016-12-30 14:07:05 +01:00
|
|
|
bit_depth = 8;
|
|
|
|
break;
|
|
|
|
case PNG_FORMAT_RGBA8:
|
|
|
|
color_type = PNG_COLOR_TYPE_RGB_ALPHA;
|
2019-06-07 18:18:33 +02:00
|
|
|
if (out_linear)
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "RGBA u8";
|
2019-06-07 18:18:33 +02:00
|
|
|
else
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "R'G'B'A u8";
|
2016-12-30 14:07:05 +01:00
|
|
|
bit_depth = 8;
|
|
|
|
break;
|
|
|
|
case PNG_FORMAT_GRAYA8:
|
|
|
|
color_type = PNG_COLOR_TYPE_GRAY_ALPHA;
|
2019-06-07 18:18:33 +02:00
|
|
|
if (out_linear)
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "YA u8";
|
2019-06-07 18:18:33 +02:00
|
|
|
else
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "Y'A u8";
|
2016-12-30 14:07:05 +01:00
|
|
|
bit_depth = 8;
|
|
|
|
break;
|
|
|
|
case PNG_FORMAT_RGB16:
|
|
|
|
color_type = PNG_COLOR_TYPE_RGB;
|
2019-06-07 18:18:33 +02:00
|
|
|
if (out_linear)
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "RGB u16";
|
2019-06-07 18:18:33 +02:00
|
|
|
else
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "R'G'B' u16";
|
2016-12-30 14:07:05 +01:00
|
|
|
bit_depth = 16;
|
|
|
|
break;
|
|
|
|
case PNG_FORMAT_GRAY16:
|
|
|
|
color_type = PNG_COLOR_TYPE_GRAY;
|
2019-06-07 18:18:33 +02:00
|
|
|
if (out_linear)
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "Y u16";
|
2019-06-07 18:18:33 +02:00
|
|
|
else
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "Y' u16";
|
2016-12-30 14:07:05 +01:00
|
|
|
bit_depth = 16;
|
|
|
|
break;
|
|
|
|
case PNG_FORMAT_RGBA16:
|
|
|
|
color_type = PNG_COLOR_TYPE_RGB_ALPHA;
|
2019-06-07 18:18:33 +02:00
|
|
|
if (out_linear)
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "RGBA u16";
|
2019-06-07 18:18:33 +02:00
|
|
|
else
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "R'G'B'A u16";
|
2016-12-30 14:07:05 +01:00
|
|
|
bit_depth = 16;
|
|
|
|
break;
|
|
|
|
case PNG_FORMAT_GRAYA16:
|
|
|
|
color_type = PNG_COLOR_TYPE_GRAY_ALPHA;
|
2019-06-07 18:18:33 +02:00
|
|
|
if (out_linear)
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "YA u16";
|
2019-06-07 18:18:33 +02:00
|
|
|
else
|
2019-07-16 22:50:55 +02:00
|
|
|
encoding = "Y'A u16";
|
2016-12-30 14:07:05 +01:00
|
|
|
bit_depth = 16;
|
|
|
|
break;
|
2019-06-07 18:18:33 +02:00
|
|
|
case PNG_FORMAT_AUTO:
|
|
|
|
g_return_val_if_reached (FALSE);
|
2023-08-03 21:20:14 +02:00
|
|
|
}
|
2005-02-26 21:44:23 +00:00
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2019-07-16 22:50:55 +02:00
|
|
|
if (! file_format)
|
|
|
|
file_format = babl_format_with_space (encoding, space);
|
2019-06-16 14:52:48 +02:00
|
|
|
|
2012-04-26 23:07:35 +02:00
|
|
|
bpp = babl_format_get_bytes_per_pixel (file_format);
|
|
|
|
|
2011-05-05 20:47:53 +05:30
|
|
|
/* Note: png_set_IHDR() must be called before any other png_set_*()
|
|
|
|
functions. */
|
2012-04-26 23:07:35 +02:00
|
|
|
png_set_IHDR (pp, info, width, height, bit_depth, color_type,
|
2019-09-25 01:06:16 +02:00
|
|
|
save_interlaced ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE,
|
2011-05-05 20:36:45 +05:30
|
|
|
PNG_COMPRESSION_TYPE_BASE,
|
|
|
|
PNG_FILTER_TYPE_BASE);
|
|
|
|
|
|
|
|
if (pngg.has_trns)
|
2012-04-30 02:25:08 +02:00
|
|
|
png_set_tRNS (pp, info, pngg.trans, pngg.num_trans, NULL);
|
2011-05-05 20:36:45 +05:30
|
|
|
|
|
|
|
if (pngg.has_plte)
|
2012-04-30 02:25:08 +02:00
|
|
|
png_set_PLTE (pp, info, pngg.palette, pngg.num_palette);
|
2011-05-05 20:36:45 +05:30
|
|
|
|
|
|
|
/* Set the compression level */
|
|
|
|
|
2019-09-25 01:06:16 +02:00
|
|
|
png_set_compression_level (pp, compression_level);
|
2011-05-05 20:36:45 +05:30
|
|
|
|
2000-04-25 21:57:46 +00:00
|
|
|
/* All this stuff is optional extras, if the user is aiming for smallest
|
|
|
|
possible file size she can turn them all off */
|
|
|
|
|
2019-09-25 01:06:16 +02:00
|
|
|
if (save_bkgd)
|
2001-01-15 00:06:43 +00:00
|
|
|
{
|
2024-03-11 18:31:06 +01:00
|
|
|
GeglColor *color;
|
|
|
|
png_color_16 background; /* Background color */
|
2001-01-15 00:06:43 +00:00
|
|
|
|
2024-03-11 18:31:06 +01:00
|
|
|
background.index = 0;
|
2023-11-14 20:04:14 +01:00
|
|
|
color = gimp_context_get_background ();
|
2024-03-11 18:31:06 +01:00
|
|
|
if (bit_depth < 16)
|
|
|
|
{
|
|
|
|
/* Per PNG spec 1.2: "(If the image bit depth is less than 16, the
|
|
|
|
* least significant bits are used and the others are 0.)"
|
|
|
|
* And png_set_bKGD() doesn't handle the conversion for us, if we try
|
|
|
|
* to set a u16 background, it outputs the following warning:
|
|
|
|
* > libpng warning: Ignoring attempt to write 16-bit bKGD chunk when bit_depth is 8
|
|
|
|
*/
|
|
|
|
guint8 rgb[3];
|
|
|
|
guint8 gray;
|
|
|
|
|
|
|
|
gegl_color_get_pixel (color, babl_format_with_space ("R'G'B' u8", space), rgb);
|
|
|
|
gegl_color_get_pixel (color, babl_format_with_space ("Y' u8", space), &gray);
|
|
|
|
|
|
|
|
background.red = rgb[0];
|
|
|
|
background.green = rgb[1];
|
|
|
|
background.blue = rgb[2];
|
|
|
|
background.gray = gray;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
guint16 rgb[3];
|
|
|
|
guint16 gray;
|
|
|
|
|
|
|
|
gegl_color_get_pixel (color, babl_format_with_space ("R'G'B' u16", space), rgb);
|
|
|
|
gegl_color_get_pixel (color, babl_format_with_space ("Y' u16", space), &gray);
|
|
|
|
|
|
|
|
background.red = rgb[0];
|
|
|
|
background.green = rgb[1];
|
|
|
|
background.blue = rgb[2];
|
|
|
|
background.gray = gray;
|
|
|
|
}
|
2002-10-14 15:32:47 +00:00
|
|
|
|
|
|
|
png_set_bKGD (pp, info, &background);
|
2024-03-11 18:31:06 +01:00
|
|
|
g_object_unref (color);
|
2002-10-14 15:32:47 +00:00
|
|
|
}
|
2004-01-05 00:57:43 +00:00
|
|
|
|
2019-09-25 01:06:16 +02:00
|
|
|
if (save_offs)
|
2002-10-14 15:32:47 +00:00
|
|
|
{
|
2021-04-06 14:28:40 +02:00
|
|
|
gimp_drawable_get_offsets (drawable, &offx, &offy);
|
2002-10-14 15:32:47 +00:00
|
|
|
if (offx != 0 || offy != 0)
|
2012-04-30 02:25:08 +02:00
|
|
|
png_set_oFFs (pp, info, offx, offy, PNG_OFFSET_PIXEL);
|
2001-01-15 00:06:43 +00:00
|
|
|
}
|
2000-04-25 21:57:46 +00:00
|
|
|
|
2019-09-25 01:06:16 +02:00
|
|
|
if (save_phys)
|
2002-10-14 15:32:47 +00:00
|
|
|
{
|
2019-08-16 23:48:56 +02:00
|
|
|
gimp_image_get_resolution (orig_image, &xres, &yres);
|
2003-10-12 18:26:47 +00:00
|
|
|
png_set_pHYs (pp, info, RINT (xres / 0.0254), RINT (yres / 0.0254),
|
2002-10-14 15:32:47 +00:00
|
|
|
PNG_RESOLUTION_METER);
|
|
|
|
}
|
2000-04-25 21:57:46 +00:00
|
|
|
|
2019-09-25 01:06:16 +02:00
|
|
|
if (save_time)
|
2002-10-14 15:32:47 +00:00
|
|
|
{
|
|
|
|
cutime = time (NULL); /* time right NOW */
|
|
|
|
gmt = gmtime (&cutime);
|
|
|
|
|
|
|
|
mod_time.year = gmt->tm_year + 1900;
|
|
|
|
mod_time.month = gmt->tm_mon + 1;
|
|
|
|
mod_time.day = gmt->tm_mday;
|
|
|
|
mod_time.hour = gmt->tm_hour;
|
|
|
|
mod_time.minute = gmt->tm_min;
|
|
|
|
mod_time.second = gmt->tm_sec;
|
|
|
|
png_set_tIME (pp, info, &mod_time);
|
2000-04-25 21:57:46 +00:00
|
|
|
}
|
2000-06-30 02:51:35 +00:00
|
|
|
|
2005-10-24 15:09:49 +00:00
|
|
|
#if defined(PNG_iCCP_SUPPORTED)
|
2019-09-25 01:06:16 +02:00
|
|
|
if (save_profile)
|
2016-09-04 22:12:30 +02:00
|
|
|
{
|
|
|
|
GimpParasite *parasite;
|
|
|
|
gchar *profile_name = NULL;
|
|
|
|
const guint8 *icc_data;
|
|
|
|
gsize icc_length;
|
|
|
|
|
|
|
|
icc_data = gimp_color_profile_get_icc_profile (profile, &icc_length);
|
|
|
|
|
2019-08-16 23:48:56 +02:00
|
|
|
parasite = gimp_image_get_parasite (orig_image,
|
2016-09-04 22:12:30 +02:00
|
|
|
"icc-profile-name");
|
|
|
|
if (parasite)
|
2021-01-30 00:20:34 +01:00
|
|
|
{
|
|
|
|
gchar *parasite_data;
|
|
|
|
guint32 parasite_size;
|
|
|
|
|
|
|
|
parasite_data = (gchar *) gimp_parasite_get_data (parasite, ¶site_size);
|
|
|
|
profile_name = g_convert (parasite_data, parasite_size,
|
|
|
|
"UTF-8", "ISO-8859-1", NULL, NULL, NULL);
|
|
|
|
}
|
2016-09-04 22:12:30 +02:00
|
|
|
|
|
|
|
png_set_iCCP (pp,
|
|
|
|
info,
|
|
|
|
profile_name ? profile_name : "ICC profile",
|
|
|
|
0,
|
|
|
|
icc_data,
|
|
|
|
icc_length);
|
|
|
|
|
|
|
|
g_free (profile_name);
|
|
|
|
|
2019-06-07 18:18:33 +02:00
|
|
|
g_object_unref (profile);
|
2016-09-04 22:12:30 +02:00
|
|
|
}
|
2021-01-16 16:31:06 +01:00
|
|
|
else
|
2005-10-24 15:09:49 +00:00
|
|
|
#endif
|
2021-01-16 16:31:06 +01:00
|
|
|
{
|
|
|
|
/* Be more specific by writing into the file that the image is in
|
|
|
|
* sRGB color space.
|
|
|
|
*/
|
|
|
|
GimpColorConfig *config = gimp_get_color_configuration ();
|
|
|
|
int srgb_intent;
|
|
|
|
|
|
|
|
switch (gimp_color_config_get_display_intent (config))
|
|
|
|
{
|
|
|
|
case GIMP_COLOR_RENDERING_INTENT_PERCEPTUAL:
|
|
|
|
srgb_intent = PNG_sRGB_INTENT_PERCEPTUAL;
|
|
|
|
break;
|
|
|
|
case GIMP_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC:
|
|
|
|
srgb_intent = PNG_sRGB_INTENT_RELATIVE;
|
|
|
|
break;
|
|
|
|
case GIMP_COLOR_RENDERING_INTENT_SATURATION:
|
|
|
|
srgb_intent = PNG_sRGB_INTENT_SATURATION;
|
|
|
|
break;
|
|
|
|
case GIMP_COLOR_RENDERING_INTENT_ABSOLUTE_COLORIMETRIC:
|
|
|
|
srgb_intent = PNG_sRGB_INTENT_ABSOLUTE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
png_set_sRGB_gAMA_and_cHRM (pp, info, srgb_intent);
|
|
|
|
|
|
|
|
g_object_unref (config);
|
|
|
|
}
|
2005-10-24 15:09:49 +00:00
|
|
|
|
2013-09-12 12:52:09 +12:00
|
|
|
#ifdef PNG_zTXt_SUPPORTED
|
|
|
|
/* Small texts are not worth compressing and will be even bigger if compressed.
|
|
|
|
Empirical length limit of a text being worth compressing. */
|
|
|
|
#define COMPRESSION_WORTHY_LENGTH 200
|
|
|
|
#endif
|
|
|
|
|
2019-10-22 12:24:12 +02:00
|
|
|
if (save_comment && comment && strlen (comment))
|
2011-05-05 20:15:02 +05:30
|
|
|
{
|
2019-10-22 12:24:12 +02:00
|
|
|
gsize text_length = 0;
|
2011-05-05 20:15:02 +05:30
|
|
|
|
2019-10-22 12:24:12 +02:00
|
|
|
text = g_new0 (png_text, 1);
|
2013-09-11 21:56:01 +12:00
|
|
|
|
2019-10-22 12:24:12 +02:00
|
|
|
text[0].key = "Comment";
|
2011-05-05 20:15:02 +05:30
|
|
|
|
|
|
|
#ifdef PNG_iTXt_SUPPORTED
|
|
|
|
|
2019-10-22 12:24:12 +02:00
|
|
|
text[0].text = g_convert (comment, -1,
|
|
|
|
"ISO-8859-1",
|
|
|
|
"UTF-8",
|
|
|
|
NULL,
|
|
|
|
&text_length,
|
|
|
|
NULL);
|
2011-05-05 20:15:02 +05:30
|
|
|
|
2019-10-22 12:24:12 +02:00
|
|
|
if (text[0].text == NULL || strlen (text[0].text) == 0)
|
|
|
|
{
|
|
|
|
/* We can't convert to ISO-8859-1 without loss.
|
|
|
|
* Save the comment as iTXt (UTF-8).
|
|
|
|
*/
|
|
|
|
g_free (text[0].text);
|
2011-05-05 20:15:02 +05:30
|
|
|
|
2019-10-22 12:24:12 +02:00
|
|
|
text[0].text = g_strdup (comment);
|
|
|
|
text[0].itxt_length = strlen (text[0].text);
|
2011-05-05 20:15:02 +05:30
|
|
|
|
2013-09-12 12:52:09 +12:00
|
|
|
#ifdef PNG_zTXt_SUPPORTED
|
2019-10-22 12:24:12 +02:00
|
|
|
text[0].compression = strlen (text[0].text) > COMPRESSION_WORTHY_LENGTH ?
|
|
|
|
PNG_ITXT_COMPRESSION_zTXt : PNG_ITXT_COMPRESSION_NONE;
|
2013-09-12 12:52:09 +12:00
|
|
|
#else
|
2019-10-22 12:24:12 +02:00
|
|
|
text[0].compression = PNG_ITXT_COMPRESSION_NONE;
|
2013-09-12 12:52:09 +12:00
|
|
|
#endif /* PNG_zTXt_SUPPORTED */
|
2019-10-22 12:24:12 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
/* The comment is ISO-8859-1 compatible, so we use tEXt even
|
|
|
|
* if there is iTXt support for compatibility to more png
|
|
|
|
* reading programs.
|
|
|
|
*/
|
2013-09-11 21:56:01 +12:00
|
|
|
#endif /* PNG_iTXt_SUPPORTED */
|
2019-10-22 12:24:12 +02:00
|
|
|
{
|
2013-09-11 21:56:01 +12:00
|
|
|
#ifndef PNG_iTXt_SUPPORTED
|
2019-10-22 12:24:12 +02:00
|
|
|
/* No iTXt support, so we are forced to use tEXt
|
|
|
|
* (ISO-8859-1). A broken comment is better than no comment
|
|
|
|
* at all, so the conversion does not fail on unknown
|
|
|
|
* character. They are simply ignored.
|
|
|
|
*/
|
|
|
|
text[0].text = g_convert_with_fallback (comment, -1,
|
|
|
|
"ISO-8859-1",
|
|
|
|
"UTF-8",
|
|
|
|
"",
|
|
|
|
NULL,
|
|
|
|
&text_length,
|
|
|
|
NULL);
|
2011-05-05 20:15:02 +05:30
|
|
|
#endif
|
|
|
|
|
2013-09-12 12:52:09 +12:00
|
|
|
#ifdef PNG_zTXt_SUPPORTED
|
2019-10-22 12:24:12 +02:00
|
|
|
text[0].compression = strlen (text[0].text) > COMPRESSION_WORTHY_LENGTH ?
|
|
|
|
PNG_TEXT_COMPRESSION_zTXt : PNG_TEXT_COMPRESSION_NONE;
|
2013-09-12 12:52:09 +12:00
|
|
|
#else
|
2019-10-22 12:24:12 +02:00
|
|
|
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
|
2013-09-12 12:52:09 +12:00
|
|
|
#endif /* PNG_zTXt_SUPPORTED */
|
|
|
|
|
2019-10-22 12:24:12 +02:00
|
|
|
text[0].text_length = text_length;
|
|
|
|
}
|
2013-09-11 21:56:01 +12:00
|
|
|
|
2019-10-22 12:24:12 +02:00
|
|
|
if (! text[0].text || strlen (text[0].text) == 0)
|
|
|
|
{
|
|
|
|
g_free (text[0].text);
|
|
|
|
g_free (text);
|
|
|
|
text = NULL;
|
2011-05-05 20:15:02 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-22 12:24:12 +02:00
|
|
|
g_free (comment);
|
|
|
|
|
2013-09-12 12:52:09 +12:00
|
|
|
#ifdef PNG_zTXt_SUPPORTED
|
|
|
|
#undef COMPRESSION_WORTHY_LENGTH
|
|
|
|
#endif
|
|
|
|
|
2011-05-05 20:15:02 +05:30
|
|
|
if (text)
|
|
|
|
png_set_text (pp, info, text, 1);
|
|
|
|
|
1999-10-03 18:54:54 +00:00
|
|
|
png_write_info (pp, info);
|
2012-04-26 23:07:35 +02:00
|
|
|
if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
|
|
|
|
png_set_swap (pp);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2023-11-08 17:27:13 +00:00
|
|
|
/* Write any safe-to-copy chunks saved from import */
|
|
|
|
parasites = gimp_image_get_parasite_list (image);
|
|
|
|
|
|
|
|
if (parasites)
|
|
|
|
{
|
|
|
|
gint count;
|
|
|
|
|
|
|
|
count = g_strv_length (parasites);
|
|
|
|
for (gint i = 0; i < count; i++)
|
|
|
|
{
|
|
|
|
if (strncmp (parasites[i], "png", 3) == 0)
|
|
|
|
{
|
|
|
|
GimpParasite *parasite;
|
|
|
|
|
|
|
|
parasite = gimp_image_get_parasite (image, parasites[i]);
|
|
|
|
|
|
|
|
if (parasite)
|
|
|
|
{
|
|
|
|
gchar buf[1024];
|
|
|
|
gchar *chunk_name;
|
|
|
|
|
|
|
|
g_strlcpy (buf, parasites[i], sizeof (buf));
|
|
|
|
chunk_name = strchr (buf, '/');
|
|
|
|
chunk_name++;
|
|
|
|
|
|
|
|
if (chunk_name)
|
|
|
|
{
|
|
|
|
png_byte name[4];
|
|
|
|
const guint8 *data;
|
|
|
|
guint32 len;
|
|
|
|
|
|
|
|
for (gint j = 0; j < 4; j++)
|
|
|
|
name[j] = chunk_name[j];
|
|
|
|
|
|
|
|
data = (const guint8 *) gimp_parasite_get_data (parasite, &len);
|
|
|
|
|
|
|
|
png_write_chunk (pp, name, data, len);
|
|
|
|
}
|
|
|
|
gimp_parasite_free (parasite);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
g_strfreev (parasites);
|
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
/*
|
|
|
|
* Turn on interlace handling...
|
|
|
|
*/
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2019-09-25 01:06:16 +02:00
|
|
|
if (save_interlaced)
|
2002-10-14 15:32:47 +00:00
|
|
|
num_passes = png_set_interlace_handling (pp);
|
1997-11-24 22:05:25 +00:00
|
|
|
else
|
2000-04-13 04:48:56 +00:00
|
|
|
num_passes = 1;
|
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
/*
|
|
|
|
* Convert unpacked pixels to packed if necessary
|
|
|
|
*/
|
2000-04-13 04:48:56 +00:00
|
|
|
|
2011-04-26 08:06:27 +05:30
|
|
|
if (color_type == PNG_COLOR_TYPE_PALETTE &&
|
|
|
|
bit_depth < 8)
|
2002-10-14 15:32:47 +00:00
|
|
|
png_set_packing (pp);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
/*
|
2016-02-16 02:35:43 +01:00
|
|
|
* Allocate memory for "tile_height" rows and export the image...
|
2002-10-14 15:32:47 +00:00
|
|
|
*/
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
tile_height = gimp_tile_height ();
|
2012-04-26 23:07:35 +02:00
|
|
|
pixel = g_new (guchar, tile_height * width * bpp);
|
2002-10-14 15:32:47 +00:00
|
|
|
pixels = g_new (guchar *, tile_height);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
for (i = 0; i < tile_height; i++)
|
2012-04-26 23:07:35 +02:00
|
|
|
pixels[i] = pixel + width * bpp * i;
|
2000-04-25 21:57:46 +00:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
for (pass = 0; pass < num_passes; pass++)
|
|
|
|
{
|
2000-04-25 21:57:46 +00:00
|
|
|
/* This works if you are only writing one row at a time... */
|
2002-10-14 15:32:47 +00:00
|
|
|
for (begin = 0, end = tile_height;
|
2012-04-26 23:07:35 +02:00
|
|
|
begin < height; begin += tile_height, end += tile_height)
|
2002-10-14 15:32:47 +00:00
|
|
|
{
|
2012-04-26 23:07:35 +02:00
|
|
|
if (end > height)
|
|
|
|
end = height;
|
2002-10-14 15:32:47 +00:00
|
|
|
|
|
|
|
num = end - begin;
|
|
|
|
|
2012-04-26 23:07:35 +02:00
|
|
|
gegl_buffer_get (buffer,
|
|
|
|
GEGL_RECTANGLE (0, begin, width, num),
|
|
|
|
1.0,
|
|
|
|
file_format,
|
|
|
|
pixel,
|
|
|
|
GEGL_AUTO_ROWSTRIDE,
|
|
|
|
GEGL_ABYSS_NONE);
|
2011-05-06 17:51:09 +05:30
|
|
|
|
|
|
|
/* If we are with a RGBA image and have to pre-multiply the
|
|
|
|
alpha channel */
|
2019-09-25 01:06:16 +02:00
|
|
|
if (bpp == 4 && ! save_transp_pixels)
|
2004-01-05 00:57:43 +00:00
|
|
|
{
|
|
|
|
for (i = 0; i < num; ++i)
|
|
|
|
{
|
|
|
|
fixed = pixels[i];
|
2012-04-26 23:07:35 +02:00
|
|
|
for (k = 0; k < width; ++k)
|
2004-01-05 00:57:43 +00:00
|
|
|
{
|
2012-04-27 03:35:07 +02:00
|
|
|
if (!fixed[3])
|
|
|
|
fixed[0] = fixed[1] = fixed[2] = 0;
|
|
|
|
fixed += bpp;
|
2004-01-05 00:57:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2002-11-06 11:31:35 +00:00
|
|
|
|
2019-09-25 01:06:16 +02:00
|
|
|
if (bpp == 8 && ! save_transp_pixels)
|
2012-04-26 23:07:35 +02:00
|
|
|
{
|
|
|
|
for (i = 0; i < num; ++i)
|
|
|
|
{
|
|
|
|
fixed = pixels[i];
|
|
|
|
for (k = 0; k < width; ++k)
|
|
|
|
{
|
2012-04-27 03:35:07 +02:00
|
|
|
if (!fixed[6] && !fixed[7])
|
|
|
|
fixed[0] = fixed[1] = fixed[2] =
|
|
|
|
fixed[3] = fixed[4] = fixed[5] = 0;
|
|
|
|
fixed += bpp;
|
2012-04-26 23:07:35 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2003-11-02 16:52:26 +00:00
|
|
|
/* If we're dealing with a paletted image with
|
2002-11-06 11:31:35 +00:00
|
|
|
* transparency set, write out the remapped palette */
|
2023-12-23 21:40:14 +00:00
|
|
|
if (pngg.has_trns)
|
2002-10-14 15:32:47 +00:00
|
|
|
{
|
2006-05-22 17:13:30 +00:00
|
|
|
guchar inverse_remap[256];
|
|
|
|
|
|
|
|
for (i = 0; i < 256; i++)
|
|
|
|
inverse_remap[ remap[i] ] = i;
|
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
for (i = 0; i < num; ++i)
|
|
|
|
{
|
|
|
|
fixed = pixels[i];
|
2012-04-26 23:07:35 +02:00
|
|
|
for (k = 0; k < width; ++k)
|
2002-10-14 15:32:47 +00:00
|
|
|
{
|
2003-11-02 16:52:26 +00:00
|
|
|
fixed[k] = (fixed[k*2+1] > 127) ?
|
2006-05-22 17:13:30 +00:00
|
|
|
inverse_remap[ fixed[k*2] ] :
|
2003-04-12 15:00:20 +00:00
|
|
|
0;
|
2002-10-14 15:32:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-05-06 17:51:09 +05:30
|
|
|
|
2003-11-02 16:52:26 +00:00
|
|
|
/* Otherwise if we have a paletted image and transparency
|
2002-11-06 11:31:35 +00:00
|
|
|
* couldn't be set, we ignore the alpha channel */
|
2011-04-26 08:06:27 +05:30
|
|
|
else if (png_get_valid (pp, info, PNG_INFO_PLTE) &&
|
|
|
|
bpp == 2)
|
2002-10-14 15:32:47 +00:00
|
|
|
{
|
|
|
|
for (i = 0; i < num; ++i)
|
|
|
|
{
|
|
|
|
fixed = pixels[i];
|
2012-04-26 23:07:35 +02:00
|
|
|
for (k = 0; k < width; ++k)
|
2002-10-14 15:32:47 +00:00
|
|
|
{
|
|
|
|
fixed[k] = fixed[k * 2];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
png_write_rows (pp, pixels, num);
|
|
|
|
|
2023-08-06 00:59:19 +02:00
|
|
|
if (report_progress)
|
2021-01-27 19:06:57 -05:00
|
|
|
gimp_progress_update (((double) pass + (double) end /
|
|
|
|
(double) height) /
|
|
|
|
(double) num_passes);
|
2005-02-26 21:44:23 +00:00
|
|
|
}
|
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2023-08-06 00:59:19 +02:00
|
|
|
if (report_progress)
|
2021-01-27 19:06:57 -05:00
|
|
|
gimp_progress_update (1.0);
|
2011-04-10 19:05:08 +02:00
|
|
|
|
1999-10-03 18:54:54 +00:00
|
|
|
png_write_end (pp, info);
|
2006-04-23 07:19:41 +00:00
|
|
|
png_destroy_write_struct (&pp, &info);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
1999-10-03 18:54:54 +00:00
|
|
|
g_free (pixel);
|
|
|
|
g_free (pixels);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2002-10-14 15:32:47 +00:00
|
|
|
/*
|
|
|
|
* Done with the file...
|
|
|
|
*/
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2003-07-03 00:30:33 +00:00
|
|
|
if (text)
|
|
|
|
{
|
2013-09-11 21:56:01 +12:00
|
|
|
g_free (text[0].text);
|
2003-07-03 00:30:33 +00:00
|
|
|
g_free (text);
|
|
|
|
}
|
|
|
|
|
1999-10-03 18:54:54 +00:00
|
|
|
free (pp);
|
|
|
|
free (info);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
1999-10-03 18:54:54 +00:00
|
|
|
fclose (fp);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2019-10-09 22:51:34 +02:00
|
|
|
*bits_per_sample = bit_depth;
|
|
|
|
|
2005-06-24 11:31:49 +00:00
|
|
|
return TRUE;
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|
|
|
|
|
2004-07-14 22:09:51 +00:00
|
|
|
static gboolean
|
2012-04-29 03:34:43 +02:00
|
|
|
ia_has_transparent_pixels (GeglBuffer *buffer)
|
2004-07-14 22:09:51 +00:00
|
|
|
{
|
2012-04-29 03:34:43 +02:00
|
|
|
GeglBufferIterator *iter;
|
|
|
|
const Babl *format;
|
|
|
|
gint n_components;
|
|
|
|
|
|
|
|
format = gegl_buffer_get_format (buffer);
|
|
|
|
iter = gegl_buffer_iterator_new (buffer, NULL, 0, format,
|
2018-09-11 02:01:47 +02:00
|
|
|
GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 1);
|
2012-04-29 03:34:43 +02:00
|
|
|
n_components = babl_format_get_n_components (format);
|
|
|
|
g_return_val_if_fail (n_components == 2, FALSE);
|
|
|
|
|
|
|
|
while (gegl_buffer_iterator_next (iter))
|
2006-08-30 16:37:55 +00:00
|
|
|
{
|
2018-09-11 02:01:47 +02:00
|
|
|
const guchar *data = iter->items[0].data;
|
2013-09-12 17:39:59 -07:00
|
|
|
gint length = iter->length;
|
2012-04-29 03:34:43 +02:00
|
|
|
|
2013-09-12 17:39:59 -07:00
|
|
|
while (length--)
|
2006-08-30 16:37:55 +00:00
|
|
|
{
|
2012-04-29 03:34:43 +02:00
|
|
|
if (data[1] <= 127)
|
2006-08-30 16:37:55 +00:00
|
|
|
{
|
2012-04-29 03:34:43 +02:00
|
|
|
gegl_buffer_iterator_stop (iter);
|
|
|
|
return TRUE;
|
2006-08-30 16:37:55 +00:00
|
|
|
}
|
2012-04-29 03:34:43 +02:00
|
|
|
|
|
|
|
data += n_components;
|
2006-08-30 16:37:55 +00:00
|
|
|
}
|
2004-07-14 22:09:51 +00:00
|
|
|
}
|
2005-06-24 11:31:49 +00:00
|
|
|
|
2004-07-14 22:09:51 +00:00
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2019-09-25 01:06:16 +02:00
|
|
|
/* Try to find a color in the palette which isn't actually used in the
|
|
|
|
* image, so that we can use it as the transparency index. Taken from
|
|
|
|
* gif.c
|
|
|
|
*/
|
2006-08-30 16:37:55 +00:00
|
|
|
static gint
|
2012-04-29 03:34:43 +02:00
|
|
|
find_unused_ia_color (GeglBuffer *buffer,
|
|
|
|
gint *colors)
|
2006-08-30 16:37:55 +00:00
|
|
|
{
|
2012-04-29 03:34:43 +02:00
|
|
|
GeglBufferIterator *iter;
|
|
|
|
const Babl *format;
|
|
|
|
gint n_components;
|
|
|
|
gboolean ix_used[256];
|
|
|
|
gboolean trans_used = FALSE;
|
|
|
|
gint i;
|
2006-08-30 16:37:55 +00:00
|
|
|
|
|
|
|
for (i = 0; i < *colors; i++)
|
2006-08-30 16:53:44 +00:00
|
|
|
ix_used[i] = FALSE;
|
2006-08-30 16:37:55 +00:00
|
|
|
|
2012-04-29 03:34:43 +02:00
|
|
|
format = gegl_buffer_get_format (buffer);
|
|
|
|
iter = gegl_buffer_iterator_new (buffer, NULL, 0, format,
|
2018-09-11 02:01:47 +02:00
|
|
|
GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 1);
|
2012-04-29 03:34:43 +02:00
|
|
|
n_components = babl_format_get_n_components (format);
|
|
|
|
g_return_val_if_fail (n_components == 2, FALSE);
|
2006-08-30 16:37:55 +00:00
|
|
|
|
2012-04-29 03:34:43 +02:00
|
|
|
while (gegl_buffer_iterator_next (iter))
|
2006-08-30 16:37:55 +00:00
|
|
|
{
|
2018-09-11 02:01:47 +02:00
|
|
|
const guchar *data = iter->items[0].data;
|
2013-09-12 17:39:59 -07:00
|
|
|
gint length = iter->length;
|
2006-08-30 16:53:44 +00:00
|
|
|
|
2013-09-12 17:39:59 -07:00
|
|
|
while (length--)
|
2006-08-30 16:37:55 +00:00
|
|
|
{
|
2012-04-29 03:34:43 +02:00
|
|
|
if (data[1] > 127)
|
|
|
|
ix_used[data[0]] = TRUE;
|
|
|
|
else
|
|
|
|
trans_used = TRUE;
|
2006-08-30 16:53:44 +00:00
|
|
|
|
2012-04-29 03:34:43 +02:00
|
|
|
data += n_components;
|
2006-08-30 16:37:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If there is no transparency, ignore alpha. */
|
|
|
|
if (trans_used == FALSE)
|
|
|
|
return -1;
|
|
|
|
|
2012-03-26 20:40:25 +02:00
|
|
|
/* If there is still some room at the end of the palette, increment
|
|
|
|
* the number of colors in the image and assign a transparent pixel
|
2019-09-25 01:06:16 +02:00
|
|
|
* there.
|
|
|
|
*/
|
2006-08-30 16:37:55 +00:00
|
|
|
if ((*colors) < 256)
|
|
|
|
{
|
|
|
|
(*colors)++;
|
|
|
|
|
|
|
|
return (*colors) - 1;
|
|
|
|
}
|
|
|
|
|
2012-03-26 20:40:25 +02:00
|
|
|
for (i = 0; i < *colors; i++)
|
|
|
|
{
|
|
|
|
if (ix_used[i] == FALSE)
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
2006-08-30 16:37:55 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2011-04-26 08:06:27 +05:30
|
|
|
static int
|
2005-06-24 11:31:49 +00:00
|
|
|
respin_cmap (png_structp pp,
|
|
|
|
png_infop info,
|
|
|
|
guchar *remap,
|
2019-08-16 23:48:56 +02:00
|
|
|
GimpImage *image,
|
|
|
|
GimpDrawable *drawable)
|
2002-09-10 16:46:41 +00:00
|
|
|
{
|
2011-05-05 20:36:45 +05:30
|
|
|
static guchar trans[] = { 0 };
|
2022-08-17 18:05:13 +00:00
|
|
|
GeglBuffer *buffer;
|
2004-05-27 15:23:56 +00:00
|
|
|
|
|
|
|
gint colors;
|
|
|
|
guchar *before;
|
2000-04-02 02:46:59 +00:00
|
|
|
|
2023-05-23 23:37:46 +02:00
|
|
|
before = gimp_image_get_colormap (image, NULL, &colors);
|
2019-08-16 23:48:56 +02:00
|
|
|
buffer = gimp_drawable_get_buffer (drawable);
|
2000-04-25 21:57:46 +00:00
|
|
|
|
2019-09-25 01:06:16 +02:00
|
|
|
/* Make sure there is something in the colormap.
|
2004-05-27 15:23:56 +00:00
|
|
|
*/
|
|
|
|
if (colors == 0)
|
|
|
|
{
|
2013-01-14 17:36:43 -05:00
|
|
|
before = g_newa (guchar, 3);
|
|
|
|
memset (before, 0, sizeof (guchar) * 3);
|
|
|
|
|
2004-05-27 15:23:56 +00:00
|
|
|
colors = 1;
|
|
|
|
}
|
|
|
|
|
2019-09-25 01:06:16 +02:00
|
|
|
/* Try to find an entry which isn't actually used in the image, for
|
|
|
|
* a transparency index.
|
|
|
|
*/
|
2002-10-14 15:32:47 +00:00
|
|
|
|
2012-04-29 03:34:43 +02:00
|
|
|
if (ia_has_transparent_pixels (buffer))
|
2002-09-10 16:46:41 +00:00
|
|
|
{
|
2012-04-29 03:34:43 +02:00
|
|
|
gint transparent = find_unused_ia_color (buffer, &colors);
|
2006-08-30 16:37:55 +00:00
|
|
|
|
2004-07-14 22:09:51 +00:00
|
|
|
if (transparent != -1) /* we have a winner for a transparent
|
|
|
|
* index - do like gif2png and swap
|
2019-09-25 01:06:16 +02:00
|
|
|
* index 0 and index transparent
|
|
|
|
*/
|
2004-07-14 22:09:51 +00:00
|
|
|
{
|
2011-05-05 20:36:45 +05:30
|
|
|
static png_color palette[256];
|
2005-06-24 11:31:49 +00:00
|
|
|
gint i;
|
2002-10-14 15:32:47 +00:00
|
|
|
|
2011-05-05 20:36:45 +05:30
|
|
|
/* Set tRNS chunk values for writing later. */
|
|
|
|
pngg.has_trns = TRUE;
|
|
|
|
pngg.trans = trans;
|
|
|
|
pngg.num_trans = 1;
|
2001-01-15 00:06:43 +00:00
|
|
|
|
2019-09-25 01:06:16 +02:00
|
|
|
/* Transform all pixels with a value = transparent to 0 and
|
|
|
|
* vice versa to compensate for re-ordering in palette due
|
|
|
|
* to png_set_tRNS()
|
|
|
|
*/
|
2002-10-14 14:52:53 +00:00
|
|
|
|
2004-07-14 22:09:51 +00:00
|
|
|
remap[0] = transparent;
|
2006-05-22 17:13:30 +00:00
|
|
|
for (i = 1; i <= transparent; i++)
|
|
|
|
remap[i] = i - 1;
|
2002-10-14 15:32:47 +00:00
|
|
|
|
2004-07-14 22:09:51 +00:00
|
|
|
/* Copy from index 0 to index transparent - 1 to index 1 to
|
|
|
|
* transparent of after, then from transparent+1 to colors-1
|
2019-09-25 01:06:16 +02:00
|
|
|
* unchanged, and finally from index transparent to index 0.
|
|
|
|
*/
|
2000-04-02 02:46:59 +00:00
|
|
|
|
2004-07-14 22:09:51 +00:00
|
|
|
for (i = 0; i < colors; i++)
|
|
|
|
{
|
|
|
|
palette[i].red = before[3 * remap[i]];
|
|
|
|
palette[i].green = before[3 * remap[i] + 1];
|
|
|
|
palette[i].blue = before[3 * remap[i] + 2];
|
|
|
|
}
|
|
|
|
|
2011-05-05 20:36:45 +05:30
|
|
|
/* Set PLTE chunk values for writing later. */
|
|
|
|
pngg.has_plte = TRUE;
|
|
|
|
pngg.palette = palette;
|
|
|
|
pngg.num_palette = colors;
|
2004-07-14 22:09:51 +00:00
|
|
|
}
|
|
|
|
else
|
2002-09-10 16:46:41 +00:00
|
|
|
{
|
2004-07-14 22:09:51 +00:00
|
|
|
/* Inform the user that we couldn't losslessly save the
|
2019-09-25 01:06:16 +02:00
|
|
|
* transparency & just use the full palette
|
|
|
|
*/
|
2004-07-14 22:09:51 +00:00
|
|
|
g_message (_("Couldn't losslessly save transparency, "
|
|
|
|
"saving opacity instead."));
|
2011-05-05 20:36:45 +05:30
|
|
|
|
|
|
|
/* Set PLTE chunk values for writing later. */
|
|
|
|
pngg.has_plte = TRUE;
|
|
|
|
pngg.palette = (png_colorp) before;
|
|
|
|
pngg.num_palette = colors;
|
2002-09-10 16:46:41 +00:00
|
|
|
}
|
2002-10-14 15:32:47 +00:00
|
|
|
}
|
|
|
|
else
|
2005-06-24 11:31:49 +00:00
|
|
|
{
|
2011-05-05 20:36:45 +05:30
|
|
|
/* Set PLTE chunk values for writing later. */
|
|
|
|
pngg.has_plte = TRUE;
|
|
|
|
pngg.palette = (png_colorp) before;
|
|
|
|
pngg.num_palette = colors;
|
2005-06-24 11:31:49 +00:00
|
|
|
}
|
|
|
|
|
2012-04-29 03:34:43 +02:00
|
|
|
g_object_unref (buffer);
|
2012-04-28 17:46:32 +02:00
|
|
|
|
2011-04-26 08:06:27 +05:30
|
|
|
return get_bit_depth_for_palette (colors);
|
2000-04-02 02:46:59 +00:00
|
|
|
}
|
|
|
|
|
2023-11-08 17:27:13 +00:00
|
|
|
static gint
|
|
|
|
read_unknown_chunk (png_structp png_ptr,
|
|
|
|
png_unknown_chunkp chunk)
|
|
|
|
{
|
|
|
|
/* Chunks with a lowercase letter in the 4th byte
|
|
|
|
* are safe to copy */
|
|
|
|
if (g_ascii_islower (chunk->name[3]))
|
|
|
|
{
|
|
|
|
GimpParasite *parasite;
|
|
|
|
gchar pname[255];
|
|
|
|
|
|
|
|
g_snprintf (pname, sizeof (pname), "png/%s", chunk->name);
|
|
|
|
|
|
|
|
if ((parasite = gimp_parasite_new (pname,
|
|
|
|
GIMP_PARASITE_PERSISTENT,
|
|
|
|
chunk->size, chunk->data)))
|
|
|
|
safe_to_copy_chunks = g_slist_prepend (safe_to_copy_chunks, parasite);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2004-05-19 17:53:21 +00:00
|
|
|
static gboolean
|
2024-04-13 15:10:25 +00:00
|
|
|
export_dialog (GimpImage *image,
|
|
|
|
GimpProcedure *procedure,
|
|
|
|
GObject *config,
|
|
|
|
gboolean alpha)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
2023-08-03 21:20:14 +02:00
|
|
|
GtkWidget *dialog;
|
|
|
|
gboolean run;
|
|
|
|
gboolean indexed;
|
2022-08-17 18:05:13 +00:00
|
|
|
|
|
|
|
indexed = (gimp_image_get_base_type (image) == GIMP_INDEXED);
|
2010-01-12 21:12:40 +01:00
|
|
|
|
2024-04-20 03:08:57 +00:00
|
|
|
dialog = gimp_export_procedure_dialog_new (GIMP_EXPORT_PROCEDURE (procedure),
|
|
|
|
GIMP_PROCEDURE_CONFIG (config),
|
|
|
|
image);
|
2010-01-12 21:12:40 +01:00
|
|
|
|
2020-11-20 00:14:59 +01:00
|
|
|
gimp_procedure_dialog_get_widget (GIMP_PROCEDURE_DIALOG (dialog),
|
2022-02-19 01:26:17 +01:00
|
|
|
"compression", GIMP_TYPE_SPIN_SCALE);
|
2019-09-25 01:06:16 +02:00
|
|
|
|
2021-04-06 21:52:33 +02:00
|
|
|
gimp_procedure_dialog_set_sensitive (GIMP_PROCEDURE_DIALOG (dialog),
|
|
|
|
"save-transparent",
|
|
|
|
alpha, NULL, NULL, FALSE);
|
2020-11-20 00:14:59 +01:00
|
|
|
|
2022-08-17 18:05:13 +00:00
|
|
|
gimp_procedure_dialog_set_sensitive (GIMP_PROCEDURE_DIALOG (dialog),
|
|
|
|
"optimize-palette",
|
|
|
|
indexed, NULL, NULL, FALSE);
|
|
|
|
|
2024-04-20 03:08:57 +00:00
|
|
|
gimp_export_procedure_dialog_add_metadata (GIMP_EXPORT_PROCEDURE_DIALOG (dialog), "bkgd");
|
|
|
|
gimp_export_procedure_dialog_add_metadata (GIMP_EXPORT_PROCEDURE_DIALOG (dialog), "offs");
|
|
|
|
gimp_export_procedure_dialog_add_metadata (GIMP_EXPORT_PROCEDURE_DIALOG (dialog), "phys");
|
|
|
|
gimp_export_procedure_dialog_add_metadata (GIMP_EXPORT_PROCEDURE_DIALOG (dialog), "time");
|
2020-11-20 00:14:59 +01:00
|
|
|
gimp_procedure_dialog_fill (GIMP_PROCEDURE_DIALOG (dialog),
|
2020-11-21 01:55:33 +01:00
|
|
|
"format", "compression",
|
|
|
|
"interlaced", "save-transparent",
|
2022-08-17 18:05:13 +00:00
|
|
|
"optimize-palette",
|
2020-11-20 00:14:59 +01:00
|
|
|
NULL);
|
2004-01-06 09:53:35 +00:00
|
|
|
|
2019-09-25 01:06:16 +02:00
|
|
|
run = gimp_procedure_dialog_run (GIMP_PROCEDURE_DIALOG (dialog));
|
2004-01-06 09:53:35 +00:00
|
|
|
|
2020-11-21 01:55:33 +01:00
|
|
|
gtk_widget_destroy (dialog);
|
|
|
|
|
2019-09-25 01:06:16 +02:00
|
|
|
return run;
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|