gimp/plug-ins/common/file-png.c

2407 lines
78 KiB
C
Raw Normal View History

/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
1997-11-24 22:05:25 +00:00
*
* Portable Network Graphics (PNG) plug-in
1997-11-24 22:05:25 +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).
* and 1999-2000 Nick Lamb (njl195@zepler.org.uk)
1997-11-24 22:05:25 +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
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
1997-11-24 22:05:25 +00:00
*/
#include "config.h"
1997-11-24 22:05:25 +00:00
#include <stdlib.h>
#include <errno.h>
1997-11-24 22:05:25 +00:00
#include <glib/gstdio.h>
#include "lcms2.h"
1997-11-24 22:05:25 +00:00
#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>
1997-11-24 22:05:25 +00:00
#include <png.h>
configure.in po-plug-ins/POTFILES.in plug-ins/common/Makefile.am 2000-01-25 Michael Natterer <mitch@gimp.org> * configure.in * po-plug-ins/POTFILES.in * plug-ins/common/Makefile.am * plug-ins/common/plugin-defs.pl * plug-ins/megawidget/*: removed. (There were only 3 functions left which were used by ~5 plugins, so I moved the resp. functions to the plugins). More preview stuff to come... * app/airbrush_blob.c * modules/colorsel_triangle.c * modules/colorsel_water.c: use G_PI instead of M_PI. * app/procedural_db.h * libgimp/gimpenums.h * plug-ins/script-fu/script-fu-constants.c * tools/pdbgen/enums.pl: new PDB return value STATUS_CANCEL which indicates that "Cancel" was pressed in a plugin dialog. (Useful only for file load/save plugins). * app/fileops.[ch] * app/menus.c: changes to handle STATUS_CANCEL correctly. Did some code cleanup in fileops.[ch]. Pop up a warning if File->Save failed. * app/plug_in.c: return_val[0] is of type PDB_STATUS, not PDB_INT32. * libgimp/gimpmath.h: new constant G_MAXRAND which equals to RAND_MAX if it exists or to G_MAXINT otherwise. * libgimp/gimpwidgets.[ch]: new function gimp_random_seed_new() which creates a spinbutton and a "Time" toggle. Call the function which does the "set_sensitive" magic from the radio button callback. * plug-ins/[75 plugins]: - Return STATUS_CANCEL in all file load/save dialogs if "Cancel" was pressed. - Standardized the file plugins' "run" functions. - Use G_PI and G_MAXRAND everywhere. - Added tons of scales and spinbuttons instead of text entries. - Applied uniform packing/spacings all over the place. - Reorganized some UIs (stuff like moving the preview to the top left corner of the dialog). - Removed many ui helper functions and callbacks and use the stuff from libgimp instead. - I tried not to restrict the range of possible values when I replaced entries with spinbuttons/scales but may have failed, though in some cases. Please test ;-) - #include <libgimp/gimpmath.h> where appropriate and use it's constants. - Indentation, s/int/gint/ et.al., code cleanup. RFC: The plugins are definitely not useable with GIMP 1.0 any more, so shouldn't we remove all the remaining compatibility stuff ??? (like "#ifdef GIMP_HAVE_PARASITES")
2000-01-25 17:46:56 +00:00
configure.in removed tips files, AC_SUBST GIMP_PLUGINS and GIMP_MODULES so * configure.in * Makefile.am: removed tips files, AC_SUBST GIMP_PLUGINS and GIMP_MODULES so you can easily skip those parts of the build * acinclude.m4 * config.sub * config.guess * ltconfig * ltmain.sh: libtool 1.3.2 * app/fileops.c: shuffle #includes to avoid warning about MIN and MAX [ The following is a big i18n patch from David Monniaux <david.monniaux@ens.fr> ] * tips/gimp_conseils.fr.txt * tips/gimp_tips.txt * tips/Makefile.am * configure.in: moved tips to separate dir * po-plugins: new dir for plug-in translation files * configure.in: add po-plugins dir and POTFILES processing * app/boundary.c * app/brightness_contrast.c * app/by_color_select.c * app/color_balance.c * app/convert.c * app/curves.c * app/free_select.c * app/gdisplay.c * app/gimpimage.c * app/gimpunit.c * app/gradient.c * app/gradient_select.c * app/install.c * app/session.c: various i18n tweaks * app/tips_dialog.c: localize tips filename * libgimp/gimpunit.c * libgimp/gimpunitmenu.c: #include "config.h" * plug-ins/CEL * plug-ins/CML_explorer * plug-ins/Lighting * plug-ins/apply_lens * plug-ins/autostretch_hsv * plug-ins/blur * plug-ins/bmp * plug-ins/borderaverage * plug-ins/bumpmap * plug-ins/bz2 * plug-ins/checkerboard * plug-ins/colorify * plug-ins/compose * plug-ins/convmatrix * plug-ins/cubism * plug-ins/depthmerge * plug-ins/destripe * plug-ins/gif * plug-ins/gifload * plug-ins/jpeg * plug-ins/mail * plug-ins/oilify * plug-ins/png * plug-ins/print * plug-ins/ps * plug-ins/xbm * plug-ins/xpm * plug-ins/xwd: plug-in i18n stuff -Yosh
1999-05-29 16:35:47 +00:00
#include "libgimp/stdplugins-intl.h"
1997-11-24 22:05:25 +00:00
#define LOAD_PROC "file-png-load"
#define EXPORT_PROC "file-png-export"
#define PLUG_IN_BINARY "file-png"
#define PLUG_IN_ROLE "gimp-file-png"
#define PLUG_IN_VERSION "1.3.4 - 03 September 2002"
#define SCALE_WIDTH 125
1997-11-24 22:05:25 +00:00
#define DEFAULT_GAMMA 2.20
1997-11-24 22:05:25 +00:00
typedef enum _PngExportformat
{
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;
static GSList *safe_to_copy_chunks;
typedef struct _Png Png;
typedef struct _PngClass PngClass;
struct _Png
{
GimpPlugIn parent_instance;
};
struct _PngClass
{
GimpPlugInClass parent_class;
};
#define PNG_TYPE (png_get_type ())
#define PNG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PNG_TYPE, Png))
GType png_get_type (void) G_GNUC_CONST;
1997-11-24 22:05:25 +00: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);
static GimpValueArray * png_export (GimpProcedure *procedure,
GimpRunMode run_mode,
GimpImage *image,
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);
static gboolean export_image (GFile *file,
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);
static gboolean export_dialog (GimpImage *image,
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);
static gint read_unknown_chunk (png_structp png_ptr,
png_unknown_chunkp chunk);
G_DEFINE_TYPE (Png, png, GIMP_TYPE_PLUG_IN)
GIMP_MAIN (PNG_TYPE)
DEFINE_STD_SET_I18N
1997-11-24 22:05:25 +00:00
static void
png_class_init (PngClass *klass)
{
GimpPlugInClass *plug_in_class = GIMP_PLUG_IN_CLASS (klass);
plug_in_class->query_procedures = png_query_procedures;
plug_in_class->create_procedure = png_create_procedure;
plug_in_class->set_i18n = STD_SET_I18N;
}
1997-11-24 22:05:25 +00:00
static void
png_init (Png *png)
1997-11-24 22:05:25 +00:00
{
}
static GList *
png_query_procedures (GimpPlugIn *plug_in)
{
GList *list = NULL;
1997-11-24 22:05:25 +00:00
list = g_list_append (list, g_strdup (LOAD_PROC));
list = g_list_append (list, g_strdup (EXPORT_PROC));
1997-11-24 22:05:25 +00:00
return list;
}
static GimpProcedure *
png_create_procedure (GimpPlugIn *plug_in,
const gchar *name)
1997-11-24 22:05:25 +00:00
{
GimpProcedure *procedure = NULL;
if (! strcmp (name, LOAD_PROC))
{
procedure = gimp_load_procedure_new (plug_in, name,
GIMP_PDB_PROC_TYPE_PLUGIN,
png_load, NULL, NULL);
gimp_procedure_set_menu_label (procedure, _("PNG image"));
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");
}
else if (! strcmp (name, EXPORT_PROC))
{
procedure = gimp_export_procedure_new (plug_in, name,
GIMP_PDB_PROC_TYPE_PLUGIN,
TRUE, png_export, NULL, NULL);
gimp_procedure_set_image_types (procedure, "*");
gimp_procedure_set_menu_label (procedure, _("PNG image"));
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);
gimp_file_procedure_set_format_name (GIMP_FILE_PROCEDURE (procedure),
_("PNG"));
gimp_file_procedure_set_mime_types (GIMP_FILE_PROCEDURE (procedure),
"image/png");
gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure),
"png");
gimp_procedure_add_boolean_argument (procedure, "interlaced",
_("_Interlacing (Adam7)"),
_("Use Adam7 interlacing"),
FALSE,
G_PARAM_READWRITE);
gimp_procedure_add_int_argument (procedure, "compression",
_("Co_mpression level"),
_("Deflate Compression factor (0..9)"),
0, 9, 9,
G_PARAM_READWRITE);
gimp_procedure_add_boolean_argument (procedure, "bkgd",
_("Save _background color"),
_("Write bKGD chunk (PNG metadata)"),
TRUE,
G_PARAM_READWRITE);
gimp_procedure_add_boolean_argument (procedure, "offs",
_("Save layer o_ffset"),
_("Write oFFs chunk (PNG metadata)"),
FALSE,
G_PARAM_READWRITE);
gimp_procedure_add_boolean_argument (procedure, "phys",
_("Save resol_ution"),
_("Write pHYs chunk (PNG metadata)"),
TRUE,
G_PARAM_READWRITE);
gimp_procedure_add_boolean_argument (procedure, "time",
_("Save creation _time"),
_("Write tIME chunk (PNG metadata)"),
TRUE,
G_PARAM_READWRITE);
gimp_procedure_add_boolean_argument (procedure, "save-transparent",
_("Save color _values from transparent pixels"),
_("Preserve color of completely transparent pixels"),
FALSE,
G_PARAM_READWRITE);
gimp_procedure_add_boolean_argument (procedure, "optimize-palette",
_("_Optimize for smallest possible palette size"),
_("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);
gimp_procedure_add_choice_argument (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);
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);
#if defined(PNG_iCCP_SUPPORTED)
gimp_export_procedure_set_support_profile (GIMP_EXPORT_PROCEDURE (procedure), TRUE);
#endif
gimp_export_procedure_set_support_thumbnail (GIMP_EXPORT_PROCEDURE (procedure), TRUE);
gimp_export_procedure_set_support_comment (GIMP_EXPORT_PROCEDURE (procedure), TRUE);
}
1997-11-24 22:05:25 +00:00
return procedure;
}
static GimpValueArray *
png_load (GimpProcedure *procedure,
GimpRunMode run_mode,
GFile *file,
GimpMetadata *metadata,
GimpMetadataLoadFlags *flags,
GimpProcedureConfig *config,
gpointer run_data)
{
GimpValueArray *return_vals;
gboolean report_progress = FALSE;
gboolean resolution_loaded = FALSE;
gboolean profile_loaded = FALSE;
GimpImage *image;
GError *error = NULL;
gegl_init (NULL, NULL);
if (run_mode != GIMP_RUN_NONINTERACTIVE)
{
gimp_ui_init (PLUG_IN_BINARY);
report_progress = TRUE;
}
image = load_image (file,
report_progress,
&resolution_loaded,
&profile_loaded,
&error);
1997-11-24 22:05:25 +00:00
if (! image)
return gimp_procedure_new_return_values (procedure,
GIMP_PDB_EXECUTION_ERROR,
error);
if (resolution_loaded)
*flags &= ~GIMP_METADATA_LOAD_RESOLUTION;
if (profile_loaded)
*flags &= ~GIMP_METADATA_LOAD_COLORSPACE;
return_vals = gimp_procedure_new_return_values (procedure,
GIMP_PDB_SUCCESS,
NULL);
GIMP_VALUES_SET_IMAGE (return_vals, 1, image);
return return_vals;
}
static GimpValueArray *
png_export (GimpProcedure *procedure,
GimpRunMode run_mode,
GimpImage *image,
GFile *file,
GimpMetadata *metadata,
GimpProcedureConfig *config,
gpointer run_data)
{
GimpPDBStatusType status = GIMP_PDB_SUCCESS;
GimpExportReturn export = GIMP_EXPORT_IGNORE;
GList *drawables;
GimpImage *orig_image;
gboolean alpha;
GError *error = NULL;
gegl_init (NULL, NULL);
orig_image = image;
switch (run_mode)
{
case GIMP_RUN_INTERACTIVE:
case GIMP_RUN_WITH_LAST_VALS:
gimp_ui_init (PLUG_IN_BINARY);
export = gimp_export_image (&image, "PNG",
GIMP_EXPORT_CAN_HANDLE_RGB |
GIMP_EXPORT_CAN_HANDLE_GRAY |
GIMP_EXPORT_CAN_HANDLE_INDEXED |
GIMP_EXPORT_CAN_HANDLE_ALPHA);
break;
default:
break;
}
drawables = gimp_image_list_layers (image);
alpha = gimp_drawable_has_alpha (drawables->data);
/* 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
*/
if (! alpha)
g_object_set (config,
"bkgd", FALSE,
NULL);
if (run_mode == GIMP_RUN_INTERACTIVE)
{
if (! export_dialog (orig_image, procedure, G_OBJECT (config), alpha))
status = GIMP_PDB_CANCEL;
}
if (status == GIMP_PDB_SUCCESS)
{
gint bits_per_sample;
if (export_image (file, image, drawables->data, orig_image, G_OBJECT (config),
&bits_per_sample, run_mode != GIMP_RUN_NONINTERACTIVE,
&error))
{
if (metadata)
gimp_metadata_set_bits_per_sample (metadata, bits_per_sample);
}
else
{
status = GIMP_PDB_EXECUTION_ERROR;
}
}
if (export == GIMP_EXPORT_EXPORT)
gimp_image_delete (image);
configure.in po-plug-ins/POTFILES.in plug-ins/common/Makefile.am 2000-01-25 Michael Natterer <mitch@gimp.org> * configure.in * po-plug-ins/POTFILES.in * plug-ins/common/Makefile.am * plug-ins/common/plugin-defs.pl * plug-ins/megawidget/*: removed. (There were only 3 functions left which were used by ~5 plugins, so I moved the resp. functions to the plugins). More preview stuff to come... * app/airbrush_blob.c * modules/colorsel_triangle.c * modules/colorsel_water.c: use G_PI instead of M_PI. * app/procedural_db.h * libgimp/gimpenums.h * plug-ins/script-fu/script-fu-constants.c * tools/pdbgen/enums.pl: new PDB return value STATUS_CANCEL which indicates that "Cancel" was pressed in a plugin dialog. (Useful only for file load/save plugins). * app/fileops.[ch] * app/menus.c: changes to handle STATUS_CANCEL correctly. Did some code cleanup in fileops.[ch]. Pop up a warning if File->Save failed. * app/plug_in.c: return_val[0] is of type PDB_STATUS, not PDB_INT32. * libgimp/gimpmath.h: new constant G_MAXRAND which equals to RAND_MAX if it exists or to G_MAXINT otherwise. * libgimp/gimpwidgets.[ch]: new function gimp_random_seed_new() which creates a spinbutton and a "Time" toggle. Call the function which does the "set_sensitive" magic from the radio button callback. * plug-ins/[75 plugins]: - Return STATUS_CANCEL in all file load/save dialogs if "Cancel" was pressed. - Standardized the file plugins' "run" functions. - Use G_PI and G_MAXRAND everywhere. - Added tons of scales and spinbuttons instead of text entries. - Applied uniform packing/spacings all over the place. - Reorganized some UIs (stuff like moving the preview to the top left corner of the dialog). - Removed many ui helper functions and callbacks and use the stuff from libgimp instead. - I tried not to restrict the range of possible values when I replaced entries with spinbuttons/scales but may have failed, though in some cases. Please test ;-) - #include <libgimp/gimpmath.h> where appropriate and use it's constants. - Indentation, s/int/gint/ et.al., code cleanup. RFC: The plugins are definitely not useable with GIMP 1.0 any more, so shouldn't we remove all the remaining compatibility stuff ??? (like "#ifdef GIMP_HAVE_PARASITES")
2000-01-25 17:46:56 +00:00
g_list_free (drawables);
return gimp_procedure_new_return_values (procedure, status, error);
}
1997-11-24 22:05:25 +00:00
struct read_error_data
{
guchar *pixel; /* Pixel data */
GeglBuffer *buffer; /* GEGL buffer for layer */
const Babl *file_format;
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
on_read_error (png_structp png_ptr,
png_const_charp error_msg)
{
struct read_error_data *error_data = png_get_error_ptr (png_ptr);
gint begin;
gint end;
gint num;
g_printerr (_("Error loading PNG file: %s\n"), error_msg);
/* 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,
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);
begin = error_data->begin + error_data->tile_height;
if (begin < error_data->height)
{
end = MIN (error_data->end + error_data->tile_height, error_data->height);
num = end - begin;
gegl_buffer_clear (error_data->buffer,
GEGL_RECTANGLE (0, begin, error_data->width, num));
}
g_object_unref (error_data->buffer);
longjmp (png_jmpbuf (png_ptr), 1);
}
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;
}
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;
png_bytep prof;
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;
}
/* 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);
}
/*
* 'load_image()' - Load a PNG image into a new image window.
*/
static GimpImage *
load_image (GFile *file,
gboolean report_progress,
gboolean *resolution_loaded,
gboolean *profile_loaded,
GError **error)
1997-11-24 22:05:25 +00: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 */
volatile GimpImage *image = NULL; /* Image -- protected for setjmp() */
GimpLayer *layer; /* Layer */
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 */
png_voidp user_chunkp; /* PNG unknown chunk pointer */
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;
safe_to_copy_chunks = NULL;
pp = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (! pp)
{
/* this could happen if the compile time and run-time libpng
versions do not match. */
g_set_error (error, G_FILE_ERROR, 0,
_("Error creating PNG read struct while loading '%s'."),
gimp_file_get_utf8_name (file));
return NULL;
}
info = png_create_info_struct (pp);
if (! info)
{
g_set_error (error, G_FILE_ERROR, 0,
_("Error while reading '%s'. Could not create PNG header info structure."),
gimp_file_get_utf8_name (file));
return NULL;
}
1997-11-24 22:05:25 +00:00
if (setjmp (png_jmpbuf (pp)))
{
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("Error while reading '%s'. File corrupted?"),
gimp_file_get_utf8_name (file));
return (GimpImage *) image;
}
#ifdef PNG_BENIGN_ERRORS_SUPPORTED
/* Change some libpng errors to warnings (e.g. bug 721135) */
png_set_benign_errors (pp, TRUE);
/* bug 765850 */
png_set_option (pp, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON);
#endif
/*
* Open the file and initialize the PNG read "engine"...
*/
1997-11-24 22:05:25 +00:00
if (report_progress)
gimp_progress_init_printf (_("Opening '%s'"),
gimp_file_get_utf8_name (file));
fp = g_fopen (g_file_peek_path (file), "rb");
if (fp == NULL)
{
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
_("Could not open '%s' for reading: %s"),
gimp_file_get_utf8_name (file), g_strerror (errno));
return NULL;
}
1997-11-24 22:05:25 +00:00
png_init_io (pp, fp);
png_set_compression_buffer_size (pp, 512);
1997-11-24 22:05:25 +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);
/*
* Get the image info
*/
1997-11-24 22:05:25 +00:00
png_read_info (pp, info);
1997-11-24 22:05:25 +00:00
if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
png_set_swap (pp);
/*
* Get the iCCP (color profile) chunk, if any.
*/
profile = load_color_profile (pp, info, &profile_name);
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);
}
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);
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)))
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);
else if (png_get_valid (pp, info, PNG_INFO_gAMA))
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);
else
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");
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);
}
}
if (profile)
*profile_loaded = TRUE;
/*
* 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.
*/
if (png_get_bit_depth (pp, info) == 16)
image_precision = GIMP_PRECISION_U16_NON_LINEAR;
2012-04-30 02:25:08 +02:00
else
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)
{
2012-04-30 02:25:08 +02:00
if (png_get_color_type (pp, info) == PNG_COLOR_TYPE_GRAY)
png_set_expand (pp);
2012-04-30 02:25:08 +02:00
if (png_get_color_type (pp, info) == PNG_COLOR_TYPE_PALETTE)
png_set_packing (pp);
}
/*
* Expand G+tRNS to GA, RGB+tRNS to RGBA
*/
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
/*
* 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
num_passes = png_set_interlace_handling (pp);
/*
* Special handling for INDEXED + tRNS (transparency palette)
*/
if (png_get_valid (pp, info, PNG_INFO_tRNS) &&
png_get_color_type (pp, info) == PNG_COLOR_TYPE_PALETTE)
{
guchar *alpha_ptr;
png_get_tRNS (pp, info, &alpha_ptr, &num, NULL);
/* Copy the existing alpha values from the tRNS chunk */
for (i = 0; i < num; ++i)
alpha[i] = alpha_ptr[i];
/* And set any others to fully opaque (255) */
for (i = num; i < 256; ++i)
alpha[i] = 255;
trns = 1;
}
else
{
trns = 0;
}
/*
* Update the info structures after the transformations take effect
*/
png_read_update_info (pp, info);
switch (png_get_color_type (pp, info))
{
case PNG_COLOR_TYPE_RGB:
image_type = GIMP_RGB;
layer_type = GIMP_RGB_IMAGE;
break;
case PNG_COLOR_TYPE_RGB_ALPHA:
image_type = GIMP_RGB;
layer_type = GIMP_RGBA_IMAGE;
break;
case PNG_COLOR_TYPE_GRAY:
image_type = GIMP_GRAY;
layer_type = GIMP_GRAY_IMAGE;
break;
case PNG_COLOR_TYPE_GRAY_ALPHA:
image_type = GIMP_GRAY;
layer_type = GIMP_GRAYA_IMAGE;
break;
case PNG_COLOR_TYPE_PALETTE:
image_type = GIMP_INDEXED;
layer_type = GIMP_INDEXED_IMAGE;
break;
default:
g_set_error (error, G_FILE_ERROR, 0,
_("Unknown color model in PNG file '%s'."),
gimp_file_get_utf8_name (file));
return NULL;
}
1997-11-24 22:05:25 +00: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);
if (! image)
{
g_set_error (error, G_FILE_ERROR, 0,
2011-12-06 20:47:37 +05:30
_("Could not create new image for '%s': %s"),
gimp_file_get_utf8_name (file),
gimp_pdb_get_last_error (gimp_get_pdb ()));
return NULL;
}
1997-11-24 22:05:25 +00:00
/*
* Attach the color profile, if any
*/
if (profile)
{
gimp_image_set_color_profile ((GimpImage *) image, profile);
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);
gimp_image_attach_parasite ((GimpImage *) image, parasite);
gimp_parasite_free (parasite);
g_free (profile_name);
}
}
/*
* Create the "background" layer to hold the image...
*/
layer = gimp_layer_new ((GimpImage *) image, _("Background"), width, height,
layer_type,
100,
gimp_image_get_default_new_layer_mode ((GimpImage *) image));
gimp_image_insert_layer ((GimpImage *) image, layer, NULL, 0);
file_format = gimp_drawable_get_format (GIMP_DRAWABLE (layer));
2012-04-30 02:25:08 +02:00
/*
* Find out everything we can about the image resolution
* 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
*/
if (png_get_valid (pp, info, PNG_INFO_oFFs))
{
gint offset_x = png_get_x_offset_pixels (pp, info);
gint offset_y = png_get_y_offset_pixels (pp, info);
if (offset_x != 0 ||
offset_y != 0)
{
if (! report_progress)
{
gimp_layer_set_offsets (layer, offset_x, offset_y);
}
else if (offsets_dialog (offset_x, offset_y))
{
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."));
}
}
}
}
if (png_get_valid (pp, info, PNG_INFO_pHYs))
{
png_uint_32 xres;
png_uint_32 yres;
gint unit_type;
if (png_get_pHYs (pp, info,
&xres, &yres, &unit_type) && xres > 0 && yres > 0)
{
switch (unit_type)
{
case PNG_RESOLUTION_UNKNOWN:
{
gdouble image_xres, image_yres;
gimp_image_get_resolution ((GimpImage *) image, &image_xres, &image_yres);
if (xres > yres)
image_xres = image_yres * (gdouble) xres / (gdouble) yres;
else
image_yres = image_xres * (gdouble) yres / (gdouble) xres;
gimp_image_set_resolution ((GimpImage *) image, image_xres, image_yres);
*resolution_loaded = TRUE;
}
break;
case PNG_RESOLUTION_METER:
gimp_image_set_resolution ((GimpImage *) image,
(gdouble) xres * 0.0254,
(gdouble) yres * 0.0254);
gimp_image_set_unit ((GimpImage *) image, GIMP_UNIT_MM);
*resolution_loaded = TRUE;
break;
default:
break;
}
}
}
/*
* Load the colormap as necessary...
*/
1997-11-24 22:05:25 +00:00
if (png_get_color_type (pp, info) & PNG_COLOR_MASK_PALETTE)
{
png_colorp palette;
int num_palette;
png_get_PLTE (pp, info, &palette, &num_palette);
gimp_image_set_colormap ((GimpImage *) image, (guchar *) palette,
num_palette);
}
1997-11-24 22:05:25 +00:00
bpp = babl_format_get_bytes_per_pixel (file_format);
buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (layer));
1997-11-24 22:05:25 +00:00
/*
* Temporary buffer...
*/
1997-11-24 22:05:25 +00:00
tile_height = gimp_tile_height ();
pixel = g_new0 (guchar, tile_height * width * bpp);
pixels = g_new (guchar *, tile_height);
1997-11-24 22:05:25 +00:00
for (i = 0; i < tile_height; i++)
pixels[i] = pixel + width * bpp * i;
1997-11-24 22:05:25 +00:00
/* Install our own error handler to handle incomplete PNG files better */
error_data.buffer = buffer;
error_data.pixel = pixel;
error_data.file_format = file_format;
error_data.tile_height = tile_height;
error_data.width = width;
error_data.height = height;
error_data.bpp = bpp;
png_set_error_fn (pp, &error_data, on_read_error, NULL);
for (pass = 0; pass < num_passes; pass++)
1997-11-24 22:05:25 +00:00
{
/*
* This works if you are only reading one row at a time...
*/
1997-11-24 22:05:25 +00:00
for (begin = 0; begin < height; begin += tile_height)
{
end = MIN (begin + tile_height, height);
num = end - begin;
1997-11-24 22:05:25 +00:00
if (pass != 0) /* to handle interlaced PiNGs */
2012-04-26 23:07:35 +02:00
gegl_buffer_get (buffer,
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
error_data.begin = begin;
error_data.end = end;
error_data.num = num;
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,
GEGL_RECTANGLE (0, begin, width, num),
2012-04-26 23:07:35 +02:00
0,
file_format,
pixel,
GEGL_AUTO_ROWSTRIDE);
if (report_progress)
gimp_progress_update (((gdouble) pass +
(gdouble) end / (gdouble) height) /
(gdouble) num_passes);
}
}
1997-11-24 22:05:25 +00:00
png_read_end (pp, info);
/* Switch back to default error handler */
png_set_error_fn (pp, NULL, NULL, NULL);
if (png_get_text (pp, info, &text, &num_texts))
{
gchar *comment = NULL;
for (i = 0; i < num_texts && !comment; i++, text++)
{
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);
}
}
if (comment && *comment)
{
GimpParasite *parasite;
parasite = gimp_parasite_new ("gimp-comment",
GIMP_PARASITE_PERSISTENT,
strlen (comment) + 1, comment);
gimp_image_attach_parasite ((GimpImage *) image, parasite);
gimp_parasite_free (parasite);
}
g_free (comment);
}
/*
* Done with the file...
*/
1997-11-24 22:05:25 +00:00
png_destroy_read_struct (&pp, &info, NULL);
1997-11-24 22:05:25 +00:00
g_free (pixel);
g_free (pixels);
g_object_unref (buffer);
free (pp);
free (info);
fclose (fp);
if (trns)
{
GeglBufferIterator *iter;
gint n_components;
gimp_layer_add_alpha (layer);
buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (layer));
file_format = gegl_buffer_get_format (buffer);
iter = gegl_buffer_iterator_new (buffer, NULL, 0, file_format,
GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE, 1);
n_components = babl_format_get_n_components (file_format);
g_warn_if_fail (n_components == 2);
while (gegl_buffer_iterator_next (iter))
{
guchar *data = iter->items[0].data;
gint length = iter->length;
while (length--)
{
data[1] = alpha[data[0]];
data += n_components;
}
}
g_object_unref (buffer);
}
1997-11-24 22:05:25 +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);
}
return (GimpImage *) image;
1997-11-24 22:05:25 +00: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;
gimp_ui_init (PLUG_IN_BINARY);
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);
gimp_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
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);
image = gtk_image_new_from_icon_name (GIMP_ICON_DIALOG_QUESTION,
GTK_ICON_SIZE_DIALOG);
2018-04-29 01:51:36 +02:00
gtk_widget_set_valign (image, GTK_ALIGN_START);
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);
gtk_label_set_yalign (GTK_LABEL (label), 0.0);
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
/*
* 'export_image ()' - Export the specified image to a PNG file.
1997-11-24 22:05:25 +00: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;
static gboolean
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
{
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 */
gchar **parasites; /* Safe-to-copy chunks */
gboolean out_linear; /* Save linear RGB */
GeglBuffer *buffer; /* GEGL buffer for layer */
const Babl *file_format = NULL; /* BABL format of file */
const gchar *encoding;
const Babl *space;
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 */
gint bit_depth; /* Default to bit depth 16 */
guchar remap[256]; /* Re-mapping for the palette */
png_textp text = NULL;
gboolean save_interlaced;
gboolean save_bkgd;
gboolean save_offs;
gboolean save_phys;
gboolean save_time;
gboolean save_comment;
gchar *comment;
gboolean save_transp_pixels;
gboolean optimize_palette;
gint compression_level;
PngExportFormat export_format;
gboolean save_profile;
#if !defined(PNG_iCCP_SUPPORTED)
g_object_set (config,
"save-color-profile", FALSE,
NULL);
#endif
g_object_get (config,
"interlaced", &save_interlaced,
"bkgd", &save_bkgd,
"offs", &save_offs,
"phys", &save_phys,
"time", &save_time,
"save-comment", &save_comment,
"gimp-comment", &comment,
"save-transparent", &save_transp_pixels,
"optimize-palette", &optimize_palette,
"compression", &compression_level,
"save-color-profile", &save_profile,
NULL);
export_format = gimp_procedure_config_get_choice_id (GIMP_PROCEDURE_CONFIG (config), "format");
out_linear = FALSE;
space = gimp_drawable_get_format (drawable);
#if defined(PNG_iCCP_SUPPORTED)
/* 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.
*/
if (save_profile)
{
profile = gimp_image_get_color_profile (orig_image);
if (profile ||
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 ||
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)
{
if (! profile)
profile = gimp_image_get_effective_color_profile (orig_image);
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.
*/
profile = gimp_image_get_effective_color_profile (orig_image);
if (gimp_color_profile_is_linear (profile))
{
GimpColorProfile *saved_profile;
saved_profile = gimp_color_profile_new_srgb_trc_from_color_profile (profile);
g_object_unref (profile);
profile = saved_profile;
}
}
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);
space = gimp_drawable_get_format (drawable);
}
}
#endif
/* 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;
switch (gimp_image_get_precision (image))
{
case GIMP_PRECISION_U8_LINEAR:
if (out_linear)
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:
if (! out_linear)
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
default:
break;
}
2012-04-26 23:07:35 +02:00
pp = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!pp)
{
/* this could happen if the compile time and run-time libpng
* versions do not match.
*/
g_set_error (error, G_FILE_ERROR, 0,
_("Error creating PNG write struct while exporting '%s'."),
gimp_file_get_utf8_name (file));
return FALSE;
}
info = png_create_info_struct (pp);
if (! info)
{
g_set_error (error, G_FILE_ERROR, 0,
_("Error while exporting '%s'. Could not create PNG header info structure."),
gimp_file_get_utf8_name (file));
return FALSE;
}
1997-11-24 22:05:25 +00:00
if (setjmp (png_jmpbuf (pp)))
{
g_set_error (error, G_FILE_ERROR, 0,
_("Error while exporting '%s'. Could not export image."),
gimp_file_get_utf8_name (file));
return FALSE;
}
#ifdef PNG_BENIGN_ERRORS_SUPPORTED
/* Change some libpng errors to warnings (e.g. bug 721135) */
png_set_benign_errors (pp, TRUE);
/* bug 765850 */
png_set_option (pp, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON);
#endif
/*
* Open the file and initialize the PNG write "engine"...
*/
1997-11-24 22:05:25 +00:00
if (report_progress)
gimp_progress_init_printf (_("Exporting '%s'"),
gimp_file_get_utf8_name (file));
fp = g_fopen (g_file_peek_path (file), "wb");
if (! fp)
{
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
_("Could not open '%s' for writing: %s"),
gimp_file_get_utf8_name (file), g_strerror (errno));
return FALSE;
}
png_init_io (pp, fp);
1997-11-24 22:05:25 +00:00
/*
2012-04-26 23:07:35 +02:00
* Get the buffer for the current image...
*/
1997-11-24 22:05:25 +00:00
buffer = gimp_drawable_get_buffer (drawable);
width = gegl_buffer_get_width (buffer);
2012-04-26 23:07:35 +02:00
height = gegl_buffer_get_height (buffer);
type = gimp_drawable_type (drawable);
1997-11-24 22:05:25 +00:00
/*
* Initialise remap[]
*/
for (i = 0; i < 256; i++)
remap[i] = i;
if (export_format == PNG_FORMAT_AUTO)
{
/*
* 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)
{
if (out_linear)
encoding = "RGB u8";
else
encoding = "R'G'B' u8";
}
else
{
if (out_linear)
encoding = "RGB u16";
else
encoding = "R'G'B' u16";
}
break;
case GIMP_RGBA_IMAGE:
color_type = PNG_COLOR_TYPE_RGB_ALPHA;
if (bit_depth == 8)
{
if (out_linear)
encoding = "RGBA u8";
else
encoding = "R'G'B'A u8";
}
else
{
if (out_linear)
encoding = "RGBA u16";
else
encoding = "R'G'B'A u16";
}
break;
case GIMP_GRAY_IMAGE:
color_type = PNG_COLOR_TYPE_GRAY;
if (bit_depth == 8)
{
if (out_linear)
encoding = "Y u8";
else
encoding = "Y' u8";
}
else
{
if (out_linear)
encoding = "Y u16";
else
encoding = "Y' u16";
}
break;
case GIMP_GRAYA_IMAGE:
color_type = PNG_COLOR_TYPE_GRAY_ALPHA;
if (bit_depth == 8)
{
if (out_linear)
encoding = "YA u8";
else
encoding = "Y'A u8";
}
else
{
if (out_linear)
encoding = "YA u16";
else
encoding = "Y'A u16";
}
break;
case GIMP_INDEXED_IMAGE:
color_type = PNG_COLOR_TYPE_PALETTE;
file_format = gimp_drawable_get_format (drawable);
pngg.has_plte = TRUE;
pngg.palette = (png_colorp) gimp_image_get_colormap (image,
NULL,
&pngg.num_palette);
if (optimize_palette)
bit_depth = get_bit_depth_for_palette (pngg.num_palette);
break;
case GIMP_INDEXEDA_IMAGE:
color_type = PNG_COLOR_TYPE_PALETTE;
file_format = gimp_drawable_get_format (drawable);
/* fix up transparency */
if (optimize_palette)
bit_depth = respin_cmap (pp, info, remap, image, drawable);
else
respin_cmap (pp, info, remap, image, drawable);
break;
default:
g_set_error (error, G_FILE_ERROR, 0, "Image type can't be exported as PNG");
return FALSE;
}
}
else
{
switch (export_format)
{
case PNG_FORMAT_RGB8:
color_type = PNG_COLOR_TYPE_RGB;
if (out_linear)
encoding = "RGB u8";
else
encoding = "R'G'B' u8";
bit_depth = 8;
break;
case PNG_FORMAT_GRAY8:
color_type = PNG_COLOR_TYPE_GRAY;
if (out_linear)
encoding = "Y u8";
else
encoding = "Y' u8";
bit_depth = 8;
break;
case PNG_FORMAT_RGBA8:
color_type = PNG_COLOR_TYPE_RGB_ALPHA;
if (out_linear)
encoding = "RGBA u8";
else
encoding = "R'G'B'A u8";
bit_depth = 8;
break;
case PNG_FORMAT_GRAYA8:
color_type = PNG_COLOR_TYPE_GRAY_ALPHA;
if (out_linear)
encoding = "YA u8";
else
encoding = "Y'A u8";
bit_depth = 8;
break;
case PNG_FORMAT_RGB16:
color_type = PNG_COLOR_TYPE_RGB;
if (out_linear)
encoding = "RGB u16";
else
encoding = "R'G'B' u16";
bit_depth = 16;
break;
case PNG_FORMAT_GRAY16:
color_type = PNG_COLOR_TYPE_GRAY;
if (out_linear)
encoding = "Y u16";
else
encoding = "Y' u16";
bit_depth = 16;
break;
case PNG_FORMAT_RGBA16:
color_type = PNG_COLOR_TYPE_RGB_ALPHA;
if (out_linear)
encoding = "RGBA u16";
else
encoding = "R'G'B'A u16";
bit_depth = 16;
break;
case PNG_FORMAT_GRAYA16:
color_type = PNG_COLOR_TYPE_GRAY_ALPHA;
if (out_linear)
encoding = "YA u16";
else
encoding = "Y'A u16";
bit_depth = 16;
break;
case PNG_FORMAT_AUTO:
g_return_val_if_reached (FALSE);
}
}
1997-11-24 22:05:25 +00:00
if (! file_format)
file_format = babl_format_with_space (encoding, space);
2012-04-26 23:07:35 +02:00
bpp = babl_format_get_bytes_per_pixel (file_format);
/* 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,
save_interlaced ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE,
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);
if (pngg.has_plte)
2012-04-30 02:25:08 +02:00
png_set_PLTE (pp, info, pngg.palette, pngg.num_palette);
/* Set the compression level */
png_set_compression_level (pp, compression_level);
/* All this stuff is optional extras, if the user is aiming for smallest
possible file size she can turn them all off */
if (save_bkgd)
{
GeglColor *color;
png_color_16 background; /* Background color */
background.index = 0;
color = gimp_context_get_background ();
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;
}
png_set_bKGD (pp, info, &background);
g_object_unref (color);
}
if (save_offs)
{
gimp_drawable_get_offsets (drawable, &offx, &offy);
if (offx != 0 || offy != 0)
2012-04-30 02:25:08 +02:00
png_set_oFFs (pp, info, offx, offy, PNG_OFFSET_PIXEL);
}
if (save_phys)
{
gimp_image_get_resolution (orig_image, &xres, &yres);
png_set_pHYs (pp, info, RINT (xres / 0.0254), RINT (yres / 0.0254),
PNG_RESOLUTION_METER);
}
if (save_time)
{
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);
}
#if defined(PNG_iCCP_SUPPORTED)
if (save_profile)
{
GimpParasite *parasite;
gchar *profile_name = NULL;
const guint8 *icc_data;
gsize icc_length;
icc_data = gimp_color_profile_get_icc_profile (profile, &icc_length);
parasite = gimp_image_get_parasite (orig_image,
"icc-profile-name");
if (parasite)
{
gchar *parasite_data;
guint32 parasite_size;
parasite_data = (gchar *) gimp_parasite_get_data (parasite, &parasite_size);
profile_name = g_convert (parasite_data, parasite_size,
"UTF-8", "ISO-8859-1", NULL, NULL, NULL);
}
png_set_iCCP (pp,
info,
profile_name ? profile_name : "ICC profile",
0,
icc_data,
icc_length);
g_free (profile_name);
g_object_unref (profile);
}
else
#endif
{
/* 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);
}
#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
if (save_comment && comment && strlen (comment))
{
gsize text_length = 0;
text = g_new0 (png_text, 1);
text[0].key = "Comment";
#ifdef PNG_iTXt_SUPPORTED
text[0].text = g_convert (comment, -1,
"ISO-8859-1",
"UTF-8",
NULL,
&text_length,
NULL);
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);
text[0].text = g_strdup (comment);
text[0].itxt_length = strlen (text[0].text);
#ifdef PNG_zTXt_SUPPORTED
text[0].compression = strlen (text[0].text) > COMPRESSION_WORTHY_LENGTH ?
PNG_ITXT_COMPRESSION_zTXt : PNG_ITXT_COMPRESSION_NONE;
#else
text[0].compression = PNG_ITXT_COMPRESSION_NONE;
#endif /* PNG_zTXt_SUPPORTED */
}
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.
*/
#endif /* PNG_iTXt_SUPPORTED */
{
#ifndef PNG_iTXt_SUPPORTED
/* 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);
#endif
#ifdef PNG_zTXt_SUPPORTED
text[0].compression = strlen (text[0].text) > COMPRESSION_WORTHY_LENGTH ?
PNG_TEXT_COMPRESSION_zTXt : PNG_TEXT_COMPRESSION_NONE;
#else
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
#endif /* PNG_zTXt_SUPPORTED */
text[0].text_length = text_length;
}
if (! text[0].text || strlen (text[0].text) == 0)
{
g_free (text[0].text);
g_free (text);
text = NULL;
}
}
g_free (comment);
#ifdef PNG_zTXt_SUPPORTED
#undef COMPRESSION_WORTHY_LENGTH
#endif
if (text)
png_set_text (pp, info, text, 1);
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
/* 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);
/*
* Turn on interlace handling...
*/
1997-11-24 22:05:25 +00:00
if (save_interlaced)
num_passes = png_set_interlace_handling (pp);
1997-11-24 22:05:25 +00:00
else
num_passes = 1;
/*
* Convert unpacked pixels to packed if necessary
*/
if (color_type == PNG_COLOR_TYPE_PALETTE &&
bit_depth < 8)
png_set_packing (pp);
1997-11-24 22:05:25 +00:00
/*
* Allocate memory for "tile_height" rows and export the image...
*/
1997-11-24 22:05:25 +00:00
tile_height = gimp_tile_height ();
2012-04-26 23:07:35 +02:00
pixel = g_new (guchar, tile_height * width * bpp);
pixels = g_new (guchar *, tile_height);
1997-11-24 22:05:25 +00:00
for (i = 0; i < tile_height; i++)
2012-04-26 23:07:35 +02:00
pixels[i] = pixel + width * bpp * i;
for (pass = 0; pass < num_passes; pass++)
{
/* This works if you are only writing one row at a time... */
for (begin = 0, end = tile_height;
2012-04-26 23:07:35 +02:00
begin < height; begin += tile_height, end += tile_height)
{
2012-04-26 23:07:35 +02:00
if (end > height)
end = height;
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 */
if (bpp == 4 && ! save_transp_pixels)
{
for (i = 0; i < num; ++i)
{
fixed = pixels[i];
2012-04-26 23:07:35 +02:00
for (k = 0; k < width; ++k)
{
if (!fixed[3])
fixed[0] = fixed[1] = fixed[2] = 0;
fixed += bpp;
}
}
}
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)
{
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
}
}
}
/* If we're dealing with a paletted image with
* transparency set, write out the remapped palette */
if (pngg.has_trns)
{
guchar inverse_remap[256];
for (i = 0; i < 256; i++)
inverse_remap[ remap[i] ] = i;
for (i = 0; i < num; ++i)
{
fixed = pixels[i];
2012-04-26 23:07:35 +02:00
for (k = 0; k < width; ++k)
{
fixed[k] = (fixed[k*2+1] > 127) ?
inverse_remap[ fixed[k*2] ] :
0;
}
}
}
2011-05-06 17:51:09 +05:30
/* Otherwise if we have a paletted image and transparency
* couldn't be set, we ignore the alpha channel */
else if (png_get_valid (pp, info, PNG_INFO_PLTE) &&
bpp == 2)
{
for (i = 0; i < num; ++i)
{
fixed = pixels[i];
2012-04-26 23:07:35 +02:00
for (k = 0; k < width; ++k)
{
fixed[k] = fixed[k * 2];
}
}
}
png_write_rows (pp, pixels, num);
if (report_progress)
gimp_progress_update (((double) pass + (double) end /
(double) height) /
(double) num_passes);
}
}
1997-11-24 22:05:25 +00:00
if (report_progress)
gimp_progress_update (1.0);
png_write_end (pp, info);
png_destroy_write_struct (&pp, &info);
1997-11-24 22:05:25 +00:00
g_free (pixel);
g_free (pixels);
1997-11-24 22:05:25 +00:00
/*
* Done with the file...
*/
1997-11-24 22:05:25 +00:00
if (text)
{
g_free (text[0].text);
g_free (text);
}
free (pp);
free (info);
1997-11-24 22:05:25 +00:00
fclose (fp);
1997-11-24 22:05:25 +00:00
*bits_per_sample = bit_depth;
return TRUE;
1997-11-24 22:05:25 +00:00
}
static gboolean
ia_has_transparent_pixels (GeglBuffer *buffer)
{
GeglBufferIterator *iter;
const Babl *format;
gint n_components;
format = gegl_buffer_get_format (buffer);
iter = gegl_buffer_iterator_new (buffer, NULL, 0, format,
GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 1);
n_components = babl_format_get_n_components (format);
g_return_val_if_fail (n_components == 2, FALSE);
while (gegl_buffer_iterator_next (iter))
{
const guchar *data = iter->items[0].data;
gint length = iter->length;
while (length--)
{
if (data[1] <= 127)
{
gegl_buffer_iterator_stop (iter);
return TRUE;
}
data += n_components;
}
}
return FALSE;
}
/* 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
*/
static gint
find_unused_ia_color (GeglBuffer *buffer,
gint *colors)
{
GeglBufferIterator *iter;
const Babl *format;
gint n_components;
gboolean ix_used[256];
gboolean trans_used = FALSE;
gint i;
for (i = 0; i < *colors; i++)
ix_used[i] = FALSE;
format = gegl_buffer_get_format (buffer);
iter = gegl_buffer_iterator_new (buffer, NULL, 0, format,
GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 1);
n_components = babl_format_get_n_components (format);
g_return_val_if_fail (n_components == 2, FALSE);
while (gegl_buffer_iterator_next (iter))
{
const guchar *data = iter->items[0].data;
gint length = iter->length;
while (length--)
{
if (data[1] > 127)
ix_used[data[0]] = TRUE;
else
trans_used = TRUE;
data += n_components;
}
}
/* If there is no transparency, ignore alpha. */
if (trans_used == FALSE)
return -1;
/* 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
* there.
*/
if ((*colors) < 256)
{
(*colors)++;
return (*colors) - 1;
}
for (i = 0; i < *colors; i++)
{
if (ix_used[i] == FALSE)
return i;
}
return -1;
}
static int
respin_cmap (png_structp pp,
png_infop info,
guchar *remap,
GimpImage *image,
GimpDrawable *drawable)
{
static guchar trans[] = { 0 };
GeglBuffer *buffer;
gint colors;
guchar *before;
before = gimp_image_get_colormap (image, NULL, &colors);
buffer = gimp_drawable_get_buffer (drawable);
/* Make sure there is something in the colormap.
*/
if (colors == 0)
{
before = g_newa (guchar, 3);
memset (before, 0, sizeof (guchar) * 3);
colors = 1;
}
/* Try to find an entry which isn't actually used in the image, for
* a transparency index.
*/
if (ia_has_transparent_pixels (buffer))
{
gint transparent = find_unused_ia_color (buffer, &colors);
if (transparent != -1) /* we have a winner for a transparent
* index - do like gif2png and swap
* index 0 and index transparent
*/
{
static png_color palette[256];
gint i;
/* Set tRNS chunk values for writing later. */
pngg.has_trns = TRUE;
pngg.trans = trans;
pngg.num_trans = 1;
/* Transform all pixels with a value = transparent to 0 and
* vice versa to compensate for re-ordering in palette due
* to png_set_tRNS()
*/
remap[0] = transparent;
for (i = 1; i <= transparent; i++)
remap[i] = i - 1;
/* Copy from index 0 to index transparent - 1 to index 1 to
* transparent of after, then from transparent+1 to colors-1
* unchanged, and finally from index transparent to index 0.
*/
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];
}
/* Set PLTE chunk values for writing later. */
pngg.has_plte = TRUE;
pngg.palette = palette;
pngg.num_palette = colors;
}
else
{
/* Inform the user that we couldn't losslessly save the
* transparency & just use the full palette
*/
g_message (_("Couldn't losslessly save transparency, "
"saving opacity instead."));
/* Set PLTE chunk values for writing later. */
pngg.has_plte = TRUE;
pngg.palette = (png_colorp) before;
pngg.num_palette = colors;
}
}
else
{
/* Set PLTE chunk values for writing later. */
pngg.has_plte = TRUE;
pngg.palette = (png_colorp) before;
pngg.num_palette = colors;
}
g_object_unref (buffer);
return get_bit_depth_for_palette (colors);
}
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;
}
static gboolean
export_dialog (GimpImage *image,
GimpProcedure *procedure,
GObject *config,
gboolean alpha)
1997-11-24 22:05:25 +00:00
{
GtkWidget *dialog;
gboolean run;
gboolean indexed;
indexed = (gimp_image_get_base_type (image) == GIMP_INDEXED);
dialog = gimp_export_procedure_dialog_new (GIMP_EXPORT_PROCEDURE (procedure),
GIMP_PROCEDURE_CONFIG (config),
image);
gimp_procedure_dialog_get_widget (GIMP_PROCEDURE_DIALOG (dialog),
"compression", GIMP_TYPE_SPIN_SCALE);
gimp_procedure_dialog_set_sensitive (GIMP_PROCEDURE_DIALOG (dialog),
"save-transparent",
alpha, NULL, NULL, FALSE);
gimp_procedure_dialog_set_sensitive (GIMP_PROCEDURE_DIALOG (dialog),
"optimize-palette",
indexed, NULL, NULL, FALSE);
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");
gimp_procedure_dialog_fill (GIMP_PROCEDURE_DIALOG (dialog),
"format", "compression",
"interlaced", "save-transparent",
"optimize-palette",
NULL);
run = gimp_procedure_dialog_run (GIMP_PROCEDURE_DIALOG (dialog));
gtk_widget_destroy (dialog);
return run;
1997-11-24 22:05:25 +00:00
}