2016-02-16 02:35:43 +01:00
|
|
|
/* GIF exporting file filter for GIMP
|
1997-11-24 22:05:25 +00:00
|
|
|
*
|
2003-06-16 17:37:11 +00:00
|
|
|
* Copyright
|
1998-03-16 10:05:41 +00:00
|
|
|
* - Adam D. Moss
|
|
|
|
* - Peter Mattis
|
|
|
|
* - Spencer Kimball
|
1997-11-24 22:05:25 +00:00
|
|
|
*
|
1998-03-16 10:05:41 +00:00
|
|
|
* Based around original GIF code by David Koblas.
|
1997-11-24 22:05:25 +00:00
|
|
|
*
|
|
|
|
*
|
2003-06-16 17:37:11 +00:00
|
|
|
* Version 4.1.0 - 2003-06-16
|
1998-03-16 10:05:41 +00:00
|
|
|
* Adam D. Moss - <adam@gimp.org> <adam@foxbox.org>
|
1997-11-24 22:05:25 +00:00
|
|
|
*/
|
|
|
|
/*
|
|
|
|
* This filter uses code taken from the "giftopnm" and "ppmtogif" programs
|
|
|
|
* which are part of the "netpbm" package.
|
|
|
|
*/
|
|
|
|
/*
|
|
|
|
* "The Graphics Interchange Format(c) is the Copyright property of
|
|
|
|
* CompuServe Incorporated. GIF(sm) is a Service Mark property of
|
2003-11-06 15:27:05 +00:00
|
|
|
* CompuServe Incorporated."
|
1997-11-24 22:05:25 +00:00
|
|
|
*/
|
2003-05-13 14:59:03 +00:00
|
|
|
/* Copyright notice for GIF code from which this plugin was long ago */
|
|
|
|
/* derived (David Koblas has granted permission to relicense): */
|
|
|
|
/* +-------------------------------------------------------------------+ */
|
|
|
|
/* | Copyright 1990, 1991, 1993, David Koblas. (koblas@extra.com) | */
|
|
|
|
/* +-------------------------------------------------------------------+ */
|
1997-11-24 22:05:25 +00:00
|
|
|
|
1999-05-29 16:35:47 +00:00
|
|
|
#include "config.h"
|
2000-01-06 22:26:10 +00:00
|
|
|
|
1997-11-24 22:05:25 +00:00
|
|
|
#include <string.h>
|
2000-01-06 22:26:10 +00:00
|
|
|
|
2000-01-25 17:46:56 +00:00
|
|
|
#include <libgimp/gimp.h>
|
|
|
|
#include <libgimp/gimpui.h>
|
2000-01-06 22:26:10 +00:00
|
|
|
|
1999-05-29 16:35:47 +00:00
|
|
|
#include "libgimp/stdplugins-intl.h"
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2000-04-30 21:03:44 +00:00
|
|
|
|
2024-04-13 15:10:25 +00:00
|
|
|
#define EXPORT_PROC "file-gif-export"
|
|
|
|
#define PLUG_IN_BINARY "file-gif-export"
|
2005-08-13 22:52:41 +00:00
|
|
|
|
|
|
|
|
1998-10-09 18:01:35 +00:00
|
|
|
/* uncomment the line below for a little debugging info */
|
|
|
|
/* #define GIFDEBUG yesplease */
|
|
|
|
|
|
|
|
|
2000-01-25 17:46:56 +00:00
|
|
|
enum
|
|
|
|
{
|
|
|
|
DISPOSE_UNSPECIFIED,
|
|
|
|
DISPOSE_COMBINE,
|
|
|
|
DISPOSE_REPLACE
|
|
|
|
};
|
1998-10-09 18:01:35 +00:00
|
|
|
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2019-08-24 15:57:54 +02:00
|
|
|
typedef struct _Gif Gif;
|
|
|
|
typedef struct _GifClass GifClass;
|
|
|
|
|
|
|
|
struct _Gif
|
|
|
|
{
|
|
|
|
GimpPlugIn parent_instance;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct _GifClass
|
|
|
|
{
|
|
|
|
GimpPlugInClass parent_class;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
#define GIF_TYPE (gif_get_type ())
|
2023-10-18 18:29:37 +02:00
|
|
|
#define GIF(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIF_TYPE, Gif))
|
2019-08-24 15:57:54 +02:00
|
|
|
|
2019-10-28 12:12:09 +01:00
|
|
|
GType gif_get_type (void) G_GNUC_CONST;
|
2008-08-17 21:30:34 +00:00
|
|
|
|
2019-10-28 12:12:09 +01:00
|
|
|
static GList * gif_query_procedures (GimpPlugIn *plug_in);
|
|
|
|
static GimpProcedure * gif_create_procedure (GimpPlugIn *plug_in,
|
2019-08-24 15:57:54 +02:00
|
|
|
const gchar *name);
|
2003-07-02 11:07:41 +00:00
|
|
|
|
2024-04-13 15:10:25 +00:00
|
|
|
static GimpValueArray * gif_export (GimpProcedure *procedure,
|
2019-08-24 15:57:54 +02:00
|
|
|
GimpRunMode run_mode,
|
|
|
|
GimpImage *image,
|
|
|
|
GFile *file,
|
2024-05-06 18:38:12 +00:00
|
|
|
GimpExportOptions *options,
|
2023-07-20 23:16:27 +02:00
|
|
|
GimpMetadata *metadata,
|
|
|
|
GimpProcedureConfig *config,
|
2019-08-24 15:57:54 +02:00
|
|
|
gpointer run_data);
|
1999-10-03 18:54:54 +00:00
|
|
|
|
2024-04-13 15:10:25 +00:00
|
|
|
static gboolean export_image (GFile *file,
|
2019-08-24 15:57:54 +02:00
|
|
|
GimpImage *image,
|
|
|
|
GimpDrawable *drawable,
|
|
|
|
GimpImage *orig_image,
|
2019-10-28 12:12:09 +01:00
|
|
|
GObject *config,
|
2019-08-24 15:57:54 +02:00
|
|
|
GError **error);
|
|
|
|
|
2024-05-06 18:38:12 +00:00
|
|
|
static void export_edit_options (GimpProcedure *procedure,
|
|
|
|
GimpProcedureConfig *config,
|
|
|
|
GimpExportOptions *options,
|
|
|
|
gpointer create_data);
|
|
|
|
|
2019-08-24 15:57:54 +02:00
|
|
|
static GimpPDBStatusType sanity_check (GFile *file,
|
|
|
|
GimpImage **image,
|
|
|
|
GimpRunMode run_mode,
|
|
|
|
GError **error);
|
2019-10-28 12:12:09 +01:00
|
|
|
static gboolean bad_bounds_dialog (void);
|
2019-08-24 15:57:54 +02:00
|
|
|
|
2019-10-28 12:12:09 +01:00
|
|
|
static gboolean save_dialog (GimpImage *image,
|
2024-01-21 14:55:38 +00:00
|
|
|
GimpImage *orig_image,
|
2019-10-28 12:12:09 +01:00
|
|
|
GimpProcedure *procedure,
|
|
|
|
GObject *config);
|
2019-08-24 15:57:54 +02:00
|
|
|
|
|
|
|
|
|
|
|
G_DEFINE_TYPE (Gif, gif, GIMP_TYPE_PLUG_IN)
|
|
|
|
|
|
|
|
GIMP_MAIN (GIF_TYPE)
|
2022-05-26 00:59:36 +02:00
|
|
|
DEFINE_STD_SET_I18N
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
|
2019-10-28 12:12:09 +01:00
|
|
|
static gint Interlace; /* For compression code */
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
|
2019-08-24 15:57:54 +02:00
|
|
|
static void
|
|
|
|
gif_class_init (GifClass *klass)
|
|
|
|
{
|
|
|
|
GimpPlugInClass *plug_in_class = GIMP_PLUG_IN_CLASS (klass);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2019-08-24 15:57:54 +02:00
|
|
|
plug_in_class->query_procedures = gif_query_procedures;
|
|
|
|
plug_in_class->create_procedure = gif_create_procedure;
|
2022-05-26 00:59:36 +02:00
|
|
|
plug_in_class->set_i18n = STD_SET_I18N;
|
2019-08-24 15:57:54 +02:00
|
|
|
}
|
2014-05-16 17:36:34 +01:00
|
|
|
|
1997-11-24 22:05:25 +00:00
|
|
|
static void
|
2019-08-24 15:57:54 +02:00
|
|
|
gif_init (Gif *gif)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2019-08-24 15:57:54 +02:00
|
|
|
static GList *
|
|
|
|
gif_query_procedures (GimpPlugIn *plug_in)
|
|
|
|
{
|
2024-04-13 15:10:25 +00:00
|
|
|
return g_list_append (NULL, g_strdup (EXPORT_PROC));
|
2019-08-24 15:57:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static GimpProcedure *
|
|
|
|
gif_create_procedure (GimpPlugIn *plug_in,
|
|
|
|
const gchar *name)
|
|
|
|
{
|
|
|
|
GimpProcedure *procedure = NULL;
|
|
|
|
|
2024-04-13 15:10:25 +00:00
|
|
|
if (! strcmp (name, EXPORT_PROC))
|
2019-08-24 15:57:54 +02:00
|
|
|
{
|
2024-04-20 03:08:57 +00:00
|
|
|
procedure = gimp_export_procedure_new (plug_in, name,
|
|
|
|
GIMP_PDB_PROC_TYPE_PLUGIN,
|
|
|
|
FALSE, gif_export, NULL, NULL);
|
2019-08-24 15:57:54 +02:00
|
|
|
|
|
|
|
gimp_procedure_set_image_types (procedure, "INDEXED*, GRAY*");
|
|
|
|
|
2022-07-04 22:50:53 +02:00
|
|
|
gimp_procedure_set_menu_label (procedure, _("GIF image"));
|
2023-04-23 17:35:52 +00:00
|
|
|
gimp_file_procedure_set_format_name (GIMP_FILE_PROCEDURE (procedure),
|
|
|
|
_("GIF"));
|
2019-08-24 15:57:54 +02:00
|
|
|
|
|
|
|
gimp_procedure_set_documentation (procedure,
|
2023-06-15 08:29:46 +02:00
|
|
|
_("exports files in GIF "
|
2023-04-23 17:35:52 +00:00
|
|
|
"file format"),
|
2023-07-07 17:36:46 +02:00
|
|
|
/* xgettext: no-c-format */
|
2023-06-15 08:29:46 +02:00
|
|
|
_("Export a file in GIF "
|
2023-04-23 17:35:52 +00:00
|
|
|
"format, with possible animation, "
|
|
|
|
"transparency, and comment. To export "
|
|
|
|
"an animation, operate on a multi-layer "
|
|
|
|
"file and give the 'as-animation' "
|
|
|
|
"parameter as TRUE. The plug-in will "
|
|
|
|
"interpret <50% alpha as transparent. "
|
|
|
|
"When run non-interactively, the value "
|
|
|
|
"for the comment is taken from the "
|
2023-06-15 08:29:46 +02:00
|
|
|
"'gimp-comment' parasite."),
|
2019-08-24 15:57:54 +02:00
|
|
|
name);
|
|
|
|
gimp_procedure_set_attribution (procedure,
|
|
|
|
"Spencer Kimball, Peter Mattis, "
|
|
|
|
"Adam Moss, David Koblas",
|
|
|
|
"Spencer Kimball, Peter Mattis, "
|
|
|
|
"Adam Moss, David Koblas",
|
|
|
|
"1995-1997");
|
|
|
|
|
|
|
|
gimp_file_procedure_set_handles_remote (GIMP_FILE_PROCEDURE (procedure),
|
|
|
|
TRUE);
|
|
|
|
gimp_file_procedure_set_mime_types (GIMP_FILE_PROCEDURE (procedure),
|
|
|
|
"image/gif");
|
|
|
|
gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure),
|
|
|
|
"gif");
|
|
|
|
|
2024-05-06 18:38:12 +00:00
|
|
|
gimp_export_procedure_set_capabilities (GIMP_EXPORT_PROCEDURE (procedure),
|
|
|
|
GIMP_EXPORT_CAN_HANDLE_INDEXED |
|
|
|
|
GIMP_EXPORT_CAN_HANDLE_GRAY |
|
|
|
|
GIMP_EXPORT_CAN_HANDLE_ALPHA,
|
app, libgimp*, pdb, plug-ins: review and enhance MR !1549.
- Fix annotations for gimp_export_options_get_image() to make it
actually introspectable with the GimpImage being both input and
output. Even though the logic doesn't change much (the input image may
be overriden or not), it doesn't matter for introspection because
images are handled centrally by libgimp and therefore must not be
freed. Actually deleting the image from the central list of images
though remains a manual action depending on code logic, not some
automatic action to be handled by binding engines.
- Add G_GNUC_WARN_UNUSED_RESULT to gimp_export_options_get_image()
because ignoring the returned value is rarely a good idea (as you
usually want to delete the image).
- Remove gimp_export_options_new(): we don't need this constructor
because at this point, the best is to tell plug-in developers to just
pass NULL everywhere. This leaves us free to create a more useful
default constructor if needed, in the future. Main description for
GimpExportOptions has also been updated to say this.
- Add a data_destroy callback for the user data passed in
gimp_export_procedure_set_capabilities().
- Fixing annotations of 'export_options' object from pdb/pdb.pl: input
args would actually be (nullable) and would not transfer ownership
(calling code must still free the object). Return value's ownership on
the other hand is fully transfered.
- Add C and Python unit testing for GimpExportOptions and
gimp_export_options_get_image() in particular.
- Fix or improve various details.
Note that I have also considered for a long time changing the signature
of gimp_export_options_get_image() to return a boolean indicating
whether `image` had been replaced (hence needed deletion) or not. This
also meant getting rid of the GimpExportReturn enum. Right now it would
work because there are no third case, but I was considering the future
possibility that for instance we got some impossible conversion for some
future capability. I'm not sure it would ever happen; and for sure, this
is not desirable because it implies an export failure a bit late in the
workflow. But just in case, let's keep the enum return value. It does
not even make the using code that much more complicated (well just a
value comparison instead of a simple boolean test).
2024-08-17 15:06:27 +02:00
|
|
|
export_edit_options, NULL, NULL);
|
2024-05-06 18:38:12 +00:00
|
|
|
|
2024-06-12 16:53:12 +00:00
|
|
|
gimp_procedure_add_boolean_argument (procedure, "interlace",
|
|
|
|
_("_Interlace"),
|
|
|
|
_("Try to export as interlaced"),
|
|
|
|
FALSE,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
|
|
|
gimp_procedure_add_boolean_argument (procedure, "loop",
|
|
|
|
_("Loop _Forever"),
|
|
|
|
_("(animated gif) Loop infinitely"),
|
|
|
|
TRUE,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
|
|
|
gimp_procedure_add_int_argument (procedure, "number-of-repeats",
|
|
|
|
_("_Number of repeats"),
|
|
|
|
_("(animated gif) Number of repeats "
|
|
|
|
"(Ignored if 'loop' is TRUE)"),
|
|
|
|
0, G_MAXSHORT - 1, 0,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
|
|
|
gimp_procedure_add_int_argument (procedure, "default-delay",
|
|
|
|
_("_Delay between frames when unspecified"),
|
|
|
|
_("(animated gif) Default delay between frames "
|
|
|
|
"in milliseconds"),
|
|
|
|
0, G_MAXINT, 100,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
2024-08-19 14:40:46 +00:00
|
|
|
gimp_procedure_add_choice_argument (procedure, "default-dispose",
|
|
|
|
_("Frame disposal _when unspecified"),
|
|
|
|
_("(animated gif) Default disposal type"),
|
|
|
|
gimp_choice_new_with_values ("unspecified", DISPOSE_UNSPECIFIED, _("I don't care"), NULL,
|
|
|
|
"combine", DISPOSE_COMBINE, _("Cumulative layers (combine)"), NULL,
|
|
|
|
"replace", DISPOSE_REPLACE, _("One frame per layer (replace)"), NULL,
|
|
|
|
NULL),
|
|
|
|
"unspecified",
|
|
|
|
G_PARAM_READWRITE);
|
2024-06-12 16:53:12 +00:00
|
|
|
|
|
|
|
gimp_procedure_add_boolean_argument (procedure, "as-animation",
|
|
|
|
_("_As animation"),
|
|
|
|
_("Export GIF as animation?"),
|
|
|
|
FALSE,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
|
|
|
gimp_procedure_add_boolean_argument (procedure, "force-delay",
|
|
|
|
_("_Use delay entered above for all frames"),
|
|
|
|
_("(animated gif) Use specified delay for all frames"),
|
|
|
|
FALSE,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
|
|
|
gimp_procedure_add_boolean_argument (procedure, "force-dispose",
|
|
|
|
_("Use dis_posal entered above "
|
|
|
|
"for all frames"),
|
|
|
|
_("(animated gif) Use specified disposal for all frames"),
|
|
|
|
FALSE,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
|
|
|
gimp_procedure_add_boolean_aux_argument (procedure, "save-comment",
|
|
|
|
_("Sa_ve comment"),
|
|
|
|
_("Save the image comment in the GIF file"),
|
|
|
|
gimp_export_comment (),
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
|
|
|
gimp_procedure_add_string_aux_argument (procedure, "gimp-comment",
|
|
|
|
_("Commen_t"),
|
|
|
|
_("Image comment"),
|
|
|
|
gimp_get_default_comment (),
|
|
|
|
G_PARAM_READWRITE);
|
2020-06-15 23:54:09 +02:00
|
|
|
|
|
|
|
gimp_procedure_set_argument_sync (procedure, "gimp-comment",
|
|
|
|
GIMP_ARGUMENT_SYNC_PARASITE);
|
2019-08-24 15:57:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return procedure;
|
|
|
|
}
|
|
|
|
|
|
|
|
static GimpValueArray *
|
2024-04-13 15:10:25 +00:00
|
|
|
gif_export (GimpProcedure *procedure,
|
|
|
|
GimpRunMode run_mode,
|
|
|
|
GimpImage *image,
|
|
|
|
GFile *file,
|
2024-05-06 18:38:12 +00:00
|
|
|
GimpExportOptions *options,
|
2024-04-13 15:10:25 +00:00
|
|
|
GimpMetadata *metadata,
|
|
|
|
GimpProcedureConfig *config,
|
|
|
|
gpointer run_data)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
2024-04-30 13:50:24 +00:00
|
|
|
GimpPDBStatusType status = GIMP_PDB_SUCCESS;
|
|
|
|
GimpExportReturn export = GIMP_EXPORT_IGNORE;
|
2023-07-20 23:16:27 +02:00
|
|
|
GimpImage *orig_image;
|
|
|
|
GimpImage *sanitized_image = NULL;
|
2023-11-11 15:35:23 +00:00
|
|
|
GimpParasite *parasite = NULL;
|
2023-07-20 23:16:27 +02:00
|
|
|
GError *error = NULL;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2012-09-22 23:19:32 +02:00
|
|
|
gegl_init (NULL, NULL);
|
|
|
|
|
2019-08-24 15:57:54 +02:00
|
|
|
orig_image = image;
|
2012-11-27 20:58:05 +01:00
|
|
|
|
2019-08-24 15:57:54 +02:00
|
|
|
if (run_mode == GIMP_RUN_INTERACTIVE ||
|
|
|
|
run_mode == GIMP_RUN_WITH_LAST_VALS)
|
2019-09-20 19:39:00 +02:00
|
|
|
gimp_ui_init (PLUG_IN_BINARY);
|
2008-03-26 13:12:02 +00:00
|
|
|
|
2019-08-24 15:57:54 +02:00
|
|
|
status = sanity_check (file, &image, run_mode, &error);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2019-08-24 15:57:54 +02:00
|
|
|
/* Get the export options */
|
|
|
|
if (status == GIMP_PDB_SUCCESS)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
2019-08-24 15:57:54 +02:00
|
|
|
/* If the sanity check succeeded, the image_ID will point to a
|
|
|
|
* duplicate image to delete later.
|
|
|
|
*/
|
|
|
|
sanitized_image = image;
|
2008-08-17 21:30:34 +00:00
|
|
|
|
2023-11-11 15:35:23 +00:00
|
|
|
/* If imported as an animation, set the animation configurations
|
|
|
|
* when overwriting the file */
|
|
|
|
parasite = gimp_image_get_parasite (image, "gif/animated");
|
|
|
|
if (parasite)
|
|
|
|
{
|
|
|
|
gint num_loops;
|
|
|
|
gchar *parasite_data;
|
|
|
|
guint32 parasite_size;
|
|
|
|
|
|
|
|
parasite_data = (gchar *) gimp_parasite_get_data (parasite, ¶site_size);
|
|
|
|
parasite_data = g_strndup (parasite_data, parasite_size);
|
|
|
|
|
|
|
|
if (sscanf (parasite_data, "%i", &num_loops) == 1)
|
|
|
|
{
|
|
|
|
gboolean loop = (num_loops == 0);
|
|
|
|
|
|
|
|
g_object_set (config,
|
|
|
|
"as-animation", TRUE,
|
|
|
|
"loop", loop,
|
|
|
|
"number-of-repeats", num_loops,
|
|
|
|
NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
gimp_image_detach_parasite (image, "gif/animated");
|
|
|
|
g_free (parasite);
|
|
|
|
g_free (parasite_data);
|
|
|
|
}
|
|
|
|
|
2019-10-28 12:12:09 +01:00
|
|
|
if (run_mode == GIMP_RUN_INTERACTIVE)
|
2006-04-13 11:16:13 +00:00
|
|
|
{
|
2024-01-21 14:55:38 +00:00
|
|
|
if (! save_dialog (image, orig_image, procedure, G_OBJECT (config)))
|
2006-04-13 11:16:13 +00:00
|
|
|
{
|
2019-08-24 15:57:54 +02:00
|
|
|
gimp_image_delete (sanitized_image);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2019-08-24 15:57:54 +02:00
|
|
|
return gimp_procedure_new_return_values (procedure,
|
|
|
|
GIMP_PDB_CANCEL,
|
|
|
|
NULL);
|
2006-04-13 11:16:13 +00:00
|
|
|
}
|
2010-02-13 13:26:46 +01:00
|
|
|
}
|
2019-08-24 15:57:54 +02:00
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2019-08-24 15:57:54 +02:00
|
|
|
if (status == GIMP_PDB_SUCCESS)
|
|
|
|
{
|
2024-07-14 20:12:57 +00:00
|
|
|
GList *drawables;
|
2024-04-30 13:50:24 +00:00
|
|
|
|
2024-05-06 18:38:12 +00:00
|
|
|
export = gimp_export_options_get_image (options, &image);
|
2024-04-30 13:50:24 +00:00
|
|
|
drawables = gimp_image_list_layers (image);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2024-06-12 16:53:12 +00:00
|
|
|
if (! export_image (file, image, drawables->data, orig_image,
|
2024-04-30 13:50:24 +00:00
|
|
|
G_OBJECT (config), &error))
|
2019-08-24 15:57:54 +02:00
|
|
|
{
|
|
|
|
status = GIMP_PDB_EXECUTION_ERROR;
|
|
|
|
}
|
2016-04-14 18:49:00 +01:00
|
|
|
|
2019-08-24 15:57:54 +02:00
|
|
|
gimp_image_delete (sanitized_image);
|
2024-04-30 13:50:24 +00:00
|
|
|
g_list_free (drawables);
|
2008-08-18 06:31:03 +00:00
|
|
|
}
|
2008-08-17 21:30:34 +00:00
|
|
|
|
2019-08-24 15:57:54 +02:00
|
|
|
if (export == GIMP_EXPORT_EXPORT)
|
2024-04-30 13:50:24 +00:00
|
|
|
gimp_image_delete (image);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2024-06-12 16:53:12 +00:00
|
|
|
|
2019-08-24 15:57:54 +02:00
|
|
|
return gimp_procedure_new_return_values (procedure, status, error);
|
2006-04-13 11:16:13 +00:00
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
/* ppmtogif.c - read a portable pixmap and produce a GIF file
|
|
|
|
**
|
1999-11-20 02:20:04 +00:00
|
|
|
** Based on GIFENCOD by David Rowley <mgardi@watdscu.waterloo.edu>. A
|
|
|
|
** Lempel-Ziv compression based on "compress".
|
1997-11-24 22:05:25 +00:00
|
|
|
**
|
|
|
|
** Modified by Marcel Wijkstra <wijkstra@fwi.uva.nl>
|
|
|
|
**
|
|
|
|
**
|
|
|
|
** Copyright (C) 1989 by Jef Poskanzer.
|
|
|
|
**
|
|
|
|
** Permission to use, copy, modify, and distribute this software and its
|
|
|
|
** documentation for any purpose and without fee is hereby granted, provided
|
|
|
|
** that the above copyright notice appear in all copies and that both that
|
|
|
|
** copyright notice and this permission notice appear in supporting
|
|
|
|
** documentation. This software is provided "as is" without express or
|
|
|
|
** implied warranty.
|
|
|
|
**
|
|
|
|
** The Graphics Interchange Format(c) is the Copyright property of
|
|
|
|
** CompuServe Incorporated. GIF(sm) is a Service Mark property of
|
|
|
|
** CompuServe Incorporated.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define MAXCOLORS 256
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Pointer to function returning an int
|
|
|
|
*/
|
2016-04-14 16:54:08 +01:00
|
|
|
typedef gint (* ifunptr) (gint x,
|
|
|
|
gint y);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
static gint find_unused_ia_color (const guchar *pixels,
|
|
|
|
gint numpixels,
|
|
|
|
gint num_indices,
|
|
|
|
gint *colors);
|
2015-02-15 22:52:37 +01:00
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
static void special_flatten_indexed_alpha (guchar *pixels,
|
|
|
|
gint transparent,
|
|
|
|
gint numpixels);
|
2015-02-15 22:52:37 +01:00
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
static gint colors_to_bpp (gint colors);
|
|
|
|
static gint bpp_to_colors (gint bpp);
|
|
|
|
static gint get_pixel (gint x,
|
|
|
|
gint y);
|
|
|
|
static gint gif_next_pixel (ifunptr getpixel);
|
2015-02-15 22:52:37 +01:00
|
|
|
static void bump_pixel (void);
|
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
static gboolean gif_encode_header (GOutputStream *output,
|
|
|
|
gboolean gif89,
|
|
|
|
gint width,
|
|
|
|
gint height,
|
|
|
|
gint background,
|
|
|
|
gint bpp,
|
|
|
|
gint *red,
|
|
|
|
gint *green,
|
|
|
|
gint *blue,
|
|
|
|
ifunptr get_pixel,
|
|
|
|
GError **error);
|
|
|
|
static gboolean gif_encode_graphic_control_ext (GOutputStream *output,
|
|
|
|
gint disposal,
|
|
|
|
gint delay89,
|
|
|
|
gint n_frames,
|
|
|
|
gint width,
|
|
|
|
gint height,
|
|
|
|
gint transparent,
|
|
|
|
gint bpp,
|
|
|
|
ifunptr get_pixel,
|
|
|
|
GError **error);
|
|
|
|
static gboolean gif_encode_image_data (GOutputStream *output,
|
|
|
|
gint width,
|
|
|
|
gint height,
|
|
|
|
gint interlace,
|
|
|
|
gint bpp,
|
|
|
|
ifunptr get_pixel,
|
|
|
|
gint offset_x,
|
|
|
|
gint offset_y,
|
|
|
|
GError **error);
|
|
|
|
static gboolean gif_encode_close (GOutputStream *output,
|
|
|
|
GError **error);
|
|
|
|
static gboolean gif_encode_loop_ext (GOutputStream *output,
|
|
|
|
guint n_loops,
|
|
|
|
GError **error);
|
|
|
|
static gboolean gif_encode_comment_ext (GOutputStream *output,
|
|
|
|
const gchar *comment,
|
|
|
|
GError **error);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2006-04-04 14:03:00 +00:00
|
|
|
static gint rowstride;
|
2006-04-13 11:16:13 +00:00
|
|
|
static guchar *pixels;
|
2006-04-04 14:03:00 +00:00
|
|
|
static gint cur_progress;
|
|
|
|
static gint max_progress;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
static gboolean compress (GOutputStream *output,
|
|
|
|
gint init_bits,
|
|
|
|
ifunptr ReadValue,
|
|
|
|
GError **error);
|
|
|
|
static gboolean no_compress (GOutputStream *output,
|
|
|
|
gint init_bits,
|
|
|
|
ifunptr ReadValue,
|
|
|
|
GError **error);
|
|
|
|
static gboolean rle_compress (GOutputStream *output,
|
|
|
|
gint init_bits,
|
|
|
|
ifunptr ReadValue,
|
|
|
|
GError **error);
|
|
|
|
static gboolean normal_compress (GOutputStream *output,
|
|
|
|
gint init_bits,
|
|
|
|
ifunptr ReadValue,
|
|
|
|
GError **error);
|
|
|
|
|
|
|
|
static gboolean put_byte (GOutputStream *output,
|
|
|
|
guchar b,
|
|
|
|
GError **error);
|
|
|
|
static gboolean put_word (GOutputStream *output,
|
|
|
|
gint w,
|
|
|
|
GError **error);
|
|
|
|
static gboolean put_string (GOutputStream *output,
|
|
|
|
const gchar *s,
|
|
|
|
GError **error);
|
|
|
|
static gboolean output_code (GOutputStream *output,
|
|
|
|
gint code,
|
|
|
|
GError **error);
|
|
|
|
static gboolean cl_block (GOutputStream *output,
|
|
|
|
GError **error);
|
|
|
|
static void cl_hash (glong hsize);
|
|
|
|
|
|
|
|
static void char_init (void);
|
|
|
|
static gboolean char_out (GOutputStream *output,
|
|
|
|
gint c,
|
|
|
|
GError **error);
|
|
|
|
static gboolean char_flush (GOutputStream *output,
|
|
|
|
GError **error);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
|
2003-07-03 17:45:12 +00:00
|
|
|
static gint
|
2013-06-06 23:26:16 +02:00
|
|
|
find_unused_ia_color (const guchar *pixels,
|
2015-02-15 22:52:37 +01:00
|
|
|
gint numpixels,
|
|
|
|
gint num_indices,
|
|
|
|
gint *colors)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
|
|
|
gboolean ix_used[256];
|
2013-05-27 22:55:34 +02:00
|
|
|
gint i;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
1998-10-09 18:01:35 +00:00
|
|
|
#ifdef GIFDEBUG
|
2003-06-16 17:51:03 +00:00
|
|
|
g_printerr ("GIF: fuiac: Image claims to use %d/%d indices - finding free "
|
2006-08-30 17:01:47 +00:00
|
|
|
"index...\n", *colors, num_indices);
|
1998-10-09 18:01:35 +00:00
|
|
|
#endif
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2006-04-13 11:16:13 +00:00
|
|
|
for (i = 0; i < 256; i++)
|
2006-08-30 17:01:47 +00:00
|
|
|
ix_used[i] = FALSE;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2006-04-13 11:16:13 +00:00
|
|
|
for (i = 0; i < numpixels; i++)
|
1998-09-15 20:34:30 +00:00
|
|
|
{
|
2006-04-13 11:16:13 +00:00
|
|
|
if (pixels[i * 2 + 1])
|
2006-08-30 17:01:47 +00:00
|
|
|
ix_used[pixels[i * 2]] = TRUE;
|
1998-09-15 20:34:30 +00:00
|
|
|
}
|
2003-11-06 15:27:05 +00:00
|
|
|
|
2006-04-13 11:16:13 +00:00
|
|
|
for (i = num_indices - 1; i >= 0; i--)
|
1998-09-15 20:34:30 +00:00
|
|
|
{
|
2006-08-30 17:01:47 +00:00
|
|
|
if (! ix_used[i])
|
2006-04-13 11:16:13 +00:00
|
|
|
{
|
1998-10-09 18:01:35 +00:00
|
|
|
#ifdef GIFDEBUG
|
2013-06-06 23:26:16 +02:00
|
|
|
g_printerr ("GIF: Found unused color index %d.\n", (int) i);
|
1998-10-09 18:01:35 +00:00
|
|
|
#endif
|
2006-04-13 11:16:13 +00:00
|
|
|
return i;
|
|
|
|
}
|
1998-09-15 20:34:30 +00:00
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2016-04-14 16:54:08 +01:00
|
|
|
/* Couldn't find an unused color index within the number of bits per
|
|
|
|
* pixel we wanted. Will have to increment the number of colors in
|
|
|
|
* the image and assign a transparent pixel there.
|
|
|
|
*/
|
2006-08-30 17:01:47 +00:00
|
|
|
if (*colors < 256)
|
1998-09-15 20:34:30 +00:00
|
|
|
{
|
|
|
|
(*colors)++;
|
2006-08-30 17:01:47 +00:00
|
|
|
|
2003-06-16 17:51:03 +00:00
|
|
|
g_printerr ("GIF: 2nd pass "
|
2013-06-06 23:26:16 +02:00
|
|
|
"- Increasing bounds and using color index %d.\n",
|
2006-08-30 17:01:47 +00:00
|
|
|
*colors - 1);
|
2006-04-13 11:16:13 +00:00
|
|
|
return ((*colors) - 1);
|
1998-09-15 20:34:30 +00:00
|
|
|
}
|
2003-11-06 15:27:05 +00:00
|
|
|
|
2016-02-16 02:35:43 +01:00
|
|
|
g_message (_("Couldn't simply reduce colors further. Exporting as opaque."));
|
2006-08-30 17:01:47 +00:00
|
|
|
|
|
|
|
return -1;
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2003-07-03 17:45:12 +00:00
|
|
|
static void
|
1997-11-24 22:05:25 +00:00
|
|
|
special_flatten_indexed_alpha (guchar *pixels,
|
2006-08-30 17:01:47 +00:00
|
|
|
gint transparent,
|
|
|
|
gint numpixels)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
2021-05-24 20:36:31 +00:00
|
|
|
gint i;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2016-04-14 16:54:08 +01:00
|
|
|
/* Each transparent pixel in the image is mapped to a uniform value
|
|
|
|
* for encoding, if image already has <=255 colors
|
|
|
|
*/
|
2003-11-06 15:27:05 +00:00
|
|
|
|
2006-08-30 17:01:47 +00:00
|
|
|
if (transparent == -1) /* tough, no indices left for the trans. index */
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
2006-04-13 11:16:13 +00:00
|
|
|
for (i = 0; i < numpixels; i++)
|
|
|
|
pixels[i] = pixels[i * 2];
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|
|
|
|
else /* make transparent */
|
|
|
|
{
|
2006-04-13 11:16:13 +00:00
|
|
|
for (i = 0; i < numpixels; i++)
|
|
|
|
{
|
|
|
|
if (! (pixels[i * 2 + 1] & 128))
|
|
|
|
{
|
2006-08-30 17:01:47 +00:00
|
|
|
pixels[i] = (guchar) transparent;
|
2006-04-13 11:16:13 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pixels[i] = pixels[i * 2];
|
|
|
|
}
|
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-08-30 17:01:47 +00:00
|
|
|
static gint
|
|
|
|
parse_ms_tag (const gchar *str)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
2013-05-27 22:55:34 +02:00
|
|
|
gint sum = 0;
|
1997-11-24 22:05:25 +00:00
|
|
|
gint offset = 0;
|
|
|
|
gint length;
|
|
|
|
|
2013-05-27 22:55:34 +02:00
|
|
|
length = strlen (str);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2016-04-14 16:54:08 +01:00
|
|
|
find_another_bra:
|
1998-04-29 10:48:02 +00:00
|
|
|
|
2006-04-13 11:16:13 +00:00
|
|
|
while ((offset < length) && (str[offset] != '('))
|
1997-11-24 22:05:25 +00:00
|
|
|
offset++;
|
2003-11-06 15:27:05 +00:00
|
|
|
|
2006-04-13 11:16:13 +00:00
|
|
|
if (offset >= length)
|
1997-11-24 22:05:25 +00:00
|
|
|
return(-1);
|
|
|
|
|
2006-04-13 11:16:13 +00:00
|
|
|
if (! g_ascii_isdigit (str[++offset]))
|
1998-04-29 10:48:02 +00:00
|
|
|
goto find_another_bra;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
sum *= 10;
|
|
|
|
sum += str[offset] - '0';
|
|
|
|
offset++;
|
|
|
|
}
|
2006-04-13 11:16:13 +00:00
|
|
|
while ((offset < length) && (g_ascii_isdigit (str[offset])));
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2006-04-13 11:16:13 +00:00
|
|
|
if (length - offset <= 2)
|
1997-11-24 22:05:25 +00:00
|
|
|
return(-3);
|
|
|
|
|
2016-04-14 16:54:08 +01:00
|
|
|
if ((g_ascii_toupper (str[offset]) != 'M') ||
|
|
|
|
(g_ascii_toupper (str[offset + 1]) != 'S'))
|
2003-11-26 19:20:24 +00:00
|
|
|
return -4;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2003-11-26 19:20:24 +00:00
|
|
|
return sum;
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-08-30 17:01:47 +00:00
|
|
|
static gint
|
2019-10-28 12:12:09 +01:00
|
|
|
parse_disposal_tag (const gchar *str,
|
|
|
|
gint default_dispose)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
|
|
|
gint offset = 0;
|
|
|
|
gint length;
|
|
|
|
|
2016-04-14 16:54:08 +01:00
|
|
|
length = strlen (str);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2006-04-13 11:16:13 +00:00
|
|
|
while ((offset + 9) <= length)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
2016-04-14 16:54:08 +01:00
|
|
|
if (strncmp (&str[offset], "(combine)", 9) == 0)
|
|
|
|
return 0x01;
|
|
|
|
|
|
|
|
if (strncmp (&str[offset], "(replace)", 9) == 0)
|
|
|
|
return 0x02 ;
|
|
|
|
|
1997-11-24 22:05:25 +00:00
|
|
|
offset++;
|
|
|
|
}
|
|
|
|
|
2019-10-28 12:12:09 +01:00
|
|
|
return default_dispose;
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-08-17 21:30:34 +00:00
|
|
|
static GimpPDBStatusType
|
2016-04-14 18:49:00 +01:00
|
|
|
sanity_check (GFile *file,
|
2019-08-24 15:57:54 +02:00
|
|
|
GimpImage **image,
|
2016-04-14 16:54:08 +01:00
|
|
|
GimpRunMode run_mode,
|
2008-08-17 21:30:34 +00:00
|
|
|
GError **error)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
2019-08-24 15:57:54 +02:00
|
|
|
GList *layers;
|
|
|
|
GList *list;
|
|
|
|
gint image_width;
|
|
|
|
gint image_height;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2021-04-06 00:47:07 +02:00
|
|
|
image_width = gimp_image_get_width (*image);
|
|
|
|
image_height = gimp_image_get_height (*image);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2008-03-20 09:29:04 +00:00
|
|
|
if (image_width > G_MAXUSHORT || image_height > G_MAXUSHORT)
|
|
|
|
{
|
2008-08-17 21:30:34 +00:00
|
|
|
g_set_error (error, 0, 0,
|
2016-02-16 02:35:43 +01:00
|
|
|
_("Unable to export '%s'. "
|
2024-01-21 14:55:38 +00:00
|
|
|
"The GIF file format does not support images that are "
|
|
|
|
"more than %d pixels wide or tall."),
|
2016-04-14 18:49:00 +01:00
|
|
|
gimp_file_get_utf8_name (file), G_MAXUSHORT);
|
2008-08-17 21:30:34 +00:00
|
|
|
|
|
|
|
return GIMP_PDB_EXECUTION_ERROR;
|
2008-03-20 09:29:04 +00:00
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2024-01-21 14:55:38 +00:00
|
|
|
*image = gimp_image_duplicate (*image);
|
|
|
|
|
|
|
|
/* Convert image to 8 bit precision if necessary */
|
|
|
|
if (gimp_image_get_precision (*image) != GIMP_PRECISION_U8_NON_LINEAR &&
|
|
|
|
gimp_image_get_precision (*image) != GIMP_PRECISION_U8_LINEAR &&
|
|
|
|
gimp_image_get_precision (*image) != GIMP_PRECISION_U8_PERCEPTUAL)
|
|
|
|
{
|
|
|
|
gboolean converted = FALSE;
|
|
|
|
|
|
|
|
converted = gimp_image_convert_precision (*image,
|
|
|
|
GIMP_PRECISION_U8_NON_LINEAR);
|
|
|
|
|
|
|
|
if (! converted)
|
|
|
|
{
|
|
|
|
g_set_error (error, 0, 0,
|
|
|
|
_("Unable to export '%s'. "
|
|
|
|
"Please convert your image to 8 bit integer "
|
|
|
|
"precision, as the GIF file format does not "
|
|
|
|
"support higher precisions."),
|
|
|
|
gimp_file_get_utf8_name (file));
|
|
|
|
|
|
|
|
return GIMP_PDB_EXECUTION_ERROR;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
1997-11-24 22:05:25 +00:00
|
|
|
/*** Iterate through the layers to make sure they're all ***/
|
|
|
|
/*** within the bounds of the image ***/
|
2019-08-27 13:26:27 +02:00
|
|
|
layers = gimp_image_list_layers (*image);
|
2008-03-20 09:29:04 +00:00
|
|
|
|
2019-08-24 15:57:54 +02:00
|
|
|
for (list = layers; list; list = g_list_next (list))
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
2019-08-24 15:57:54 +02:00
|
|
|
GimpDrawable *drawable = list->data;
|
|
|
|
gint offset_x;
|
|
|
|
gint offset_y;
|
2008-03-20 09:29:04 +00:00
|
|
|
|
2021-04-06 14:28:40 +02:00
|
|
|
gimp_drawable_get_offsets (drawable, &offset_x, &offset_y);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2008-03-20 09:29:04 +00:00
|
|
|
if (offset_x < 0 ||
|
|
|
|
offset_y < 0 ||
|
2021-04-06 14:28:40 +02:00
|
|
|
offset_x + gimp_drawable_get_width (drawable) > image_width ||
|
|
|
|
offset_y + gimp_drawable_get_height (drawable) > image_height)
|
2006-04-13 11:16:13 +00:00
|
|
|
{
|
2019-08-24 15:57:54 +02:00
|
|
|
g_list_free (layers);
|
2006-04-13 11:16:13 +00:00
|
|
|
|
|
|
|
/* Image has illegal bounds - ask the user what it wants to do */
|
|
|
|
|
|
|
|
/* Do the crop if we can't talk to the user, or if we asked
|
2008-08-17 21:30:34 +00:00
|
|
|
* the user and they said yes.
|
|
|
|
*/
|
2006-04-13 11:16:13 +00:00
|
|
|
if ((run_mode == GIMP_RUN_NONINTERACTIVE) || bad_bounds_dialog ())
|
|
|
|
{
|
2019-08-24 15:57:54 +02:00
|
|
|
gimp_image_crop (*image, image_width, image_height, 0, 0);
|
2008-08-17 21:30:34 +00:00
|
|
|
return GIMP_PDB_SUCCESS;
|
2006-04-13 11:16:13 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-08-24 15:57:54 +02:00
|
|
|
gimp_image_delete (*image);
|
2008-08-17 21:30:34 +00:00
|
|
|
return GIMP_PDB_CANCEL;
|
2006-04-13 11:16:13 +00:00
|
|
|
}
|
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|
|
|
|
|
2019-08-24 15:57:54 +02:00
|
|
|
g_list_free (layers);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2008-08-17 21:30:34 +00:00
|
|
|
return GIMP_PDB_SUCCESS;
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|
|
|
|
|
2008-08-17 21:30:34 +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,
|
|
|
|
GError **error)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
2012-09-22 23:19:32 +02:00
|
|
|
GeglBuffer *buffer;
|
|
|
|
GimpImageType drawable_type;
|
|
|
|
const Babl *format = NULL;
|
2016-04-14 18:49:00 +01:00
|
|
|
GOutputStream *output;
|
2012-09-22 23:19:32 +02:00
|
|
|
gint Red[MAXCOLORS];
|
|
|
|
gint Green[MAXCOLORS];
|
|
|
|
gint Blue[MAXCOLORS];
|
|
|
|
guchar *cmap;
|
|
|
|
guint rows, cols;
|
2016-04-14 16:54:08 +01:00
|
|
|
gint BitsPerPixel;
|
|
|
|
gint liberalBPP = 0;
|
|
|
|
gint useBPP = 0;
|
2012-09-22 23:19:32 +02:00
|
|
|
gint colors;
|
|
|
|
gint i;
|
|
|
|
gint transparent;
|
|
|
|
gint offset_x, offset_y;
|
|
|
|
|
2019-08-24 15:57:54 +02:00
|
|
|
GList *layers;
|
|
|
|
GList *list;
|
2012-09-22 23:19:32 +02:00
|
|
|
gint nlayers;
|
|
|
|
|
|
|
|
gboolean is_gif89 = FALSE;
|
|
|
|
|
|
|
|
gint Delay89;
|
|
|
|
gint Disposal;
|
|
|
|
gchar *layer_name;
|
|
|
|
|
2023-11-14 20:04:14 +01:00
|
|
|
guchar bgred = 255;
|
|
|
|
guchar bggreen = 255;
|
|
|
|
guchar bgblue = 255;
|
2012-09-22 23:19:32 +02:00
|
|
|
guchar bgindex = 0;
|
|
|
|
guint best_error = 0xFFFFFFFF;
|
1998-03-16 10:05:41 +00:00
|
|
|
|
2019-10-28 12:12:09 +01:00
|
|
|
gboolean config_interlace;
|
|
|
|
gboolean config_loop;
|
2022-03-21 14:54:57 +00:00
|
|
|
gint config_number_of_repeats;
|
2019-10-28 12:12:09 +01:00
|
|
|
gint config_default_delay;
|
|
|
|
gint config_default_dispose;
|
|
|
|
gboolean config_use_default_delay;
|
|
|
|
gboolean config_use_default_dispose;
|
|
|
|
gboolean config_as_animation;
|
|
|
|
gboolean config_save_comment;
|
|
|
|
gchar *config_comment;
|
|
|
|
|
|
|
|
g_object_get (config,
|
2022-03-21 14:54:57 +00:00
|
|
|
"interlace", &config_interlace,
|
|
|
|
"loop", &config_loop,
|
|
|
|
"number-of-repeats", &config_number_of_repeats,
|
|
|
|
"default-delay", &config_default_delay,
|
|
|
|
"force-delay", &config_use_default_delay,
|
|
|
|
"force-dispose", &config_use_default_dispose,
|
|
|
|
"as-animation", &config_as_animation,
|
|
|
|
"save-comment", &config_save_comment,
|
|
|
|
"gimp-comment", &config_comment,
|
2019-10-28 12:12:09 +01:00
|
|
|
NULL);
|
2024-08-19 14:40:46 +00:00
|
|
|
config_default_dispose =
|
|
|
|
gimp_procedure_config_get_choice_id (GIMP_PROCEDURE_CONFIG (config),
|
|
|
|
"default-dispose");
|
1999-04-25 16:10:43 +00:00
|
|
|
|
2003-07-03 17:45:12 +00:00
|
|
|
/* The GIF spec says 7bit ASCII for the comment block. */
|
2019-10-28 12:12:09 +01:00
|
|
|
if (config_save_comment && config_comment && strlen (config_comment))
|
2003-07-03 17:45:12 +00:00
|
|
|
{
|
2019-10-28 12:12:09 +01:00
|
|
|
const gchar *c = config_comment;
|
2003-07-03 17:45:12 +00:00
|
|
|
gint len;
|
|
|
|
|
|
|
|
for (len = strlen (c); len; c++, len--)
|
|
|
|
{
|
2003-12-05 23:40:09 +00:00
|
|
|
if ((guchar) *c > 127)
|
2003-07-03 17:45:12 +00:00
|
|
|
{
|
2003-11-15 13:53:33 +00:00
|
|
|
g_message (_("The GIF format only supports comments in "
|
2003-07-03 17:45:12 +00:00
|
|
|
"7bit ASCII encoding. No comment is saved."));
|
|
|
|
|
2019-10-28 12:12:09 +01:00
|
|
|
g_free (config_comment);
|
|
|
|
config_comment = NULL;
|
|
|
|
|
|
|
|
config_save_comment = FALSE;
|
2003-07-03 17:45:12 +00:00
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-10-28 12:12:09 +01:00
|
|
|
else
|
|
|
|
{
|
|
|
|
config_save_comment = FALSE;
|
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2019-08-24 15:57:54 +02:00
|
|
|
/* get a list of layers for this image */
|
2019-08-27 13:26:27 +02:00
|
|
|
layers = gimp_image_list_layers (image);
|
2019-08-24 15:57:54 +02:00
|
|
|
nlayers = g_list_length (layers);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2019-08-24 15:57:54 +02:00
|
|
|
drawable_type = gimp_drawable_type (layers->data);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2016-04-14 16:54:08 +01:00
|
|
|
/* If the image has multiple layers (i.e. will be animated), a
|
|
|
|
* comment, or transparency, then it must be encoded as a GIF89a
|
|
|
|
* file, not a vanilla GIF87a.
|
|
|
|
*/
|
1999-10-03 18:54:54 +00:00
|
|
|
if (nlayers > 1)
|
2021-05-19 17:36:58 -04:00
|
|
|
{
|
|
|
|
is_gif89 = TRUE;
|
|
|
|
|
|
|
|
/* Layers can be with or without alpha channel. Make sure we set
|
|
|
|
* alpha if there is at least one layer with alpha channel. */
|
|
|
|
if (drawable_type == GIMP_GRAY_IMAGE ||
|
|
|
|
drawable_type == GIMP_INDEXED_IMAGE)
|
|
|
|
{
|
2021-05-31 11:59:58 -04:00
|
|
|
for (list = layers; list; list = g_list_next (list))
|
2021-05-19 17:36:58 -04:00
|
|
|
{
|
2021-05-31 11:59:58 -04:00
|
|
|
GimpImageType dr_type = gimp_drawable_type (list->data);
|
2021-05-19 17:36:58 -04:00
|
|
|
|
|
|
|
if (dr_type == GIMP_GRAYA_IMAGE ||
|
|
|
|
dr_type == GIMP_INDEXEDA_IMAGE)
|
|
|
|
{
|
|
|
|
drawable_type = dr_type;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2000-06-07 22:51:23 +00:00
|
|
|
|
2019-10-28 12:12:09 +01:00
|
|
|
if (config_save_comment)
|
1997-11-24 22:05:25 +00:00
|
|
|
is_gif89 = TRUE;
|
|
|
|
|
|
|
|
switch (drawable_type)
|
|
|
|
{
|
2000-08-22 01:26:57 +00:00
|
|
|
case GIMP_INDEXEDA_IMAGE:
|
1997-11-24 22:05:25 +00:00
|
|
|
is_gif89 = TRUE;
|
2000-08-22 01:26:57 +00:00
|
|
|
case GIMP_INDEXED_IMAGE:
|
2023-11-14 20:04:14 +01:00
|
|
|
{
|
|
|
|
GeglColor *background;
|
|
|
|
guchar bg[3];
|
2003-11-06 15:27:05 +00:00
|
|
|
|
2024-09-23 15:17:04 +02:00
|
|
|
cmap = gimp_palette_get_colormap (gimp_image_get_palette (image), babl_format ("R'G'B' u8"), &colors, NULL);
|
1998-03-16 10:05:41 +00:00
|
|
|
|
2023-11-14 20:04:14 +01:00
|
|
|
background = gimp_context_get_background ();
|
|
|
|
gegl_color_get_pixel (background, babl_format_with_space ("R'G'B' u8", NULL), bg);
|
|
|
|
g_object_unref (background);
|
|
|
|
bgred = bg[0];
|
|
|
|
bggreen = bg[1];
|
|
|
|
bgblue = bg[2];
|
|
|
|
|
|
|
|
for (i = 0; i < colors; i++)
|
|
|
|
{
|
|
|
|
Red[i] = *cmap++;
|
|
|
|
Green[i] = *cmap++;
|
|
|
|
Blue[i] = *cmap++;
|
|
|
|
}
|
|
|
|
for ( ; i < 256; i++)
|
|
|
|
{
|
|
|
|
Red[i] = bgred;
|
|
|
|
Green[i] = bggreen;
|
|
|
|
Blue[i] = bgblue;
|
|
|
|
}
|
2006-04-13 11:16:13 +00:00
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
break;
|
2000-08-22 01:26:57 +00:00
|
|
|
case GIMP_GRAYA_IMAGE:
|
1997-11-24 22:05:25 +00:00
|
|
|
is_gif89 = TRUE;
|
2000-08-22 01:26:57 +00:00
|
|
|
case GIMP_GRAY_IMAGE:
|
1998-03-16 10:05:41 +00:00
|
|
|
colors = 256; /* FIXME: Not ideal. */
|
1997-11-24 22:05:25 +00:00
|
|
|
for ( i = 0; i < 256; i++)
|
2006-04-13 11:16:13 +00:00
|
|
|
{
|
|
|
|
Red[i] = Green[i] = Blue[i] = i;
|
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2016-02-16 02:35:43 +01:00
|
|
|
g_message (_("Cannot export RGB color images. Convert to "
|
2003-11-15 13:53:33 +00:00
|
|
|
"indexed color or grayscale first."));
|
1997-11-24 22:05:25 +00:00
|
|
|
return FALSE;
|
|
|
|
}
|
2003-11-06 15:27:05 +00:00
|
|
|
|
2003-06-16 17:37:11 +00:00
|
|
|
|
|
|
|
/* find earliest index in palette which is closest to the background
|
2016-04-14 16:54:08 +01:00
|
|
|
* color, and ATTEMPT to use that as the GIF's default background
|
|
|
|
* color.
|
|
|
|
*/
|
2006-04-04 14:03:00 +00:00
|
|
|
for (i = 255; i >= 0; --i)
|
|
|
|
{
|
|
|
|
guint local_error = 0;
|
|
|
|
|
2016-04-14 16:54:08 +01:00
|
|
|
local_error += (Red[i] - bgred) * (Red[i] - bgred);
|
2006-04-04 14:03:00 +00:00
|
|
|
local_error += (Green[i] - bggreen) * (Green[i] - bggreen);
|
2016-04-14 16:54:08 +01:00
|
|
|
local_error += (Blue[i] - bgblue) * (Blue[i] - bgblue);
|
2006-04-04 14:03:00 +00:00
|
|
|
|
|
|
|
if (local_error <= best_error)
|
|
|
|
{
|
|
|
|
bgindex = i;
|
|
|
|
best_error = local_error;
|
|
|
|
}
|
2003-06-16 17:37:11 +00:00
|
|
|
}
|
|
|
|
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2014-07-23 16:39:00 +02:00
|
|
|
/* init the progress meter */
|
2016-02-16 02:35:43 +01:00
|
|
|
gimp_progress_init_printf (_("Exporting '%s'"),
|
2016-04-14 18:49:00 +01:00
|
|
|
gimp_file_get_utf8_name (file));
|
2014-07-23 16:39:00 +02:00
|
|
|
|
|
|
|
|
1997-11-24 22:05:25 +00:00
|
|
|
/* open the destination file for writing */
|
2016-04-14 18:49:00 +01:00
|
|
|
output = G_OUTPUT_STREAM (g_file_replace (file,
|
|
|
|
NULL, FALSE, G_FILE_CREATE_NONE,
|
|
|
|
NULL, error));
|
|
|
|
if (output)
|
|
|
|
{
|
|
|
|
GDataOutputStream *data_output;
|
|
|
|
|
|
|
|
data_output = g_data_output_stream_new (output);
|
|
|
|
g_object_unref (output);
|
|
|
|
|
|
|
|
g_data_output_stream_set_byte_order (data_output,
|
|
|
|
G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN);
|
|
|
|
|
|
|
|
output = G_OUTPUT_STREAM (data_output);
|
|
|
|
}
|
|
|
|
else
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* write the GIFheader */
|
|
|
|
|
|
|
|
if (colors < 256)
|
1998-03-16 10:05:41 +00:00
|
|
|
{
|
2016-04-14 16:54:08 +01:00
|
|
|
/* we keep track of how many bits we promised to have in
|
|
|
|
* liberalBPP, so that we don't accidentally come under this
|
|
|
|
* when doing clever transparency stuff where we can re-use
|
|
|
|
* wasted indices.
|
|
|
|
*/
|
1998-09-15 20:34:30 +00:00
|
|
|
liberalBPP = BitsPerPixel =
|
2006-04-13 11:16:13 +00:00
|
|
|
colors_to_bpp (colors + ((drawable_type==GIMP_INDEXEDA_IMAGE) ? 1 : 0));
|
1998-03-16 10:05:41 +00:00
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
else
|
1998-03-16 10:05:41 +00:00
|
|
|
{
|
1998-09-28 17:14:29 +00:00
|
|
|
liberalBPP = BitsPerPixel =
|
2006-04-13 11:16:13 +00:00
|
|
|
colors_to_bpp (256);
|
1998-09-28 17:14:29 +00:00
|
|
|
|
2006-04-13 11:16:13 +00:00
|
|
|
if (drawable_type == GIMP_INDEXEDA_IMAGE)
|
|
|
|
{
|
2013-06-06 23:26:16 +02:00
|
|
|
g_printerr ("GIF: Too many colors?\n");
|
2006-04-13 11:16:13 +00:00
|
|
|
}
|
1998-03-16 10:05:41 +00:00
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2021-04-06 00:47:07 +02:00
|
|
|
cols = gimp_image_get_width (image);
|
|
|
|
rows = gimp_image_get_height (image);
|
2019-10-28 12:12:09 +01:00
|
|
|
Interlace = config_interlace;
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! gif_encode_header (output, is_gif89, cols, rows, bgindex,
|
|
|
|
BitsPerPixel, Red, Green, Blue, get_pixel,
|
|
|
|
error))
|
|
|
|
return FALSE;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
|
2016-04-14 16:54:08 +01:00
|
|
|
/* If the image has multiple layers it'll be made into an animated
|
2022-03-21 14:54:57 +00:00
|
|
|
* GIF, so write out the looping extension
|
2016-04-14 16:54:08 +01:00
|
|
|
*/
|
2022-03-21 14:54:57 +00:00
|
|
|
if (nlayers > 1)
|
|
|
|
{
|
|
|
|
/* If 'Forever' toggle was checked, override number_of_repeats */
|
|
|
|
if (config_loop)
|
|
|
|
config_number_of_repeats = 0;
|
|
|
|
|
|
|
|
if (config_loop || config_number_of_repeats > 0)
|
|
|
|
if (! gif_encode_loop_ext (output, config_number_of_repeats, error))
|
|
|
|
return FALSE;
|
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
/* Write comment extension - mustn't be written before the looping ext. */
|
2019-10-28 12:12:09 +01:00
|
|
|
if (config_save_comment && config_comment && strlen (config_comment))
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
2019-10-28 12:12:09 +01:00
|
|
|
if (! gif_encode_comment_ext (output, config_comment, error))
|
2016-04-14 18:49:00 +01:00
|
|
|
return FALSE;
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*** Now for each layer in the image, save an image in a compound GIF ***/
|
|
|
|
/************************************************************************/
|
|
|
|
|
2006-04-04 14:03:00 +00:00
|
|
|
cur_progress = 0;
|
|
|
|
max_progress = nlayers * rows;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2019-08-27 13:26:27 +02:00
|
|
|
layers = g_list_reverse (layers);
|
|
|
|
|
|
|
|
for (list = layers, i = nlayers - 1;
|
2019-08-24 15:57:54 +02:00
|
|
|
list && i >= 0;
|
2019-08-27 13:26:27 +02:00
|
|
|
list = g_list_next (list), i--, cur_progress = (nlayers - i) * rows)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
2019-08-24 15:57:54 +02:00
|
|
|
GimpDrawable *drawable = list->data;
|
|
|
|
|
|
|
|
drawable_type = gimp_drawable_type (drawable);
|
2021-05-19 17:36:58 -04:00
|
|
|
if (drawable_type == GIMP_GRAYA_IMAGE)
|
|
|
|
{
|
|
|
|
format = babl_format ("Y'A u8");
|
|
|
|
}
|
|
|
|
else if (drawable_type == GIMP_GRAY_IMAGE)
|
|
|
|
{
|
|
|
|
format = babl_format ("Y' u8");
|
|
|
|
}
|
|
|
|
|
2019-08-24 15:57:54 +02:00
|
|
|
buffer = gimp_drawable_get_buffer (drawable);
|
2021-04-06 14:28:40 +02:00
|
|
|
gimp_drawable_get_offsets (drawable, &offset_x, &offset_y);
|
|
|
|
cols = gimp_drawable_get_width (drawable);
|
|
|
|
rows = gimp_drawable_get_height (drawable);
|
2012-09-22 23:19:32 +02:00
|
|
|
rowstride = cols;
|
2003-11-06 15:27:05 +00:00
|
|
|
|
2012-09-22 23:19:32 +02:00
|
|
|
pixels = g_new (guchar, (cols * rows *
|
|
|
|
(((drawable_type == GIMP_INDEXEDA_IMAGE) ||
|
|
|
|
(drawable_type == GIMP_GRAYA_IMAGE)) ? 2 : 1)));
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2012-09-22 23:19:32 +02:00
|
|
|
gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, cols, rows), 1.0,
|
|
|
|
format, pixels,
|
|
|
|
GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
/* sort out whether we need to do transparency jiggery-pokery */
|
2012-09-22 23:19:32 +02:00
|
|
|
if ((drawable_type == GIMP_INDEXEDA_IMAGE) ||
|
|
|
|
(drawable_type == GIMP_GRAYA_IMAGE))
|
2006-04-13 11:16:13 +00:00
|
|
|
{
|
|
|
|
/* Try to find an entry which isn't actually used in the
|
2016-04-14 16:54:08 +01:00
|
|
|
* image, for a transparency index.
|
|
|
|
*/
|
2006-04-13 11:16:13 +00:00
|
|
|
|
|
|
|
transparent =
|
2013-06-06 23:26:16 +02:00
|
|
|
find_unused_ia_color (pixels,
|
2012-09-22 23:19:32 +02:00
|
|
|
cols * rows,
|
2006-08-30 17:01:47 +00:00
|
|
|
bpp_to_colors (colors_to_bpp (colors)),
|
|
|
|
&colors);
|
2006-04-13 11:16:13 +00:00
|
|
|
|
|
|
|
special_flatten_indexed_alpha (pixels,
|
2006-08-30 17:01:47 +00:00
|
|
|
transparent,
|
2012-09-22 23:19:32 +02:00
|
|
|
cols * rows);
|
2006-04-13 11:16:13 +00:00
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
else
|
2006-02-19 17:17:03 +00:00
|
|
|
{
|
|
|
|
transparent = -1;
|
|
|
|
}
|
2003-11-06 15:27:05 +00:00
|
|
|
|
2006-04-13 11:16:13 +00:00
|
|
|
BitsPerPixel = colors_to_bpp (colors);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
1998-09-15 20:34:30 +00:00
|
|
|
if (BitsPerPixel != liberalBPP)
|
2006-04-13 11:16:13 +00:00
|
|
|
{
|
2016-04-14 16:54:08 +01:00
|
|
|
/* We were able to re-use an index within the existing
|
|
|
|
* bitspace, whereas the estimate in the header was
|
|
|
|
* pessimistic but still needs to be upheld...
|
|
|
|
*/
|
2006-02-19 17:17:03 +00:00
|
|
|
#ifdef GIFDEBUG
|
2006-04-13 11:16:13 +00:00
|
|
|
static gboolean onceonly = FALSE;
|
2003-11-06 15:27:05 +00:00
|
|
|
|
2006-04-13 11:16:13 +00:00
|
|
|
if (! onceonly)
|
|
|
|
{
|
|
|
|
g_warning ("Promised %d bpp, pondered writing chunk with %d bpp!",
|
2003-06-16 17:51:03 +00:00
|
|
|
liberalBPP, BitsPerPixel);
|
|
|
|
onceonly = TRUE;
|
2006-04-13 11:16:13 +00:00
|
|
|
}
|
2006-02-19 17:17:03 +00:00
|
|
|
#endif
|
2006-04-13 11:16:13 +00:00
|
|
|
}
|
1998-09-15 20:34:30 +00:00
|
|
|
|
2006-02-19 17:17:03 +00:00
|
|
|
useBPP = (BitsPerPixel > liberalBPP) ? BitsPerPixel : liberalBPP;
|
2003-11-06 15:27:05 +00:00
|
|
|
|
1997-11-24 22:05:25 +00:00
|
|
|
if (is_gif89)
|
2006-04-13 11:16:13 +00:00
|
|
|
{
|
2019-10-28 12:12:09 +01:00
|
|
|
if (i > 0 && ! config_use_default_dispose)
|
2006-04-13 11:16:13 +00:00
|
|
|
{
|
2019-10-28 12:12:09 +01:00
|
|
|
layer_name = gimp_item_get_name (list->next->data);
|
|
|
|
Disposal = parse_disposal_tag (layer_name,
|
|
|
|
config_default_dispose);
|
2006-04-13 11:16:13 +00:00
|
|
|
g_free (layer_name);
|
|
|
|
}
|
|
|
|
else
|
2006-04-04 14:03:00 +00:00
|
|
|
{
|
2019-10-28 12:12:09 +01:00
|
|
|
Disposal = config_default_dispose;
|
2006-04-04 14:03:00 +00:00
|
|
|
}
|
1997-12-12 10:01:21 +00:00
|
|
|
|
2019-08-24 15:57:54 +02:00
|
|
|
layer_name = gimp_item_get_name (GIMP_ITEM (drawable));
|
2006-04-13 11:16:13 +00:00
|
|
|
Delay89 = parse_ms_tag (layer_name);
|
|
|
|
g_free (layer_name);
|
|
|
|
|
2019-10-28 12:12:09 +01:00
|
|
|
if (Delay89 < 0 || config_use_default_delay)
|
|
|
|
Delay89 = (config_default_delay + 5) / 10;
|
2006-04-13 11:16:13 +00:00
|
|
|
else
|
|
|
|
Delay89 = (Delay89 + 5) / 10;
|
|
|
|
|
|
|
|
/* don't allow a CPU-sucking completely 0-delay looping anim */
|
2019-10-28 12:12:09 +01:00
|
|
|
if ((nlayers > 1) && config_loop && (Delay89 == 0))
|
2006-04-13 11:16:13 +00:00
|
|
|
{
|
|
|
|
static gboolean onceonly = FALSE;
|
|
|
|
|
2016-04-14 16:54:08 +01:00
|
|
|
if (! onceonly)
|
2006-04-13 11:16:13 +00:00
|
|
|
{
|
|
|
|
g_message (_("Delay inserted to prevent evil "
|
2006-02-19 17:17:03 +00:00
|
|
|
"CPU-sucking animation."));
|
2006-04-13 11:16:13 +00:00
|
|
|
onceonly = TRUE;
|
|
|
|
}
|
2016-04-14 16:54:08 +01:00
|
|
|
|
2006-04-13 11:16:13 +00:00
|
|
|
Delay89 = 1;
|
|
|
|
}
|
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! gif_encode_graphic_control_ext (output,
|
|
|
|
Disposal, Delay89, nlayers,
|
|
|
|
cols, rows,
|
|
|
|
transparent,
|
|
|
|
useBPP,
|
|
|
|
get_pixel,
|
|
|
|
error))
|
|
|
|
return FALSE;
|
2006-04-13 11:16:13 +00:00
|
|
|
}
|
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! gif_encode_image_data (output, cols, rows,
|
2019-10-28 12:12:09 +01:00
|
|
|
(rows > 4) ? config_interlace : 0,
|
2016-04-14 18:49:00 +01:00
|
|
|
useBPP,
|
|
|
|
get_pixel,
|
|
|
|
offset_x, offset_y,
|
|
|
|
error))
|
|
|
|
return FALSE;
|
|
|
|
|
2011-04-10 19:05:08 +02:00
|
|
|
gimp_progress_update (1.0);
|
2003-11-06 15:27:05 +00:00
|
|
|
|
2012-09-22 23:19:32 +02:00
|
|
|
g_object_unref (buffer);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2011-04-10 19:05:08 +02:00
|
|
|
g_free (pixels);
|
|
|
|
}
|
2003-11-06 15:27:05 +00:00
|
|
|
|
2019-08-24 15:57:54 +02:00
|
|
|
g_list_free (layers);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! gif_encode_close (output, error))
|
|
|
|
return FALSE;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2024-05-06 18:38:12 +00:00
|
|
|
static void
|
|
|
|
export_edit_options (GimpProcedure *procedure,
|
|
|
|
GimpProcedureConfig *config,
|
|
|
|
GimpExportOptions *options,
|
|
|
|
gpointer create_data)
|
|
|
|
{
|
|
|
|
GimpExportCapabilities capabilities;
|
|
|
|
gboolean as_animation;
|
|
|
|
|
|
|
|
g_object_get (G_OBJECT (config),
|
|
|
|
"as-animation", &as_animation,
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
capabilities = (GIMP_EXPORT_CAN_HANDLE_INDEXED |
|
|
|
|
GIMP_EXPORT_CAN_HANDLE_GRAY |
|
|
|
|
GIMP_EXPORT_CAN_HANDLE_ALPHA);
|
|
|
|
|
|
|
|
if (as_animation)
|
|
|
|
capabilities |= GIMP_EXPORT_CAN_HANDLE_LAYERS;
|
|
|
|
|
|
|
|
g_object_set (G_OBJECT (options),
|
|
|
|
"capabilities", capabilities,
|
|
|
|
NULL);
|
|
|
|
}
|
|
|
|
|
1997-11-24 22:05:25 +00:00
|
|
|
static gboolean
|
2006-04-13 11:16:13 +00:00
|
|
|
bad_bounds_dialog (void)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
2005-09-09 18:07:31 +00:00
|
|
|
GtkWidget *dialog;
|
2003-11-06 15:27:05 +00:00
|
|
|
gboolean crop;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2006-10-12 10:00:23 +00:00
|
|
|
dialog = gtk_message_dialog_new (NULL, 0,
|
|
|
|
GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE,
|
2016-02-16 02:35:43 +01:00
|
|
|
_("The image you are trying to export as a "
|
2006-10-12 10:00:23 +00:00
|
|
|
"GIF contains layers which extend beyond "
|
|
|
|
"the actual borders of the image."));
|
2000-01-06 22:26:10 +00:00
|
|
|
|
2006-10-12 10:00:23 +00:00
|
|
|
gtk_dialog_add_buttons (GTK_DIALOG (dialog),
|
2017-02-12 16:18:24 +01:00
|
|
|
_("_Cancel"), GTK_RESPONSE_CANCEL,
|
|
|
|
_("Cr_op"), GTK_RESPONSE_OK,
|
2006-10-12 10:00:23 +00:00
|
|
|
NULL);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2018-05-10 17:04:37 +02:00
|
|
|
gimp_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
|
2005-08-13 22:52:41 +00:00
|
|
|
GTK_RESPONSE_OK,
|
|
|
|
GTK_RESPONSE_CANCEL,
|
|
|
|
-1);
|
2005-02-08 20:40:33 +00:00
|
|
|
|
2005-09-09 18:07:31 +00:00
|
|
|
gimp_window_set_transient (GTK_WINDOW (dialog));
|
|
|
|
|
2006-10-12 10:00:23 +00:00
|
|
|
gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
|
|
|
|
_("The GIF file format does not "
|
|
|
|
"allow this. You may choose "
|
|
|
|
"whether to crop all of the "
|
|
|
|
"layers to the image borders, "
|
2016-02-16 02:35:43 +01:00
|
|
|
"or cancel this export."));
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2005-09-09 18:07:31 +00:00
|
|
|
gtk_widget_show (dialog);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2006-10-12 10:00:23 +00:00
|
|
|
crop = (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK);
|
2003-11-06 15:27:05 +00:00
|
|
|
|
2005-09-09 18:07:31 +00:00
|
|
|
gtk_widget_destroy (dialog);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2003-11-06 15:27:05 +00:00
|
|
|
return crop;
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|
|
|
|
|
2016-04-14 16:54:08 +01:00
|
|
|
static gboolean
|
2019-10-28 12:12:09 +01:00
|
|
|
save_dialog (GimpImage *image,
|
2024-01-21 14:55:38 +00:00
|
|
|
GimpImage *orig_image,
|
2019-10-28 12:12:09 +01:00
|
|
|
GimpProcedure *procedure,
|
|
|
|
GObject *config)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
2005-09-09 18:07:31 +00:00
|
|
|
GtkWidget *dialog;
|
2023-04-23 17:35:52 +00:00
|
|
|
GtkWidget *vbox;
|
2001-09-09 19:54:37 +00:00
|
|
|
GtkWidget *text_view;
|
2003-11-06 15:27:05 +00:00
|
|
|
GtkTextBuffer *text_buffer;
|
2024-10-22 01:47:45 +02:00
|
|
|
GimpLayer **layers;
|
2019-10-28 12:12:09 +01:00
|
|
|
gint32 n_layers;
|
|
|
|
gboolean animation_supported;
|
2003-11-06 15:27:05 +00:00
|
|
|
gboolean run;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2024-10-22 01:47:45 +02:00
|
|
|
layers = gimp_image_get_layers (image);
|
|
|
|
n_layers = gimp_core_object_array_get_length ((GObject **) layers);
|
|
|
|
g_free (layers);
|
2019-10-28 12:12:09 +01:00
|
|
|
|
|
|
|
animation_supported = n_layers > 1;
|
2019-08-24 15:57:54 +02:00
|
|
|
|
2024-04-20 03:08:57 +00:00
|
|
|
dialog = gimp_export_procedure_dialog_new (GIMP_EXPORT_PROCEDURE (procedure),
|
|
|
|
GIMP_PROCEDURE_CONFIG (config),
|
|
|
|
image);
|
2000-01-25 17:46:56 +00:00
|
|
|
|
2024-01-21 14:55:38 +00:00
|
|
|
/* Warn user that image will be converted to 8 bits if not already */
|
|
|
|
if (gimp_image_get_precision (orig_image) != GIMP_PRECISION_U8_NON_LINEAR &&
|
|
|
|
gimp_image_get_precision (orig_image) != GIMP_PRECISION_U8_LINEAR &&
|
|
|
|
gimp_image_get_precision (orig_image) != GIMP_PRECISION_U8_PERCEPTUAL)
|
|
|
|
{
|
|
|
|
GtkWidget *hint;
|
|
|
|
|
|
|
|
/* Used to create vbox to store hintbox in */
|
|
|
|
gimp_procedure_dialog_get_label (GIMP_PROCEDURE_DIALOG (dialog),
|
|
|
|
"spacer-8-bit", " ",
|
|
|
|
FALSE, FALSE);
|
|
|
|
|
|
|
|
vbox = gimp_procedure_dialog_fill_box (GIMP_PROCEDURE_DIALOG (dialog),
|
|
|
|
"8-bit-warning-vbox", "spacer-8-bit",
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
hint = g_object_new (GIMP_TYPE_HINT_BOX,
|
|
|
|
"icon-name", GIMP_ICON_DIALOG_WARNING,
|
|
|
|
"hint", _("The GIF format only supports 8 bit "
|
|
|
|
"integer precision.\n"
|
|
|
|
"Your image will be converted on "
|
|
|
|
"export, which may change the pixel "
|
|
|
|
"values."),
|
|
|
|
NULL);
|
|
|
|
gtk_widget_set_visible (hint, TRUE);
|
|
|
|
|
|
|
|
gtk_box_pack_start (GTK_BOX (vbox), hint, FALSE, FALSE, 0);
|
|
|
|
gtk_box_reorder_child (GTK_BOX (vbox), hint, 0);
|
|
|
|
|
|
|
|
gimp_procedure_dialog_fill (GIMP_PROCEDURE_DIALOG (dialog),
|
|
|
|
"8-bit-warning-vbox", NULL);
|
|
|
|
}
|
|
|
|
|
2023-04-23 17:35:52 +00:00
|
|
|
#define MAX_COMMENT 240
|
1999-01-23 18:49:52 +00:00
|
|
|
|
2023-04-23 17:35:52 +00:00
|
|
|
text_view = gimp_procedure_dialog_get_widget (GIMP_PROCEDURE_DIALOG (dialog),
|
|
|
|
"gimp-comment",
|
|
|
|
GTK_TYPE_TEXT_VIEW);
|
|
|
|
text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
|
|
|
|
g_object_set_data (G_OBJECT (text_buffer), "max-len",
|
|
|
|
GINT_TO_POINTER (MAX_COMMENT));
|
2004-11-23 14:28:43 +00:00
|
|
|
|
2023-04-23 17:35:52 +00:00
|
|
|
gimp_procedure_dialog_fill_scrolled_window (GIMP_PROCEDURE_DIALOG (dialog),
|
|
|
|
"comment-scrolled",
|
|
|
|
"gimp-comment");
|
2019-10-28 12:12:09 +01:00
|
|
|
|
2023-04-23 17:35:52 +00:00
|
|
|
gimp_procedure_dialog_fill_frame (GIMP_PROCEDURE_DIALOG (dialog),
|
|
|
|
"comment-frame", "save-comment", FALSE,
|
|
|
|
"comment-scrolled");
|
2019-10-28 12:12:09 +01:00
|
|
|
|
2023-04-23 17:35:52 +00:00
|
|
|
gimp_procedure_dialog_fill (GIMP_PROCEDURE_DIALOG (dialog),
|
|
|
|
"interlace", "comment-frame", NULL);
|
2019-10-28 12:12:09 +01:00
|
|
|
|
|
|
|
if (animation_supported)
|
2013-05-27 22:32:08 +02:00
|
|
|
{
|
2019-10-28 12:12:09 +01:00
|
|
|
GtkWidget *grid;
|
2023-04-23 17:35:52 +00:00
|
|
|
GtkWidget *widget;
|
2019-10-28 12:12:09 +01:00
|
|
|
|
|
|
|
grid = gtk_grid_new ();
|
2023-04-23 17:35:52 +00:00
|
|
|
gtk_grid_set_column_spacing (GTK_GRID (grid), 1);
|
2019-10-28 12:12:09 +01:00
|
|
|
gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
|
|
|
|
gtk_widget_show (grid);
|
|
|
|
|
2023-04-23 17:35:52 +00:00
|
|
|
widget = gimp_procedure_dialog_get_widget (GIMP_PROCEDURE_DIALOG (dialog),
|
|
|
|
"number-of-repeats",
|
|
|
|
GIMP_TYPE_LABEL_SPIN);
|
|
|
|
gtk_grid_attach (GTK_GRID (grid), widget, 0, 0, 1, 1);
|
|
|
|
gtk_widget_set_visible (widget, TRUE);
|
|
|
|
|
|
|
|
widget = gimp_procedure_dialog_get_widget (GIMP_PROCEDURE_DIALOG (dialog),
|
|
|
|
"loop", GTK_TYPE_CHECK_BUTTON);
|
|
|
|
gtk_grid_attach (GTK_GRID (grid), widget, 1, 0, 1, 1);
|
|
|
|
|
|
|
|
gimp_procedure_dialog_set_sensitive (GIMP_PROCEDURE_DIALOG (dialog),
|
|
|
|
"number-of-repeats", TRUE,
|
|
|
|
config, "loop",
|
|
|
|
TRUE);
|
|
|
|
|
|
|
|
widget = gimp_procedure_dialog_get_widget (GIMP_PROCEDURE_DIALOG (dialog),
|
|
|
|
"default-delay",
|
|
|
|
GIMP_TYPE_LABEL_SPIN);
|
|
|
|
gtk_grid_attach (GTK_GRID (grid), widget, 0, 1, 1, 1);
|
|
|
|
gtk_widget_set_visible (widget, TRUE);
|
|
|
|
|
|
|
|
widget = gimp_procedure_dialog_get_label (GIMP_PROCEDURE_DIALOG (dialog),
|
|
|
|
"milliseconds",
|
2023-07-18 00:06:26 +02:00
|
|
|
_("milliseconds"),
|
|
|
|
FALSE, FALSE);
|
2023-04-23 17:35:52 +00:00
|
|
|
gtk_label_set_xalign (GTK_LABEL (widget), 0.1);
|
|
|
|
gtk_grid_attach (GTK_GRID (grid), widget, 1, 1, 1, 1);
|
|
|
|
gtk_widget_set_visible (widget, TRUE);
|
2019-10-28 12:12:09 +01:00
|
|
|
|
2024-08-19 14:40:46 +00:00
|
|
|
widget = gimp_procedure_dialog_get_widget (GIMP_PROCEDURE_DIALOG (dialog),
|
|
|
|
"default-dispose",
|
|
|
|
G_TYPE_NONE);
|
2023-04-23 17:35:52 +00:00
|
|
|
gtk_grid_attach (GTK_GRID (grid), widget, 0, 2, 2, 1);
|
|
|
|
gtk_widget_set_visible (widget, TRUE);
|
|
|
|
|
|
|
|
vbox = gimp_procedure_dialog_fill_box (GIMP_PROCEDURE_DIALOG (dialog),
|
|
|
|
"animation-vbox", "force-delay",
|
|
|
|
"force-dispose", NULL);
|
|
|
|
|
|
|
|
gtk_box_pack_start (GTK_BOX (vbox), grid, FALSE, FALSE, 0);
|
|
|
|
gtk_box_reorder_child (GTK_BOX (vbox), grid, 0);
|
|
|
|
|
|
|
|
gimp_procedure_dialog_fill_frame (GIMP_PROCEDURE_DIALOG (dialog),
|
|
|
|
"ani-frame", "as-animation", FALSE,
|
|
|
|
"animation-vbox");
|
|
|
|
|
|
|
|
gimp_procedure_dialog_fill (GIMP_PROCEDURE_DIALOG (dialog),
|
|
|
|
"ani-frame", NULL);
|
2013-05-27 22:32:08 +02:00
|
|
|
}
|
2000-01-25 17:46:56 +00:00
|
|
|
else
|
2013-05-27 22:32:08 +02:00
|
|
|
{
|
2019-10-28 12:12:09 +01:00
|
|
|
GtkWidget *hint;
|
|
|
|
|
2023-04-23 17:35:52 +00:00
|
|
|
/* Used to create vbox to store hintbox in */
|
|
|
|
gimp_procedure_dialog_get_label (GIMP_PROCEDURE_DIALOG (dialog),
|
2023-07-18 00:06:26 +02:00
|
|
|
"spacer", " ",
|
|
|
|
FALSE, FALSE);
|
2023-04-23 17:35:52 +00:00
|
|
|
gimp_procedure_dialog_get_label (GIMP_PROCEDURE_DIALOG (dialog),
|
2023-07-18 00:06:26 +02:00
|
|
|
"no-ani-title", _("Animated GIF"),
|
|
|
|
FALSE, FALSE);
|
2023-04-23 17:35:52 +00:00
|
|
|
|
|
|
|
vbox = gimp_procedure_dialog_fill_box (GIMP_PROCEDURE_DIALOG (dialog),
|
|
|
|
"no-animation-vbox", "spacer",
|
|
|
|
NULL);
|
2019-10-28 12:12:09 +01:00
|
|
|
|
|
|
|
hint = gimp_hint_box_new (_("You can only export as animation when the "
|
|
|
|
"image has more than one layer.\n"
|
|
|
|
"The image you are trying to export only "
|
|
|
|
"has one layer."));
|
2023-04-23 17:35:52 +00:00
|
|
|
gtk_box_pack_start (GTK_BOX (vbox), hint, FALSE, FALSE, 0);
|
|
|
|
gtk_widget_set_margin_bottom (vbox, 12);
|
2019-10-28 12:12:09 +01:00
|
|
|
gtk_widget_show (hint);
|
2023-04-23 17:35:52 +00:00
|
|
|
gtk_box_reorder_child (GTK_BOX (vbox), hint, 0);
|
|
|
|
|
|
|
|
gimp_procedure_dialog_fill_frame (GIMP_PROCEDURE_DIALOG (dialog),
|
|
|
|
"no-ani-frame", "no-ani-title", FALSE,
|
|
|
|
"no-animation-vbox");
|
|
|
|
|
|
|
|
gimp_procedure_dialog_fill (GIMP_PROCEDURE_DIALOG (dialog),
|
|
|
|
"no-ani-frame", NULL);
|
2016-07-31 06:23:43 +02:00
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2005-09-09 18:07:31 +00:00
|
|
|
gtk_widget_show (dialog);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2019-10-28 12:12:09 +01:00
|
|
|
run = gimp_procedure_dialog_run (GIMP_PROCEDURE_DIALOG (dialog));
|
2003-11-06 15:27:05 +00:00
|
|
|
|
2005-09-09 18:07:31 +00:00
|
|
|
gtk_widget_destroy (dialog);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2003-11-06 15:27:05 +00:00
|
|
|
return run;
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2016-04-14 16:54:08 +01:00
|
|
|
colors_to_bpp (gint colors)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
2013-05-27 22:55:34 +02:00
|
|
|
gint bpp;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
if (colors <= 2)
|
|
|
|
bpp = 1;
|
|
|
|
else if (colors <= 4)
|
|
|
|
bpp = 2;
|
|
|
|
else if (colors <= 8)
|
|
|
|
bpp = 3;
|
|
|
|
else if (colors <= 16)
|
|
|
|
bpp = 4;
|
|
|
|
else if (colors <= 32)
|
|
|
|
bpp = 5;
|
|
|
|
else if (colors <= 64)
|
|
|
|
bpp = 6;
|
|
|
|
else if (colors <= 128)
|
|
|
|
bpp = 7;
|
|
|
|
else if (colors <= 256)
|
|
|
|
bpp = 8;
|
|
|
|
else
|
|
|
|
{
|
2013-06-06 23:26:16 +02:00
|
|
|
g_warning ("GIF: colors_to_bpp - Eep! too many colors: %d\n", colors);
|
1997-11-24 22:05:25 +00:00
|
|
|
return 8;
|
|
|
|
}
|
|
|
|
|
|
|
|
return bpp;
|
|
|
|
}
|
|
|
|
|
1998-09-15 20:34:30 +00:00
|
|
|
static int
|
2016-04-14 16:54:08 +01:00
|
|
|
bpp_to_colors (gint bpp)
|
1998-09-15 20:34:30 +00:00
|
|
|
{
|
2013-05-27 22:55:34 +02:00
|
|
|
gint colors;
|
1998-09-15 20:34:30 +00:00
|
|
|
|
2013-05-27 22:55:34 +02:00
|
|
|
if (bpp > 8)
|
1998-09-15 20:34:30 +00:00
|
|
|
{
|
2006-04-13 11:16:13 +00:00
|
|
|
g_warning ("GIF: bpp_to_colors - Eep! bpp==%d !\n", bpp);
|
1998-09-15 20:34:30 +00:00
|
|
|
return 256;
|
|
|
|
}
|
2003-11-06 15:27:05 +00:00
|
|
|
|
1998-09-15 20:34:30 +00:00
|
|
|
colors = 1 << bpp;
|
|
|
|
|
2013-05-27 22:55:34 +02:00
|
|
|
return colors;
|
1998-09-15 20:34:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-14 16:54:08 +01:00
|
|
|
static gint
|
|
|
|
get_pixel (gint x,
|
|
|
|
gint y)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
|
|
|
return *(pixels + (rowstride * (long) y) + (long) x);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
|
|
*
|
|
|
|
* GIFENCODE.C - GIF Image compression interface
|
|
|
|
*
|
|
|
|
* GIFEncode( FName, GHeight, GWidth, GInterlace, Background, Transparent,
|
2006-04-13 11:16:13 +00:00
|
|
|
* BitsPerPixel, Red, Green, Blue, get_pixel )
|
1997-11-24 22:05:25 +00:00
|
|
|
*
|
|
|
|
*****************************************************************************/
|
|
|
|
|
2000-01-25 17:46:56 +00:00
|
|
|
static gint Width, Height;
|
|
|
|
static gint curx, cury;
|
|
|
|
static glong CountDown;
|
|
|
|
static gint Pass = 0;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Bump the 'curx' and 'cury' to point to the next pixel
|
|
|
|
*/
|
|
|
|
static void
|
2006-04-13 11:16:13 +00:00
|
|
|
bump_pixel (void)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Bump the current X position
|
|
|
|
*/
|
1998-10-09 18:01:35 +00:00
|
|
|
curx++;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* If we are at the end of a scan line, set curx back to the beginning
|
|
|
|
* If we are interlaced, bump the cury to the appropriate spot,
|
|
|
|
* otherwise, just increment it.
|
|
|
|
*/
|
|
|
|
if (curx == Width)
|
|
|
|
{
|
2003-06-13 14:37:00 +00:00
|
|
|
cur_progress++;
|
2006-04-04 14:03:00 +00:00
|
|
|
|
2008-03-20 09:29:04 +00:00
|
|
|
if ((cur_progress % 20) == 0)
|
2006-04-04 14:03:00 +00:00
|
|
|
gimp_progress_update ((gdouble) cur_progress / (gdouble) max_progress);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
curx = 0;
|
|
|
|
|
2006-04-13 11:16:13 +00:00
|
|
|
if (! Interlace)
|
|
|
|
++cury;
|
1997-11-24 22:05:25 +00:00
|
|
|
else
|
2006-04-13 11:16:13 +00:00
|
|
|
{
|
|
|
|
switch (Pass)
|
|
|
|
{
|
|
|
|
|
|
|
|
case 0:
|
|
|
|
cury += 8;
|
|
|
|
if (cury >= Height)
|
|
|
|
{
|
|
|
|
Pass++;
|
|
|
|
cury = 4;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 1:
|
|
|
|
cury += 8;
|
|
|
|
if (cury >= Height)
|
|
|
|
{
|
|
|
|
Pass++;
|
|
|
|
cury = 2;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 2:
|
|
|
|
cury += 4;
|
|
|
|
if (cury >= Height)
|
|
|
|
{
|
|
|
|
Pass++;
|
|
|
|
cury = 1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 3:
|
|
|
|
cury += 2;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return the next pixel from the image
|
|
|
|
*/
|
2016-04-14 16:54:08 +01:00
|
|
|
static gint
|
2006-04-13 11:16:13 +00:00
|
|
|
gif_next_pixel (ifunptr getpixel)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
2016-04-14 16:54:08 +01:00
|
|
|
gint r;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
if (CountDown == 0)
|
|
|
|
return EOF;
|
|
|
|
|
|
|
|
--CountDown;
|
|
|
|
|
|
|
|
r = (*getpixel) (curx, cury);
|
|
|
|
|
2006-04-13 11:16:13 +00:00
|
|
|
bump_pixel ();
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* public */
|
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
static gboolean
|
|
|
|
gif_encode_header (GOutputStream *output,
|
|
|
|
gboolean gif89,
|
|
|
|
gint GWidth,
|
|
|
|
gint GHeight,
|
|
|
|
gint Background,
|
|
|
|
gint BitsPerPixel,
|
|
|
|
gint Red[],
|
|
|
|
gint Green[],
|
|
|
|
gint Blue[],
|
|
|
|
ifunptr get_pixel,
|
|
|
|
GError **error)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
2016-04-14 16:54:08 +01:00
|
|
|
gint B;
|
|
|
|
gint RWidth, RHeight;
|
|
|
|
gint Resolution;
|
|
|
|
gint ColorMapSize;
|
|
|
|
gint i;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
ColorMapSize = 1 << BitsPerPixel;
|
|
|
|
|
|
|
|
RWidth = Width = GWidth;
|
|
|
|
RHeight = Height = GHeight;
|
|
|
|
|
|
|
|
Resolution = BitsPerPixel;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Calculate number of bits we are expecting
|
|
|
|
*/
|
|
|
|
CountDown = (long) Width *(long) Height;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Indicate which pass we are on (if interlace)
|
|
|
|
*/
|
|
|
|
Pass = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set up the current x and y position
|
|
|
|
*/
|
|
|
|
curx = cury = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Write the Magic header
|
|
|
|
*/
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! put_string (output, gif89 ? "GIF89a" : "GIF87a", error))
|
|
|
|
return FALSE;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Write out the screen width and height
|
|
|
|
*/
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! put_word (output, RWidth, error) ||
|
|
|
|
! put_word (output, RHeight, error))
|
|
|
|
return FALSE;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
/*
|
2013-06-06 23:26:16 +02:00
|
|
|
* Indicate that there is a global color map
|
1997-11-24 22:05:25 +00:00
|
|
|
*/
|
2006-04-13 11:16:13 +00:00
|
|
|
B = 0x80; /* Yes, there is a color map */
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* OR in the resolution
|
|
|
|
*/
|
|
|
|
B |= (Resolution - 1) << 5;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* OR in the Bits per Pixel
|
|
|
|
*/
|
|
|
|
B |= (BitsPerPixel - 1);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Write it out
|
|
|
|
*/
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! put_byte (output, B, error))
|
|
|
|
return FALSE;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
/*
|
2013-06-06 23:26:16 +02:00
|
|
|
* Write out the Background color
|
1997-11-24 22:05:25 +00:00
|
|
|
*/
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! put_byte (output, Background, error))
|
|
|
|
return FALSE;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Byte of 0's (future expansion)
|
|
|
|
*/
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! put_byte (output, 0, error))
|
|
|
|
return FALSE;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
/*
|
2013-06-06 23:26:16 +02:00
|
|
|
* Write out the Global Color Map
|
1997-11-24 22:05:25 +00:00
|
|
|
*/
|
1998-10-09 18:01:35 +00:00
|
|
|
for (i = 0; i < ColorMapSize; i++)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! put_byte (output, Red[i], error) ||
|
|
|
|
! put_byte (output, Green[i], error) ||
|
|
|
|
! put_byte (output, Blue[i], error))
|
|
|
|
return FALSE;
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|
2016-04-14 18:49:00 +01:00
|
|
|
|
|
|
|
return TRUE;
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
static gboolean
|
|
|
|
gif_encode_graphic_control_ext (GOutputStream *output,
|
|
|
|
int Disposal,
|
|
|
|
int Delay89,
|
|
|
|
int NumFramesInImage,
|
|
|
|
int GWidth,
|
|
|
|
int GHeight,
|
|
|
|
int Transparent,
|
|
|
|
int BitsPerPixel,
|
|
|
|
ifunptr get_pixel,
|
|
|
|
GError **error)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
2011-10-03 12:49:06 +05:30
|
|
|
Width = GWidth;
|
|
|
|
Height = GHeight;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Calculate number of bits we are expecting
|
|
|
|
*/
|
|
|
|
CountDown = (long) Width *(long) Height;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Indicate which pass we are on (if interlace)
|
|
|
|
*/
|
|
|
|
Pass = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set up the current x and y position
|
|
|
|
*/
|
|
|
|
curx = cury = 0;
|
|
|
|
|
|
|
|
/*
|
2013-06-06 23:26:16 +02:00
|
|
|
* Write out extension for transparent color index, if necessary.
|
1997-11-24 22:05:25 +00:00
|
|
|
*/
|
|
|
|
if ( (Transparent >= 0) || (NumFramesInImage > 1) )
|
|
|
|
{
|
|
|
|
/* Extension Introducer - fixed. */
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! put_byte (output, '!', error))
|
|
|
|
return FALSE;
|
|
|
|
|
1997-11-24 22:05:25 +00:00
|
|
|
/* Graphic Control Label - fixed. */
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! put_byte (output, 0xf9, error))
|
|
|
|
return FALSE;
|
|
|
|
|
1997-11-24 22:05:25 +00:00
|
|
|
/* Block Size - fixed. */
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! put_byte (output, 4, error))
|
|
|
|
return FALSE;
|
2003-11-06 15:27:05 +00:00
|
|
|
|
1997-11-24 22:05:25 +00:00
|
|
|
/* Packed Fields - XXXdddut (d=disposal, u=userInput, t=transFlag) */
|
|
|
|
/* s8421 */
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! put_byte (output,
|
|
|
|
((Transparent >= 0) ? 0x01 : 0x00) /* TRANSPARENCY */
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
/* DISPOSAL */
|
|
|
|
| ((NumFramesInImage > 1) ? (Disposal << 2) : 0x00 ),
|
|
|
|
/* 0x03 or 0x01 build frames cumulatively */
|
|
|
|
/* 0x02 clears frame before drawing */
|
|
|
|
/* 0x00 'don't care' */
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
error))
|
|
|
|
return FALSE;
|
2003-11-06 15:27:05 +00:00
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! put_word (output, Delay89, error))
|
|
|
|
return FALSE;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! put_byte (output, Transparent, error) ||
|
|
|
|
! put_byte (output, 0, error))
|
|
|
|
return FALSE;
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|
2016-04-14 18:49:00 +01:00
|
|
|
|
|
|
|
return TRUE;
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
static gboolean
|
|
|
|
gif_encode_image_data (GOutputStream *output,
|
|
|
|
int GWidth,
|
|
|
|
int GHeight,
|
|
|
|
int GInterlace,
|
|
|
|
int BitsPerPixel,
|
|
|
|
ifunptr get_pixel,
|
|
|
|
gint offset_x,
|
|
|
|
gint offset_y,
|
|
|
|
GError **error)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
2016-04-14 18:49:00 +01:00
|
|
|
gint LeftOfs, TopOfs;
|
|
|
|
gint InitCodeSize;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
Interlace = GInterlace;
|
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
Width = GWidth;
|
|
|
|
Height = GHeight;
|
|
|
|
LeftOfs = (gint) offset_x;
|
|
|
|
TopOfs = (gint) offset_y;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Calculate number of bits we are expecting
|
|
|
|
*/
|
2006-04-13 11:16:13 +00:00
|
|
|
CountDown = (long) Width * (long) Height;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Indicate which pass we are on (if interlace)
|
|
|
|
*/
|
|
|
|
Pass = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The initial code size
|
|
|
|
*/
|
|
|
|
if (BitsPerPixel <= 1)
|
|
|
|
InitCodeSize = 2;
|
|
|
|
else
|
|
|
|
InitCodeSize = BitsPerPixel;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set up the current x and y position
|
|
|
|
*/
|
|
|
|
curx = cury = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Write an Image separator
|
|
|
|
*/
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! put_byte (output, ',', error))
|
|
|
|
return FALSE;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Write the Image header
|
|
|
|
*/
|
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! put_word (output, LeftOfs, error) ||
|
|
|
|
! put_word (output, TopOfs, error) ||
|
|
|
|
! put_word (output, Width, error) ||
|
|
|
|
! put_word (output, Height, error))
|
|
|
|
return FALSE;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Write out whether or not the image is interlaced
|
|
|
|
*/
|
|
|
|
if (Interlace)
|
2016-04-14 18:49:00 +01:00
|
|
|
{
|
|
|
|
if (! put_byte (output, 0x40, error))
|
|
|
|
return FALSE;
|
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
else
|
2016-04-14 18:49:00 +01:00
|
|
|
{
|
|
|
|
if (! put_byte (output, 0x00, error))
|
|
|
|
return FALSE;
|
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Write out the initial code size
|
|
|
|
*/
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! put_byte (output, InitCodeSize, error))
|
|
|
|
return FALSE;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Go and actually compress the data
|
|
|
|
*/
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! compress (output, InitCodeSize + 1, get_pixel, error))
|
|
|
|
return FALSE;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Write out a Zero-length packet (to end the series)
|
|
|
|
*/
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! put_byte (output, 0, error))
|
|
|
|
return FALSE;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2006-04-13 11:16:13 +00:00
|
|
|
#if 0
|
1997-11-24 22:05:25 +00:00
|
|
|
/***************************/
|
|
|
|
Interlace = GInterlace;
|
2011-10-03 18:56:14 +05:30
|
|
|
Width = GWidth;
|
|
|
|
Height = GHeight;
|
1997-11-24 22:05:25 +00:00
|
|
|
LeftOfs = TopOfs = 0;
|
|
|
|
|
|
|
|
CountDown = (long) Width *(long) Height;
|
|
|
|
Pass = 0;
|
|
|
|
/*
|
|
|
|
* The initial code size
|
|
|
|
*/
|
|
|
|
if (BitsPerPixel <= 1)
|
|
|
|
InitCodeSize = 2;
|
|
|
|
else
|
|
|
|
InitCodeSize = BitsPerPixel;
|
|
|
|
/*
|
|
|
|
* Set up the current x and y position
|
|
|
|
*/
|
|
|
|
curx = cury = 0;
|
|
|
|
#endif
|
2016-04-14 18:49:00 +01:00
|
|
|
|
|
|
|
return TRUE;
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
static gboolean
|
|
|
|
gif_encode_close (GOutputStream *output,
|
|
|
|
GError **error)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Write the GIF file terminator
|
|
|
|
*/
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! put_byte (output, ';', error))
|
|
|
|
return FALSE;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* And close the file
|
|
|
|
*/
|
2016-04-14 18:49:00 +01:00
|
|
|
return g_output_stream_close (output, NULL, error);
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
static gboolean
|
|
|
|
gif_encode_loop_ext (GOutputStream *output,
|
|
|
|
guint num_loops,
|
|
|
|
GError **error)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
2016-04-14 18:49:00 +01:00
|
|
|
return (put_byte (output, 0x21, error) &&
|
|
|
|
put_byte (output, 0xff, error) &&
|
|
|
|
put_byte (output, 0x0b, error) &&
|
|
|
|
put_string (output, "NETSCAPE2.0", error) &&
|
|
|
|
put_byte (output, 0x03, error) &&
|
|
|
|
put_byte (output, 0x01, error) &&
|
|
|
|
put_word (output, num_loops, error) &&
|
|
|
|
put_byte (output, 0x00, error));
|
2006-04-13 11:16:13 +00:00
|
|
|
|
|
|
|
/* NOTE: num_loops == 0 means 'loop infinitely' */
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
static gboolean
|
|
|
|
gif_encode_comment_ext (GOutputStream *output,
|
|
|
|
const gchar *comment,
|
|
|
|
GError **error)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
1999-11-20 02:20:04 +00:00
|
|
|
if (!comment || !*comment)
|
2016-04-14 18:49:00 +01:00
|
|
|
return TRUE;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2003-07-03 17:45:12 +00:00
|
|
|
if (strlen (comment) > 240)
|
1999-01-23 18:49:52 +00:00
|
|
|
{
|
2003-06-16 17:51:03 +00:00
|
|
|
g_printerr ("GIF: warning:"
|
|
|
|
"comment too large - comment block not written.\n");
|
2016-04-14 18:49:00 +01:00
|
|
|
return TRUE;
|
1999-01-23 18:49:52 +00:00
|
|
|
}
|
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
return (put_byte (output, 0x21, error) &&
|
|
|
|
put_byte (output, 0xfe, error) &&
|
|
|
|
put_byte (output, strlen (comment), error) &&
|
|
|
|
put_string (output, comment, error) &&
|
|
|
|
put_byte (output, 0x00, error));
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
2016-04-14 18:49:00 +01:00
|
|
|
* Write stuff to the GIF file
|
1997-11-24 22:05:25 +00:00
|
|
|
*/
|
2016-04-14 18:49:00 +01:00
|
|
|
static gboolean
|
|
|
|
put_byte (GOutputStream *output,
|
|
|
|
guchar b,
|
|
|
|
GError **error)
|
|
|
|
{
|
|
|
|
return g_data_output_stream_put_byte (G_DATA_OUTPUT_STREAM (output),
|
|
|
|
b, NULL, error);
|
|
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
put_word (GOutputStream *output,
|
|
|
|
gint w,
|
|
|
|
GError **error)
|
|
|
|
{
|
|
|
|
return g_data_output_stream_put_int16 (G_DATA_OUTPUT_STREAM (output),
|
|
|
|
w, NULL, error);
|
|
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
put_string (GOutputStream *output,
|
|
|
|
const gchar *s,
|
|
|
|
GError **error)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
2016-04-14 18:49:00 +01:00
|
|
|
return g_data_output_stream_put_string (G_DATA_OUTPUT_STREAM (output),
|
|
|
|
s, NULL, error);
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
|
|
*
|
|
|
|
* GIFCOMPR.C - GIF Image compression routines
|
|
|
|
*
|
|
|
|
* Lempel-Ziv compression based on 'compress'. GIF modifications by
|
|
|
|
* David Rowley (mgardi@watdcsu.waterloo.edu)
|
|
|
|
*
|
|
|
|
***************************************************************************/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* General DEFINEs
|
|
|
|
*/
|
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
#define GIF_BITS 12
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
#define HSIZE 5003 /* 80% occupancy */
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* GIF Image compression - modified 'compress'
|
|
|
|
*
|
|
|
|
* Based on: compress.c - File compression ala IEEE Computer, June 1984.
|
|
|
|
*
|
|
|
|
* By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
|
|
|
|
* Jim McKie (decvax!mcvax!jim)
|
|
|
|
* Steve Davies (decvax!vax135!petsd!peora!srd)
|
|
|
|
* Ken Turkowski (decvax!decwrl!turtlevax!ken)
|
|
|
|
* James A. Woods (decvax!ihnp4!ames!jaw)
|
|
|
|
* Joe Orost (decvax!vax135!petsd!joe)
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
static gint n_bits; /* number of bits/code */
|
|
|
|
static gint maxbits = GIF_BITS; /* user settable max # bits/code */
|
|
|
|
static gint maxcode; /* maximum code, given n_bits */
|
2013-05-27 22:55:34 +02:00
|
|
|
static gint maxmaxcode = (gint) 1 << GIF_BITS; /* should NEVER generate this code */
|
2006-04-13 11:16:13 +00:00
|
|
|
#ifdef COMPATIBLE /* But wrong! */
|
2013-05-27 22:55:34 +02:00
|
|
|
#define MAXCODE(Mn_bits) ((gint) 1 << (Mn_bits) - 1)
|
1997-11-24 22:05:25 +00:00
|
|
|
#else /*COMPATIBLE */
|
2013-05-27 22:55:34 +02:00
|
|
|
#define MAXCODE(Mn_bits) (((gint) 1 << (Mn_bits)) - 1)
|
1997-11-24 22:05:25 +00:00
|
|
|
#endif /*COMPATIBLE */
|
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
static glong htab[HSIZE];
|
|
|
|
static gushort codetab[HSIZE];
|
1997-11-24 22:05:25 +00:00
|
|
|
#define HashTabOf(i) htab[i]
|
|
|
|
#define CodeTabOf(i) codetab[i]
|
|
|
|
|
2013-05-27 22:55:34 +02:00
|
|
|
static const gint hsize = HSIZE; /* the original reason for this being
|
|
|
|
variable was "for dynamic table sizing",
|
|
|
|
but since it was never actually changed
|
|
|
|
I made it const --Adam. */
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2013-05-27 22:55:34 +02:00
|
|
|
static gint free_ent = 0; /* first unused entry */
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* block compression parameters -- after all codes are used up,
|
|
|
|
* and compression rate changes, start over.
|
|
|
|
*/
|
2016-04-14 18:49:00 +01:00
|
|
|
static gint clear_flg = 0;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
static gint offset;
|
|
|
|
static glong in_count = 1; /* length of input */
|
|
|
|
static glong out_count = 0; /* # of codes output (for debugging) */
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* compress stdin to stdout
|
|
|
|
*
|
|
|
|
* Algorithm: use open addressing double hashing (no chaining) on the
|
|
|
|
* prefix code / next character combination. We do a variant of Knuth's
|
|
|
|
* algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
|
|
|
|
* secondary probe. Here, the modular division first probe is gives way
|
|
|
|
* to a faster exclusive-or manipulation. Also do block compression with
|
|
|
|
* an adaptive reset, whereby the code table is cleared when the compression
|
|
|
|
* ratio decreases, but after the table fills. The variable-length output
|
|
|
|
* codes are re-sized at this point, and a special CLEAR code is generated
|
|
|
|
* for the decompressor. Late addition: construct the table according to
|
|
|
|
* file size for noticeable speed improvement on small files. Please direct
|
|
|
|
* questions about this implementation to ames!jaw.
|
|
|
|
*/
|
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
static gint g_init_bits;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
static gint ClearCode;
|
|
|
|
static gint EOFCode;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2013-05-27 22:55:34 +02:00
|
|
|
static gulong cur_accum;
|
|
|
|
static gint cur_bits;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2013-05-27 22:55:34 +02:00
|
|
|
static gulong masks[] =
|
|
|
|
{
|
|
|
|
0x0000, 0x0001, 0x0003, 0x0007,
|
|
|
|
0x000F, 0x001F, 0x003F, 0x007F,
|
|
|
|
0x00FF, 0x01FF, 0x03FF, 0x07FF,
|
|
|
|
0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF,
|
|
|
|
0xFFFF
|
|
|
|
};
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
static gboolean
|
|
|
|
compress (GOutputStream *output,
|
|
|
|
gint init_bits,
|
|
|
|
ifunptr ReadValue,
|
|
|
|
GError **error)
|
2003-07-29 06:52:41 +00:00
|
|
|
{
|
2013-05-27 22:36:03 +02:00
|
|
|
if (FALSE)
|
2016-04-14 18:49:00 +01:00
|
|
|
return no_compress (output, init_bits, ReadValue, error);
|
2013-05-27 22:36:03 +02:00
|
|
|
else if (FALSE)
|
2016-04-14 18:49:00 +01:00
|
|
|
return rle_compress (output, init_bits, ReadValue, error);
|
2013-05-27 22:36:03 +02:00
|
|
|
else
|
2016-04-14 18:49:00 +01:00
|
|
|
return normal_compress (output, init_bits, ReadValue, error);
|
2003-07-29 06:52:41 +00:00
|
|
|
}
|
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
static gboolean
|
|
|
|
no_compress (GOutputStream *output,
|
|
|
|
gint init_bits,
|
|
|
|
ifunptr ReadValue,
|
|
|
|
GError **error)
|
2003-07-29 06:52:41 +00:00
|
|
|
{
|
2016-04-14 16:54:08 +01:00
|
|
|
glong fcode;
|
|
|
|
gint i /* = 0 */ ;
|
|
|
|
gint c;
|
|
|
|
gint ent;
|
|
|
|
gint hsize_reg;
|
|
|
|
gint hshift;
|
2003-07-29 06:52:41 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Set up the globals: g_init_bits - initial number of bits
|
|
|
|
*/
|
|
|
|
g_init_bits = init_bits;
|
|
|
|
|
|
|
|
cur_bits = 0;
|
|
|
|
cur_accum = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set up the necessary values
|
|
|
|
*/
|
|
|
|
offset = 0;
|
|
|
|
out_count = 0;
|
|
|
|
clear_flg = 0;
|
|
|
|
in_count = 1;
|
|
|
|
|
|
|
|
ClearCode = (1 << (init_bits - 1));
|
|
|
|
EOFCode = ClearCode + 1;
|
|
|
|
free_ent = ClearCode + 2;
|
|
|
|
|
|
|
|
|
|
|
|
/* Had some problems here... should be okay now. --Adam */
|
|
|
|
n_bits = g_init_bits;
|
|
|
|
maxcode = MAXCODE (n_bits);
|
|
|
|
|
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
char_init();
|
2003-07-29 06:52:41 +00:00
|
|
|
|
2006-04-13 11:16:13 +00:00
|
|
|
ent = gif_next_pixel (ReadValue);
|
2003-07-29 06:52:41 +00:00
|
|
|
|
|
|
|
hshift = 0;
|
|
|
|
for (fcode = (long) hsize; fcode < 65536L; fcode *= 2L)
|
|
|
|
++hshift;
|
2006-04-13 11:16:13 +00:00
|
|
|
hshift = 8 - hshift; /* set hash code range bound */
|
2003-07-29 06:52:41 +00:00
|
|
|
|
|
|
|
hsize_reg = hsize;
|
2013-05-27 22:55:34 +02:00
|
|
|
cl_hash ((glong) hsize_reg); /* clear hash table */
|
2003-07-29 06:52:41 +00:00
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! output_code (output, (gint) ClearCode, error))
|
|
|
|
return FALSE;
|
2003-07-29 06:52:41 +00:00
|
|
|
|
2006-04-13 11:16:13 +00:00
|
|
|
while ((c = gif_next_pixel (ReadValue)) != EOF)
|
2013-05-27 22:32:08 +02:00
|
|
|
{
|
2003-07-29 06:52:41 +00:00
|
|
|
++in_count;
|
|
|
|
|
|
|
|
fcode = (long) (((long) c << maxbits) + ent);
|
2013-05-27 22:55:34 +02:00
|
|
|
i = (((gint) c << hshift) ^ ent); /* xor hashing */
|
2003-07-29 06:52:41 +00:00
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! output_code (output, (gint) ent, error))
|
|
|
|
return FALSE;
|
|
|
|
|
2003-07-29 06:52:41 +00:00
|
|
|
++out_count;
|
|
|
|
ent = c;
|
2013-05-27 22:32:08 +02:00
|
|
|
|
2003-07-29 06:52:41 +00:00
|
|
|
if (free_ent < maxmaxcode)
|
2013-05-27 22:32:08 +02:00
|
|
|
{
|
2006-04-13 11:16:13 +00:00
|
|
|
CodeTabOf (i) = free_ent++; /* code -> hashtable */
|
|
|
|
HashTabOf (i) = fcode;
|
|
|
|
}
|
2003-07-29 06:52:41 +00:00
|
|
|
else
|
2016-04-14 18:49:00 +01:00
|
|
|
{
|
|
|
|
if (! cl_block (output, error))
|
|
|
|
return FALSE;
|
|
|
|
}
|
2003-07-29 06:52:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Put out the final code.
|
|
|
|
*/
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! output_code (output, (gint) ent, error))
|
|
|
|
return FALSE;
|
|
|
|
|
2003-07-29 06:52:41 +00:00
|
|
|
++out_count;
|
2016-04-14 18:49:00 +01:00
|
|
|
|
|
|
|
if (! output_code (output, (gint) EOFCode, error))
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
return TRUE;
|
2003-07-29 06:52:41 +00:00
|
|
|
}
|
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
static gboolean
|
|
|
|
rle_compress (GOutputStream *output,
|
|
|
|
gint init_bits,
|
|
|
|
ifunptr ReadValue,
|
|
|
|
GError **error)
|
2003-07-29 06:52:41 +00:00
|
|
|
{
|
2016-04-14 18:49:00 +01:00
|
|
|
glong fcode;
|
|
|
|
gint i /* = 0 */ ;
|
|
|
|
gint c, last;
|
|
|
|
gint ent;
|
|
|
|
gint disp;
|
|
|
|
gint hsize_reg;
|
|
|
|
gint hshift;
|
2003-07-29 06:52:41 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Set up the globals: g_init_bits - initial number of bits
|
|
|
|
*/
|
|
|
|
g_init_bits = init_bits;
|
|
|
|
|
|
|
|
cur_bits = 0;
|
|
|
|
cur_accum = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set up the necessary values
|
|
|
|
*/
|
|
|
|
offset = 0;
|
|
|
|
out_count = 0;
|
|
|
|
clear_flg = 0;
|
|
|
|
in_count = 1;
|
|
|
|
|
|
|
|
ClearCode = (1 << (init_bits - 1));
|
|
|
|
EOFCode = ClearCode + 1;
|
|
|
|
free_ent = ClearCode + 2;
|
|
|
|
|
|
|
|
|
|
|
|
/* Had some problems here... should be okay now. --Adam */
|
|
|
|
n_bits = g_init_bits;
|
|
|
|
maxcode = MAXCODE (n_bits);
|
|
|
|
|
|
|
|
|
|
|
|
char_init ();
|
|
|
|
|
2006-04-13 11:16:13 +00:00
|
|
|
last = ent = gif_next_pixel (ReadValue);
|
2003-07-29 06:52:41 +00:00
|
|
|
|
|
|
|
hshift = 0;
|
|
|
|
for (fcode = (long) hsize; fcode < 65536L; fcode *= 2L)
|
|
|
|
++hshift;
|
2006-04-13 11:16:13 +00:00
|
|
|
hshift = 8 - hshift; /* set hash code range bound */
|
2003-07-29 06:52:41 +00:00
|
|
|
|
|
|
|
hsize_reg = hsize;
|
2013-05-27 22:55:34 +02:00
|
|
|
cl_hash ((glong) hsize_reg); /* clear hash table */
|
2003-07-29 06:52:41 +00:00
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! output_code (output, (gint) ClearCode, error))
|
|
|
|
return FALSE;
|
2003-07-29 06:52:41 +00:00
|
|
|
|
|
|
|
|
2006-04-13 11:16:13 +00:00
|
|
|
while ((c = gif_next_pixel (ReadValue)) != EOF)
|
2013-05-27 22:32:08 +02:00
|
|
|
{
|
2003-07-29 06:52:41 +00:00
|
|
|
++in_count;
|
|
|
|
|
|
|
|
fcode = (long) (((long) c << maxbits) + ent);
|
2013-05-27 22:55:34 +02:00
|
|
|
i = (((gint) c << hshift) ^ ent); /* xor hashing */
|
2003-07-29 06:52:41 +00:00
|
|
|
|
|
|
|
|
|
|
|
if (last == c) {
|
|
|
|
if (HashTabOf (i) == fcode)
|
2006-04-13 11:16:13 +00:00
|
|
|
{
|
|
|
|
ent = CodeTabOf (i);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if ((long) HashTabOf (i) < 0) /* empty slot */
|
|
|
|
goto nomatch;
|
|
|
|
disp = hsize_reg - i; /* secondary hash (after G. Knott) */
|
2003-07-29 06:52:41 +00:00
|
|
|
if (i == 0)
|
2006-04-13 11:16:13 +00:00
|
|
|
disp = 1;
|
2003-07-29 06:52:41 +00:00
|
|
|
probe:
|
|
|
|
if ((i -= disp) < 0)
|
2006-04-13 11:16:13 +00:00
|
|
|
i += hsize_reg;
|
2003-11-06 15:27:05 +00:00
|
|
|
|
2003-07-29 06:52:41 +00:00
|
|
|
if (HashTabOf (i) == fcode)
|
2006-04-13 11:16:13 +00:00
|
|
|
{
|
|
|
|
ent = CodeTabOf (i);
|
|
|
|
continue;
|
|
|
|
}
|
2003-07-29 06:52:41 +00:00
|
|
|
if ((long) HashTabOf (i) > 0)
|
2006-04-13 11:16:13 +00:00
|
|
|
goto probe;
|
2003-07-29 06:52:41 +00:00
|
|
|
}
|
|
|
|
nomatch:
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! output_code (output, (gint) ent, error))
|
|
|
|
return FALSE;
|
|
|
|
|
2003-07-29 06:52:41 +00:00
|
|
|
++out_count;
|
|
|
|
last = ent = c;
|
|
|
|
if (free_ent < maxmaxcode)
|
2013-05-27 22:32:08 +02:00
|
|
|
{
|
2006-04-13 11:16:13 +00:00
|
|
|
CodeTabOf (i) = free_ent++; /* code -> hashtable */
|
|
|
|
HashTabOf (i) = fcode;
|
|
|
|
}
|
2003-07-29 06:52:41 +00:00
|
|
|
else
|
2016-04-14 18:49:00 +01:00
|
|
|
{
|
|
|
|
if (! cl_block (output, error))
|
|
|
|
return FALSE;
|
|
|
|
}
|
2003-07-29 06:52:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Put out the final code.
|
|
|
|
*/
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! output_code (output, (gint) ent, error))
|
|
|
|
return FALSE;
|
|
|
|
|
2003-07-29 06:52:41 +00:00
|
|
|
++out_count;
|
2016-04-14 18:49:00 +01:00
|
|
|
|
|
|
|
if (! output_code (output, (gint) EOFCode, error))
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
return TRUE;
|
2003-07-29 06:52:41 +00:00
|
|
|
}
|
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
static gboolean
|
|
|
|
normal_compress (GOutputStream *output,
|
|
|
|
gint init_bits,
|
|
|
|
ifunptr ReadValue,
|
|
|
|
GError **error)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
2016-04-14 18:49:00 +01:00
|
|
|
glong fcode;
|
|
|
|
gint i /* = 0 */ ;
|
|
|
|
gint c;
|
|
|
|
gint ent;
|
|
|
|
gint disp;
|
|
|
|
gint hsize_reg;
|
|
|
|
gint hshift;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Set up the globals: g_init_bits - initial number of bits
|
|
|
|
*/
|
|
|
|
g_init_bits = init_bits;
|
|
|
|
|
|
|
|
cur_bits = 0;
|
|
|
|
cur_accum = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set up the necessary values
|
|
|
|
*/
|
|
|
|
offset = 0;
|
|
|
|
out_count = 0;
|
|
|
|
clear_flg = 0;
|
|
|
|
in_count = 1;
|
|
|
|
|
|
|
|
ClearCode = (1 << (init_bits - 1));
|
|
|
|
EOFCode = ClearCode + 1;
|
|
|
|
free_ent = ClearCode + 2;
|
|
|
|
|
|
|
|
|
|
|
|
/* Had some problems here... should be okay now. --Adam */
|
|
|
|
n_bits = g_init_bits;
|
|
|
|
maxcode = MAXCODE (n_bits);
|
|
|
|
|
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
char_init();
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2006-04-13 11:16:13 +00:00
|
|
|
ent = gif_next_pixel (ReadValue);
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
hshift = 0;
|
|
|
|
for (fcode = (long) hsize; fcode < 65536L; fcode *= 2L)
|
|
|
|
++hshift;
|
2006-04-13 11:16:13 +00:00
|
|
|
hshift = 8 - hshift; /* set hash code range bound */
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
hsize_reg = hsize;
|
2013-05-27 22:55:34 +02:00
|
|
|
cl_hash ((glong) hsize_reg); /* clear hash table */
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! output_code (output, (gint) ClearCode, error))
|
|
|
|
return FALSE;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
|
2006-04-13 11:16:13 +00:00
|
|
|
while ((c = gif_next_pixel (ReadValue)) != EOF)
|
2013-05-27 22:32:08 +02:00
|
|
|
{
|
1997-11-24 22:05:25 +00:00
|
|
|
++in_count;
|
|
|
|
|
|
|
|
fcode = (long) (((long) c << maxbits) + ent);
|
2013-05-27 22:55:34 +02:00
|
|
|
i = (((gint) c << hshift) ^ ent); /* xor hashing */
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
if (HashTabOf (i) == fcode)
|
2006-04-13 11:16:13 +00:00
|
|
|
{
|
|
|
|
ent = CodeTabOf (i);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if ((long) HashTabOf (i) < 0) /* empty slot */
|
|
|
|
goto nomatch;
|
|
|
|
disp = hsize_reg - i; /* secondary hash (after G. Knott) */
|
1997-11-24 22:05:25 +00:00
|
|
|
if (i == 0)
|
2006-04-13 11:16:13 +00:00
|
|
|
disp = 1;
|
1997-11-24 22:05:25 +00:00
|
|
|
probe:
|
|
|
|
if ((i -= disp) < 0)
|
2006-04-13 11:16:13 +00:00
|
|
|
i += hsize_reg;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
if (HashTabOf (i) == fcode)
|
2006-04-13 11:16:13 +00:00
|
|
|
{
|
|
|
|
ent = CodeTabOf (i);
|
|
|
|
continue;
|
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
if ((long) HashTabOf (i) > 0)
|
2006-04-13 11:16:13 +00:00
|
|
|
goto probe;
|
1997-11-24 22:05:25 +00:00
|
|
|
nomatch:
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! output_code (output, (gint) ent, error))
|
|
|
|
return FALSE;
|
|
|
|
|
1997-11-24 22:05:25 +00:00
|
|
|
++out_count;
|
|
|
|
ent = c;
|
|
|
|
if (free_ent < maxmaxcode)
|
2013-05-27 22:32:08 +02:00
|
|
|
{
|
2006-04-13 11:16:13 +00:00
|
|
|
CodeTabOf (i) = free_ent++; /* code -> hashtable */
|
|
|
|
HashTabOf (i) = fcode;
|
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
else
|
2016-04-14 18:49:00 +01:00
|
|
|
{
|
|
|
|
if (! cl_block (output, error))
|
|
|
|
return FALSE;
|
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|
2003-07-29 06:52:41 +00:00
|
|
|
|
1997-11-24 22:05:25 +00:00
|
|
|
/*
|
|
|
|
* Put out the final code.
|
|
|
|
*/
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! output_code (output, (gint) ent, error))
|
|
|
|
return FALSE;
|
|
|
|
|
1997-11-24 22:05:25 +00:00
|
|
|
++out_count;
|
2016-04-14 18:49:00 +01:00
|
|
|
|
|
|
|
if (! output_code (output, (gint) EOFCode, error))
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
return TRUE;
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|
2003-07-29 06:52:41 +00:00
|
|
|
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
/*****************************************************************
|
|
|
|
* TAG( output )
|
|
|
|
*
|
|
|
|
* Output the given code.
|
|
|
|
* Inputs:
|
|
|
|
* code: A n_bits-bit integer. If == -1, then EOF. This assumes
|
|
|
|
* that n_bits =< (long)wordsize - 1.
|
|
|
|
* Outputs:
|
|
|
|
* Outputs code to the file.
|
|
|
|
* Assumptions:
|
|
|
|
* Chars are 8 bits long.
|
|
|
|
* Algorithm:
|
|
|
|
* Maintain a GIF_BITS character long buffer (so that 8 codes will
|
|
|
|
* fit in it exactly). Use the VAX insv instruction to insert each
|
|
|
|
* code in turn. When the buffer fills up empty it and start over.
|
|
|
|
*/
|
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
static gboolean
|
|
|
|
output_code (GOutputStream *output,
|
|
|
|
gint code,
|
|
|
|
GError **error)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
|
|
|
cur_accum &= masks[cur_bits];
|
|
|
|
|
|
|
|
if (cur_bits > 0)
|
|
|
|
cur_accum |= ((long) code << cur_bits);
|
|
|
|
else
|
|
|
|
cur_accum = code;
|
|
|
|
|
|
|
|
cur_bits += n_bits;
|
|
|
|
|
|
|
|
while (cur_bits >= 8)
|
|
|
|
{
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! char_out (output, (guchar) (cur_accum & 0xff), error))
|
|
|
|
return FALSE;
|
|
|
|
|
1997-11-24 22:05:25 +00:00
|
|
|
cur_accum >>= 8;
|
|
|
|
cur_bits -= 8;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If the next entry is going to be too big for the code size,
|
|
|
|
* then increase it, if possible.
|
|
|
|
*/
|
|
|
|
if (free_ent > maxcode || clear_flg)
|
|
|
|
{
|
|
|
|
if (clear_flg)
|
2006-04-13 11:16:13 +00:00
|
|
|
{
|
|
|
|
maxcode = MAXCODE (n_bits = g_init_bits);
|
|
|
|
clear_flg = 0;
|
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
else
|
2006-04-13 11:16:13 +00:00
|
|
|
{
|
|
|
|
++n_bits;
|
|
|
|
if (n_bits == maxbits)
|
|
|
|
maxcode = maxmaxcode;
|
|
|
|
else
|
|
|
|
maxcode = MAXCODE (n_bits);
|
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (code == EOFCode)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* At EOF, write the rest of the buffer.
|
|
|
|
*/
|
|
|
|
while (cur_bits > 0)
|
2006-04-13 11:16:13 +00:00
|
|
|
{
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! char_out (output, (guchar) (cur_accum & 0xff), error))
|
|
|
|
return FALSE;
|
|
|
|
|
2006-04-13 11:16:13 +00:00
|
|
|
cur_accum >>= 8;
|
|
|
|
cur_bits -= 8;
|
|
|
|
}
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! char_flush (output, error))
|
|
|
|
return FALSE;
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|
2016-04-14 18:49:00 +01:00
|
|
|
|
|
|
|
return TRUE;
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Clear out the hash table
|
|
|
|
*/
|
2016-04-14 18:49:00 +01:00
|
|
|
static gboolean
|
|
|
|
cl_block (GOutputStream *output,
|
|
|
|
GError **error) /* table clear for block compress */
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
2013-05-27 22:55:34 +02:00
|
|
|
cl_hash ((glong) hsize);
|
1997-11-24 22:05:25 +00:00
|
|
|
free_ent = ClearCode + 2;
|
|
|
|
clear_flg = 1;
|
|
|
|
|
2016-04-14 18:49:00 +01:00
|
|
|
return output_code (output, (gint) ClearCode, error);
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2013-05-27 22:55:34 +02:00
|
|
|
cl_hash (glong hsize) /* reset code table */
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
2013-05-30 10:56:24 +05:30
|
|
|
glong *htab_p = htab + hsize;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
2013-05-30 10:56:24 +05:30
|
|
|
long i;
|
|
|
|
long m1 = -1;
|
1997-11-24 22:05:25 +00:00
|
|
|
|
|
|
|
i = hsize - 16;
|
|
|
|
do
|
2006-04-13 11:16:13 +00:00
|
|
|
{ /* might use Sys V memset(3) here */
|
1997-11-24 22:05:25 +00:00
|
|
|
*(htab_p - 16) = m1;
|
|
|
|
*(htab_p - 15) = m1;
|
|
|
|
*(htab_p - 14) = m1;
|
|
|
|
*(htab_p - 13) = m1;
|
|
|
|
*(htab_p - 12) = m1;
|
|
|
|
*(htab_p - 11) = m1;
|
|
|
|
*(htab_p - 10) = m1;
|
|
|
|
*(htab_p - 9) = m1;
|
|
|
|
*(htab_p - 8) = m1;
|
|
|
|
*(htab_p - 7) = m1;
|
|
|
|
*(htab_p - 6) = m1;
|
|
|
|
*(htab_p - 5) = m1;
|
|
|
|
*(htab_p - 4) = m1;
|
|
|
|
*(htab_p - 3) = m1;
|
|
|
|
*(htab_p - 2) = m1;
|
|
|
|
*(htab_p - 1) = m1;
|
|
|
|
htab_p -= 16;
|
|
|
|
}
|
|
|
|
while ((i -= 16) >= 0);
|
|
|
|
|
|
|
|
for (i += 16; i > 0; --i)
|
|
|
|
*--htab_p = m1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
* GIF Specific routines
|
|
|
|
******************************************************************************/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Number of characters so far in this 'packet'
|
|
|
|
*/
|
|
|
|
static int a_count;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set up the 'byte output' routine
|
|
|
|
*/
|
|
|
|
static void
|
2004-07-21 17:06:18 +00:00
|
|
|
char_init (void)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
|
|
|
a_count = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Define the storage for the packet accumulator
|
|
|
|
*/
|
|
|
|
static char accum[256];
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Add a character to the end of the current packet, and if it is 254
|
|
|
|
* characters, flush the packet to disk.
|
|
|
|
*/
|
2016-04-14 18:49:00 +01:00
|
|
|
static gboolean
|
|
|
|
char_out (GOutputStream *output,
|
|
|
|
gint c,
|
|
|
|
GError **error)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
|
|
|
accum[a_count++] = c;
|
2016-04-14 18:49:00 +01:00
|
|
|
|
1997-11-24 22:05:25 +00:00
|
|
|
if (a_count >= 254)
|
2016-04-14 18:49:00 +01:00
|
|
|
return char_flush (output, error);
|
|
|
|
|
|
|
|
return TRUE;
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Flush the packet to disk, and reset the accumulator
|
|
|
|
*/
|
2016-04-14 18:49:00 +01:00
|
|
|
static gboolean
|
|
|
|
char_flush (GOutputStream *output,
|
|
|
|
GError **error)
|
1997-11-24 22:05:25 +00:00
|
|
|
{
|
|
|
|
if (a_count > 0)
|
|
|
|
{
|
2016-04-14 18:49:00 +01:00
|
|
|
if (! put_byte (output, a_count, error))
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
if (! g_output_stream_write_all (output, accum, a_count,
|
|
|
|
NULL, NULL, error))
|
|
|
|
return FALSE;
|
|
|
|
|
1997-11-24 22:05:25 +00:00
|
|
|
a_count = 0;
|
|
|
|
}
|
2016-04-14 18:49:00 +01:00
|
|
|
|
|
|
|
return TRUE;
|
1997-11-24 22:05:25 +00:00
|
|
|
}
|