2510 lines
71 KiB
C
2510 lines
71 KiB
C
/* GIF exporting file filter for PIKA
|
|
*
|
|
* Copyright
|
|
* - Adam D. Moss
|
|
* - Peter Mattis
|
|
* - Spencer Kimball
|
|
*
|
|
* Based around original GIF code by David Koblas.
|
|
*
|
|
*
|
|
* Version 4.1.0 - 2003-06-16
|
|
* Adam D. Moss - <adam@gimp.org> <adam@foxbox.org>
|
|
*/
|
|
/*
|
|
* 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
|
|
* CompuServe Incorporated."
|
|
*/
|
|
/* 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) | */
|
|
/* +-------------------------------------------------------------------+ */
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include <libpika/pika.h>
|
|
#include <libpika/pikaui.h>
|
|
|
|
#include "libpika/stdplugins-intl.h"
|
|
|
|
|
|
#define SAVE_PROC "file-gif-save"
|
|
#define PLUG_IN_BINARY "file-gif-save"
|
|
|
|
|
|
/* uncomment the line below for a little debugging info */
|
|
/* #define GIFDEBUG yesplease */
|
|
|
|
|
|
enum
|
|
{
|
|
DISPOSE_UNSPECIFIED,
|
|
DISPOSE_COMBINE,
|
|
DISPOSE_REPLACE
|
|
};
|
|
|
|
|
|
typedef struct _Gif Gif;
|
|
typedef struct _GifClass GifClass;
|
|
|
|
struct _Gif
|
|
{
|
|
PikaPlugIn parent_instance;
|
|
};
|
|
|
|
struct _GifClass
|
|
{
|
|
PikaPlugInClass parent_class;
|
|
};
|
|
|
|
|
|
#define GIF_TYPE (gif_get_type ())
|
|
#define GIF(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIF_TYPE, Gif))
|
|
|
|
GType gif_get_type (void) G_GNUC_CONST;
|
|
|
|
static GList * gif_query_procedures (PikaPlugIn *plug_in);
|
|
static PikaProcedure * gif_create_procedure (PikaPlugIn *plug_in,
|
|
const gchar *name);
|
|
|
|
static PikaValueArray * gif_save (PikaProcedure *procedure,
|
|
PikaRunMode run_mode,
|
|
PikaImage *image,
|
|
gint n_drawables,
|
|
PikaDrawable **drawables,
|
|
GFile *file,
|
|
PikaMetadata *metadata,
|
|
PikaProcedureConfig *config,
|
|
gpointer run_data);
|
|
|
|
static gboolean save_image (GFile *file,
|
|
PikaImage *image,
|
|
PikaDrawable *drawable,
|
|
PikaImage *orig_image,
|
|
GObject *config,
|
|
GError **error);
|
|
|
|
static PikaPDBStatusType sanity_check (GFile *file,
|
|
PikaImage **image,
|
|
PikaRunMode run_mode,
|
|
GError **error);
|
|
static gboolean bad_bounds_dialog (void);
|
|
|
|
static gboolean save_dialog (PikaImage *image,
|
|
PikaProcedure *procedure,
|
|
GObject *config);
|
|
|
|
|
|
G_DEFINE_TYPE (Gif, gif, PIKA_TYPE_PLUG_IN)
|
|
|
|
PIKA_MAIN (GIF_TYPE)
|
|
DEFINE_STD_SET_I18N
|
|
|
|
|
|
static gint Interlace; /* For compression code */
|
|
|
|
|
|
static void
|
|
gif_class_init (GifClass *klass)
|
|
{
|
|
PikaPlugInClass *plug_in_class = PIKA_PLUG_IN_CLASS (klass);
|
|
|
|
plug_in_class->query_procedures = gif_query_procedures;
|
|
plug_in_class->create_procedure = gif_create_procedure;
|
|
plug_in_class->set_i18n = STD_SET_I18N;
|
|
}
|
|
|
|
static void
|
|
gif_init (Gif *gif)
|
|
{
|
|
}
|
|
|
|
static GList *
|
|
gif_query_procedures (PikaPlugIn *plug_in)
|
|
{
|
|
return g_list_append (NULL, g_strdup (SAVE_PROC));
|
|
}
|
|
|
|
static PikaProcedure *
|
|
gif_create_procedure (PikaPlugIn *plug_in,
|
|
const gchar *name)
|
|
{
|
|
PikaProcedure *procedure = NULL;
|
|
|
|
if (! strcmp (name, SAVE_PROC))
|
|
{
|
|
procedure = pika_save_procedure_new (plug_in, name,
|
|
PIKA_PDB_PROC_TYPE_PLUGIN,
|
|
FALSE, gif_save, NULL, NULL);
|
|
|
|
pika_procedure_set_image_types (procedure, "INDEXED*, GRAY*");
|
|
|
|
pika_procedure_set_menu_label (procedure, _("GIF image"));
|
|
pika_file_procedure_set_format_name (PIKA_FILE_PROCEDURE (procedure),
|
|
_("GIF"));
|
|
|
|
pika_procedure_set_documentation (procedure,
|
|
_("exports files in GIF "
|
|
"file format"),
|
|
/* xgettext: no-c-format */
|
|
_("Export a file in GIF "
|
|
"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 "
|
|
"'pika-comment' parasite."),
|
|
name);
|
|
pika_procedure_set_attribution (procedure,
|
|
"Spencer Kimball, Peter Mattis, "
|
|
"Adam Moss, David Koblas",
|
|
"Spencer Kimball, Peter Mattis, "
|
|
"Adam Moss, David Koblas",
|
|
"1995-1997");
|
|
|
|
pika_file_procedure_set_handles_remote (PIKA_FILE_PROCEDURE (procedure),
|
|
TRUE);
|
|
pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure),
|
|
"image/gif");
|
|
pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure),
|
|
"gif");
|
|
|
|
PIKA_PROC_ARG_BOOLEAN (procedure, "interlace",
|
|
_("_Interlace"),
|
|
_("Try to export as interlaced"),
|
|
FALSE,
|
|
G_PARAM_READWRITE);
|
|
|
|
PIKA_PROC_ARG_BOOLEAN (procedure, "loop",
|
|
_("Loop _Forever"),
|
|
_("(animated gif) Loop infinitely"),
|
|
TRUE,
|
|
G_PARAM_READWRITE);
|
|
|
|
PIKA_PROC_ARG_INT (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);
|
|
|
|
PIKA_PROC_ARG_INT (procedure, "default-delay",
|
|
_("_Delay between frames when unspecified"),
|
|
_("(animated gif) Default delay between frames "
|
|
"in milliseconds"),
|
|
0, G_MAXINT, 100,
|
|
G_PARAM_READWRITE);
|
|
|
|
PIKA_PROC_ARG_INT (procedure, "default-dispose",
|
|
_("Frame disposal _when unspecified"),
|
|
_("(animated gif) Default disposal type "
|
|
"(0=`don't care`, "
|
|
"1=combine, "
|
|
"2=replace)"),
|
|
0, 2, 0,
|
|
G_PARAM_READWRITE);
|
|
|
|
PIKA_PROC_ARG_BOOLEAN (procedure, "as-animation",
|
|
_("_As animation"),
|
|
_("Export GIF as animation?"),
|
|
FALSE,
|
|
G_PARAM_READWRITE);
|
|
|
|
PIKA_PROC_ARG_BOOLEAN (procedure, "force-delay",
|
|
_("_Use delay entered above for all frames"),
|
|
_("(animated gif) Use specified delay for all frames"),
|
|
FALSE,
|
|
G_PARAM_READWRITE);
|
|
|
|
PIKA_PROC_ARG_BOOLEAN (procedure, "force-dispose",
|
|
_("Use dis_posal entered above "
|
|
"for all frames"),
|
|
_("(animated gif) Use specified disposal for all frames"),
|
|
FALSE,
|
|
G_PARAM_READWRITE);
|
|
|
|
PIKA_PROC_AUX_ARG_BOOLEAN (procedure, "save-comment",
|
|
_("Sa_ve comment"),
|
|
_("Save the image comment in the GIF file"),
|
|
pika_export_comment (),
|
|
G_PARAM_READWRITE);
|
|
|
|
PIKA_PROC_AUX_ARG_STRING (procedure, "pika-comment",
|
|
_("Commen_t"),
|
|
_("Image comment"),
|
|
pika_get_default_comment (),
|
|
G_PARAM_READWRITE);
|
|
|
|
pika_procedure_set_argument_sync (procedure, "pika-comment",
|
|
PIKA_ARGUMENT_SYNC_PARASITE);
|
|
}
|
|
|
|
return procedure;
|
|
}
|
|
|
|
static PikaValueArray *
|
|
gif_save (PikaProcedure *procedure,
|
|
PikaRunMode run_mode,
|
|
PikaImage *image,
|
|
gint n_drawables,
|
|
PikaDrawable **drawables,
|
|
GFile *file,
|
|
PikaMetadata *metadata,
|
|
PikaProcedureConfig *config,
|
|
gpointer run_data)
|
|
{
|
|
PikaPDBStatusType status = PIKA_PDB_SUCCESS;
|
|
PikaExportReturn export = PIKA_EXPORT_CANCEL;
|
|
PikaImage *orig_image;
|
|
PikaImage *sanitized_image = NULL;
|
|
GError *error = NULL;
|
|
|
|
gegl_init (NULL, NULL);
|
|
|
|
orig_image = image;
|
|
|
|
if (run_mode == PIKA_RUN_INTERACTIVE ||
|
|
run_mode == PIKA_RUN_WITH_LAST_VALS)
|
|
pika_ui_init (PLUG_IN_BINARY);
|
|
|
|
status = sanity_check (file, &image, run_mode, &error);
|
|
|
|
/* Get the export options */
|
|
if (status == PIKA_PDB_SUCCESS)
|
|
{
|
|
/* If the sanity check succeeded, the image_ID will point to a
|
|
* duplicate image to delete later.
|
|
*/
|
|
sanitized_image = image;
|
|
|
|
if (run_mode == PIKA_RUN_INTERACTIVE)
|
|
{
|
|
if (! save_dialog (image, procedure, G_OBJECT (config)))
|
|
{
|
|
pika_image_delete (sanitized_image);
|
|
|
|
return pika_procedure_new_return_values (procedure,
|
|
PIKA_PDB_CANCEL,
|
|
NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (status == PIKA_PDB_SUCCESS)
|
|
{
|
|
/* Create an exportable image based on the export options */
|
|
switch (run_mode)
|
|
{
|
|
case PIKA_RUN_INTERACTIVE:
|
|
case PIKA_RUN_WITH_LAST_VALS:
|
|
{
|
|
PikaExportCapabilities capabilities;
|
|
gboolean as_animation;
|
|
|
|
capabilities = (PIKA_EXPORT_CAN_HANDLE_INDEXED |
|
|
PIKA_EXPORT_CAN_HANDLE_GRAY |
|
|
PIKA_EXPORT_CAN_HANDLE_ALPHA);
|
|
|
|
g_object_get (config,
|
|
"as-animation", &as_animation,
|
|
NULL);
|
|
|
|
if (as_animation)
|
|
capabilities |= PIKA_EXPORT_CAN_HANDLE_LAYERS;
|
|
|
|
export = pika_export_image (&image, &n_drawables, &drawables, "GIF",
|
|
capabilities);
|
|
|
|
if (export == PIKA_EXPORT_CANCEL)
|
|
{
|
|
if (sanitized_image)
|
|
pika_image_delete (sanitized_image);
|
|
|
|
return pika_procedure_new_return_values (procedure,
|
|
PIKA_PDB_CANCEL,
|
|
NULL);
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (n_drawables != 1)
|
|
{
|
|
g_set_error (&error, G_FILE_ERROR, 0,
|
|
_("GIF format does not support multiple layers."));
|
|
|
|
return pika_procedure_new_return_values (procedure,
|
|
PIKA_PDB_CALLING_ERROR,
|
|
error);
|
|
}
|
|
|
|
if (! save_image (file, image, drawables[0], orig_image, G_OBJECT (config),
|
|
&error))
|
|
{
|
|
status = PIKA_PDB_EXECUTION_ERROR;
|
|
}
|
|
|
|
pika_image_delete (sanitized_image);
|
|
}
|
|
|
|
if (export == PIKA_EXPORT_EXPORT)
|
|
{
|
|
pika_image_delete (image);
|
|
g_free (drawables);
|
|
}
|
|
|
|
return pika_procedure_new_return_values (procedure, status, error);
|
|
}
|
|
|
|
/* ppmtogif.c - read a portable pixmap and produce a GIF file
|
|
**
|
|
** Based on GIFENCOD by David Rowley <mgardi@watdscu.waterloo.edu>. A
|
|
** Lempel-Ziv compression based on "compress".
|
|
**
|
|
** 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
|
|
*/
|
|
typedef gint (* ifunptr) (gint x,
|
|
gint y);
|
|
|
|
|
|
static gint find_unused_ia_color (const guchar *pixels,
|
|
gint numpixels,
|
|
gint num_indices,
|
|
gint *colors);
|
|
|
|
static void special_flatten_indexed_alpha (guchar *pixels,
|
|
gint transparent,
|
|
gint numpixels);
|
|
|
|
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);
|
|
static void bump_pixel (void);
|
|
|
|
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);
|
|
|
|
static gint rowstride;
|
|
static guchar *pixels;
|
|
static gint cur_progress;
|
|
static gint max_progress;
|
|
|
|
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);
|
|
|
|
|
|
static gint
|
|
find_unused_ia_color (const guchar *pixels,
|
|
gint numpixels,
|
|
gint num_indices,
|
|
gint *colors)
|
|
{
|
|
gboolean ix_used[256];
|
|
gint i;
|
|
|
|
#ifdef GIFDEBUG
|
|
g_printerr ("GIF: fuiac: Image claims to use %d/%d indices - finding free "
|
|
"index...\n", *colors, num_indices);
|
|
#endif
|
|
|
|
for (i = 0; i < 256; i++)
|
|
ix_used[i] = FALSE;
|
|
|
|
for (i = 0; i < numpixels; i++)
|
|
{
|
|
if (pixels[i * 2 + 1])
|
|
ix_used[pixels[i * 2]] = TRUE;
|
|
}
|
|
|
|
for (i = num_indices - 1; i >= 0; i--)
|
|
{
|
|
if (! ix_used[i])
|
|
{
|
|
#ifdef GIFDEBUG
|
|
g_printerr ("GIF: Found unused color index %d.\n", (int) i);
|
|
#endif
|
|
return i;
|
|
}
|
|
}
|
|
|
|
/* 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.
|
|
*/
|
|
if (*colors < 256)
|
|
{
|
|
(*colors)++;
|
|
|
|
g_printerr ("GIF: 2nd pass "
|
|
"- Increasing bounds and using color index %d.\n",
|
|
*colors - 1);
|
|
return ((*colors) - 1);
|
|
}
|
|
|
|
g_message (_("Couldn't simply reduce colors further. Exporting as opaque."));
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
static void
|
|
special_flatten_indexed_alpha (guchar *pixels,
|
|
gint transparent,
|
|
gint numpixels)
|
|
{
|
|
gint i;
|
|
|
|
/* Each transparent pixel in the image is mapped to a uniform value
|
|
* for encoding, if image already has <=255 colors
|
|
*/
|
|
|
|
if (transparent == -1) /* tough, no indices left for the trans. index */
|
|
{
|
|
for (i = 0; i < numpixels; i++)
|
|
pixels[i] = pixels[i * 2];
|
|
}
|
|
else /* make transparent */
|
|
{
|
|
for (i = 0; i < numpixels; i++)
|
|
{
|
|
if (! (pixels[i * 2 + 1] & 128))
|
|
{
|
|
pixels[i] = (guchar) transparent;
|
|
}
|
|
else
|
|
{
|
|
pixels[i] = pixels[i * 2];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static gint
|
|
parse_ms_tag (const gchar *str)
|
|
{
|
|
gint sum = 0;
|
|
gint offset = 0;
|
|
gint length;
|
|
|
|
length = strlen (str);
|
|
|
|
find_another_bra:
|
|
|
|
while ((offset < length) && (str[offset] != '('))
|
|
offset++;
|
|
|
|
if (offset >= length)
|
|
return(-1);
|
|
|
|
if (! g_ascii_isdigit (str[++offset]))
|
|
goto find_another_bra;
|
|
|
|
do
|
|
{
|
|
sum *= 10;
|
|
sum += str[offset] - '0';
|
|
offset++;
|
|
}
|
|
while ((offset < length) && (g_ascii_isdigit (str[offset])));
|
|
|
|
if (length - offset <= 2)
|
|
return(-3);
|
|
|
|
if ((g_ascii_toupper (str[offset]) != 'M') ||
|
|
(g_ascii_toupper (str[offset + 1]) != 'S'))
|
|
return -4;
|
|
|
|
return sum;
|
|
}
|
|
|
|
|
|
static gint
|
|
parse_disposal_tag (const gchar *str,
|
|
gint default_dispose)
|
|
{
|
|
gint offset = 0;
|
|
gint length;
|
|
|
|
length = strlen (str);
|
|
|
|
while ((offset + 9) <= length)
|
|
{
|
|
if (strncmp (&str[offset], "(combine)", 9) == 0)
|
|
return 0x01;
|
|
|
|
if (strncmp (&str[offset], "(replace)", 9) == 0)
|
|
return 0x02 ;
|
|
|
|
offset++;
|
|
}
|
|
|
|
return default_dispose;
|
|
}
|
|
|
|
|
|
static PikaPDBStatusType
|
|
sanity_check (GFile *file,
|
|
PikaImage **image,
|
|
PikaRunMode run_mode,
|
|
GError **error)
|
|
{
|
|
GList *layers;
|
|
GList *list;
|
|
gint image_width;
|
|
gint image_height;
|
|
|
|
image_width = pika_image_get_width (*image);
|
|
image_height = pika_image_get_height (*image);
|
|
|
|
if (image_width > G_MAXUSHORT || image_height > G_MAXUSHORT)
|
|
{
|
|
g_set_error (error, 0, 0,
|
|
_("Unable to export '%s'. "
|
|
"The GIF file format does not support images that are "
|
|
"more than %d pixels wide or tall."),
|
|
pika_file_get_utf8_name (file), G_MAXUSHORT);
|
|
|
|
return PIKA_PDB_EXECUTION_ERROR;
|
|
}
|
|
|
|
/*** Iterate through the layers to make sure they're all ***/
|
|
/*** within the bounds of the image ***/
|
|
|
|
*image = pika_image_duplicate (*image);
|
|
layers = pika_image_list_layers (*image);
|
|
|
|
for (list = layers; list; list = g_list_next (list))
|
|
{
|
|
PikaDrawable *drawable = list->data;
|
|
gint offset_x;
|
|
gint offset_y;
|
|
|
|
pika_drawable_get_offsets (drawable, &offset_x, &offset_y);
|
|
|
|
if (offset_x < 0 ||
|
|
offset_y < 0 ||
|
|
offset_x + pika_drawable_get_width (drawable) > image_width ||
|
|
offset_y + pika_drawable_get_height (drawable) > image_height)
|
|
{
|
|
g_list_free (layers);
|
|
|
|
/* 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
|
|
* the user and they said yes.
|
|
*/
|
|
if ((run_mode == PIKA_RUN_NONINTERACTIVE) || bad_bounds_dialog ())
|
|
{
|
|
pika_image_crop (*image, image_width, image_height, 0, 0);
|
|
return PIKA_PDB_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
pika_image_delete (*image);
|
|
return PIKA_PDB_CANCEL;
|
|
}
|
|
}
|
|
}
|
|
|
|
g_list_free (layers);
|
|
|
|
return PIKA_PDB_SUCCESS;
|
|
}
|
|
|
|
static gboolean
|
|
save_image (GFile *file,
|
|
PikaImage *image,
|
|
PikaDrawable *drawable,
|
|
PikaImage *orig_image,
|
|
GObject *config,
|
|
GError **error)
|
|
{
|
|
GeglBuffer *buffer;
|
|
PikaImageType drawable_type;
|
|
const Babl *format = NULL;
|
|
GOutputStream *output;
|
|
gint Red[MAXCOLORS];
|
|
gint Green[MAXCOLORS];
|
|
gint Blue[MAXCOLORS];
|
|
guchar *cmap;
|
|
guint rows, cols;
|
|
gint BitsPerPixel;
|
|
gint liberalBPP = 0;
|
|
gint useBPP = 0;
|
|
gint colors;
|
|
gint i;
|
|
gint transparent;
|
|
gint offset_x, offset_y;
|
|
|
|
GList *layers;
|
|
GList *list;
|
|
gint nlayers;
|
|
|
|
gboolean is_gif89 = FALSE;
|
|
|
|
gint Delay89;
|
|
gint Disposal;
|
|
gchar *layer_name;
|
|
|
|
PikaRGB background;
|
|
guchar bgred, bggreen, bgblue;
|
|
guchar bgindex = 0;
|
|
guint best_error = 0xFFFFFFFF;
|
|
|
|
gboolean config_interlace;
|
|
gboolean config_loop;
|
|
gint config_number_of_repeats;
|
|
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,
|
|
"interlace", &config_interlace,
|
|
"loop", &config_loop,
|
|
"number-of-repeats", &config_number_of_repeats,
|
|
"default-delay", &config_default_delay,
|
|
"default-dispose", &config_default_dispose,
|
|
"force-delay", &config_use_default_delay,
|
|
"force-dispose", &config_use_default_dispose,
|
|
"as-animation", &config_as_animation,
|
|
"save-comment", &config_save_comment,
|
|
"pika-comment", &config_comment,
|
|
NULL);
|
|
|
|
/* The GIF spec says 7bit ASCII for the comment block. */
|
|
if (config_save_comment && config_comment && strlen (config_comment))
|
|
{
|
|
const gchar *c = config_comment;
|
|
gint len;
|
|
|
|
for (len = strlen (c); len; c++, len--)
|
|
{
|
|
if ((guchar) *c > 127)
|
|
{
|
|
g_message (_("The GIF format only supports comments in "
|
|
"7bit ASCII encoding. No comment is saved."));
|
|
|
|
g_free (config_comment);
|
|
config_comment = NULL;
|
|
|
|
config_save_comment = FALSE;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
config_save_comment = FALSE;
|
|
}
|
|
|
|
/* get a list of layers for this image */
|
|
layers = pika_image_list_layers (image);
|
|
nlayers = g_list_length (layers);
|
|
|
|
drawable_type = pika_drawable_type (layers->data);
|
|
|
|
/* 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.
|
|
*/
|
|
if (nlayers > 1)
|
|
{
|
|
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 == PIKA_GRAY_IMAGE ||
|
|
drawable_type == PIKA_INDEXED_IMAGE)
|
|
{
|
|
for (list = layers; list; list = g_list_next (list))
|
|
{
|
|
PikaImageType dr_type = pika_drawable_type (list->data);
|
|
|
|
if (dr_type == PIKA_GRAYA_IMAGE ||
|
|
dr_type == PIKA_INDEXEDA_IMAGE)
|
|
{
|
|
drawable_type = dr_type;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (config_save_comment)
|
|
is_gif89 = TRUE;
|
|
|
|
switch (drawable_type)
|
|
{
|
|
case PIKA_INDEXEDA_IMAGE:
|
|
is_gif89 = TRUE;
|
|
case PIKA_INDEXED_IMAGE:
|
|
cmap = pika_image_get_colormap (image, NULL, &colors);
|
|
|
|
pika_context_get_background (&background);
|
|
pika_rgb_get_uchar (&background, &bgred, &bggreen, &bgblue);
|
|
|
|
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;
|
|
}
|
|
break;
|
|
case PIKA_GRAYA_IMAGE:
|
|
is_gif89 = TRUE;
|
|
case PIKA_GRAY_IMAGE:
|
|
colors = 256; /* FIXME: Not ideal. */
|
|
for ( i = 0; i < 256; i++)
|
|
{
|
|
Red[i] = Green[i] = Blue[i] = i;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
g_message (_("Cannot export RGB color images. Convert to "
|
|
"indexed color or grayscale first."));
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/* find earliest index in palette which is closest to the background
|
|
* color, and ATTEMPT to use that as the GIF's default background
|
|
* color.
|
|
*/
|
|
for (i = 255; i >= 0; --i)
|
|
{
|
|
guint local_error = 0;
|
|
|
|
local_error += (Red[i] - bgred) * (Red[i] - bgred);
|
|
local_error += (Green[i] - bggreen) * (Green[i] - bggreen);
|
|
local_error += (Blue[i] - bgblue) * (Blue[i] - bgblue);
|
|
|
|
if (local_error <= best_error)
|
|
{
|
|
bgindex = i;
|
|
best_error = local_error;
|
|
}
|
|
}
|
|
|
|
|
|
/* init the progress meter */
|
|
pika_progress_init_printf (_("Exporting '%s'"),
|
|
pika_file_get_utf8_name (file));
|
|
|
|
|
|
/* open the destination file for writing */
|
|
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
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/* write the GIFheader */
|
|
|
|
if (colors < 256)
|
|
{
|
|
/* 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.
|
|
*/
|
|
liberalBPP = BitsPerPixel =
|
|
colors_to_bpp (colors + ((drawable_type==PIKA_INDEXEDA_IMAGE) ? 1 : 0));
|
|
}
|
|
else
|
|
{
|
|
liberalBPP = BitsPerPixel =
|
|
colors_to_bpp (256);
|
|
|
|
if (drawable_type == PIKA_INDEXEDA_IMAGE)
|
|
{
|
|
g_printerr ("GIF: Too many colors?\n");
|
|
}
|
|
}
|
|
|
|
cols = pika_image_get_width (image);
|
|
rows = pika_image_get_height (image);
|
|
Interlace = config_interlace;
|
|
if (! gif_encode_header (output, is_gif89, cols, rows, bgindex,
|
|
BitsPerPixel, Red, Green, Blue, get_pixel,
|
|
error))
|
|
return FALSE;
|
|
|
|
|
|
/* If the image has multiple layers it'll be made into an animated
|
|
* GIF, so write out the looping extension
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/* Write comment extension - mustn't be written before the looping ext. */
|
|
if (config_save_comment && config_comment && strlen (config_comment))
|
|
{
|
|
if (! gif_encode_comment_ext (output, config_comment, error))
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/*** Now for each layer in the image, save an image in a compound GIF ***/
|
|
/************************************************************************/
|
|
|
|
cur_progress = 0;
|
|
max_progress = nlayers * rows;
|
|
|
|
layers = g_list_reverse (layers);
|
|
|
|
for (list = layers, i = nlayers - 1;
|
|
list && i >= 0;
|
|
list = g_list_next (list), i--, cur_progress = (nlayers - i) * rows)
|
|
{
|
|
PikaDrawable *drawable = list->data;
|
|
|
|
drawable_type = pika_drawable_type (drawable);
|
|
if (drawable_type == PIKA_GRAYA_IMAGE)
|
|
{
|
|
format = babl_format ("Y'A u8");
|
|
}
|
|
else if (drawable_type == PIKA_GRAY_IMAGE)
|
|
{
|
|
format = babl_format ("Y' u8");
|
|
}
|
|
|
|
buffer = pika_drawable_get_buffer (drawable);
|
|
pika_drawable_get_offsets (drawable, &offset_x, &offset_y);
|
|
cols = pika_drawable_get_width (drawable);
|
|
rows = pika_drawable_get_height (drawable);
|
|
rowstride = cols;
|
|
|
|
pixels = g_new (guchar, (cols * rows *
|
|
(((drawable_type == PIKA_INDEXEDA_IMAGE) ||
|
|
(drawable_type == PIKA_GRAYA_IMAGE)) ? 2 : 1)));
|
|
|
|
gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, cols, rows), 1.0,
|
|
format, pixels,
|
|
GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
|
|
|
|
/* sort out whether we need to do transparency jiggery-pokery */
|
|
if ((drawable_type == PIKA_INDEXEDA_IMAGE) ||
|
|
(drawable_type == PIKA_GRAYA_IMAGE))
|
|
{
|
|
/* Try to find an entry which isn't actually used in the
|
|
* image, for a transparency index.
|
|
*/
|
|
|
|
transparent =
|
|
find_unused_ia_color (pixels,
|
|
cols * rows,
|
|
bpp_to_colors (colors_to_bpp (colors)),
|
|
&colors);
|
|
|
|
special_flatten_indexed_alpha (pixels,
|
|
transparent,
|
|
cols * rows);
|
|
}
|
|
else
|
|
{
|
|
transparent = -1;
|
|
}
|
|
|
|
BitsPerPixel = colors_to_bpp (colors);
|
|
|
|
if (BitsPerPixel != liberalBPP)
|
|
{
|
|
/* 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...
|
|
*/
|
|
#ifdef GIFDEBUG
|
|
static gboolean onceonly = FALSE;
|
|
|
|
if (! onceonly)
|
|
{
|
|
g_warning ("Promised %d bpp, pondered writing chunk with %d bpp!",
|
|
liberalBPP, BitsPerPixel);
|
|
onceonly = TRUE;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
useBPP = (BitsPerPixel > liberalBPP) ? BitsPerPixel : liberalBPP;
|
|
|
|
if (is_gif89)
|
|
{
|
|
if (i > 0 && ! config_use_default_dispose)
|
|
{
|
|
layer_name = pika_item_get_name (list->next->data);
|
|
Disposal = parse_disposal_tag (layer_name,
|
|
config_default_dispose);
|
|
g_free (layer_name);
|
|
}
|
|
else
|
|
{
|
|
Disposal = config_default_dispose;
|
|
}
|
|
|
|
layer_name = pika_item_get_name (PIKA_ITEM (drawable));
|
|
Delay89 = parse_ms_tag (layer_name);
|
|
g_free (layer_name);
|
|
|
|
if (Delay89 < 0 || config_use_default_delay)
|
|
Delay89 = (config_default_delay + 5) / 10;
|
|
else
|
|
Delay89 = (Delay89 + 5) / 10;
|
|
|
|
/* don't allow a CPU-sucking completely 0-delay looping anim */
|
|
if ((nlayers > 1) && config_loop && (Delay89 == 0))
|
|
{
|
|
static gboolean onceonly = FALSE;
|
|
|
|
if (! onceonly)
|
|
{
|
|
g_message (_("Delay inserted to prevent evil "
|
|
"CPU-sucking animation."));
|
|
onceonly = TRUE;
|
|
}
|
|
|
|
Delay89 = 1;
|
|
}
|
|
|
|
if (! gif_encode_graphic_control_ext (output,
|
|
Disposal, Delay89, nlayers,
|
|
cols, rows,
|
|
transparent,
|
|
useBPP,
|
|
get_pixel,
|
|
error))
|
|
return FALSE;
|
|
}
|
|
|
|
if (! gif_encode_image_data (output, cols, rows,
|
|
(rows > 4) ? config_interlace : 0,
|
|
useBPP,
|
|
get_pixel,
|
|
offset_x, offset_y,
|
|
error))
|
|
return FALSE;
|
|
|
|
pika_progress_update (1.0);
|
|
|
|
g_object_unref (buffer);
|
|
|
|
g_free (pixels);
|
|
}
|
|
|
|
g_list_free (layers);
|
|
|
|
if (! gif_encode_close (output, error))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
bad_bounds_dialog (void)
|
|
{
|
|
GtkWidget *dialog;
|
|
gboolean crop;
|
|
|
|
dialog = gtk_message_dialog_new (NULL, 0,
|
|
GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE,
|
|
_("The image you are trying to export as a "
|
|
"GIF contains layers which extend beyond "
|
|
"the actual borders of the image."));
|
|
|
|
gtk_dialog_add_buttons (GTK_DIALOG (dialog),
|
|
_("_Cancel"), GTK_RESPONSE_CANCEL,
|
|
_("Cr_op"), GTK_RESPONSE_OK,
|
|
NULL);
|
|
|
|
pika_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
|
|
GTK_RESPONSE_OK,
|
|
GTK_RESPONSE_CANCEL,
|
|
-1);
|
|
|
|
pika_window_set_transient (GTK_WINDOW (dialog));
|
|
|
|
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, "
|
|
"or cancel this export."));
|
|
|
|
gtk_widget_show (dialog);
|
|
|
|
crop = (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK);
|
|
|
|
gtk_widget_destroy (dialog);
|
|
|
|
return crop;
|
|
}
|
|
|
|
static gboolean
|
|
save_dialog (PikaImage *image,
|
|
PikaProcedure *procedure,
|
|
GObject *config)
|
|
{
|
|
GtkWidget *dialog;
|
|
GtkWidget *vbox;
|
|
GtkWidget *text_view;
|
|
GtkTextBuffer *text_buffer;
|
|
gint32 n_layers;
|
|
gboolean animation_supported;
|
|
gboolean run;
|
|
|
|
g_free (pika_image_get_layers (image, &n_layers));
|
|
|
|
animation_supported = n_layers > 1;
|
|
|
|
dialog = pika_save_procedure_dialog_new (PIKA_SAVE_PROCEDURE (procedure),
|
|
PIKA_PROCEDURE_CONFIG (config),
|
|
image);
|
|
|
|
#define MAX_COMMENT 240
|
|
|
|
text_view = pika_procedure_dialog_get_widget (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"pika-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));
|
|
|
|
pika_procedure_dialog_fill_scrolled_window (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"comment-scrolled",
|
|
"pika-comment");
|
|
|
|
pika_procedure_dialog_fill_frame (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"comment-frame", "save-comment", FALSE,
|
|
"comment-scrolled");
|
|
|
|
pika_procedure_dialog_fill (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"interlace", "comment-frame", NULL);
|
|
|
|
if (animation_supported)
|
|
{
|
|
GtkWidget *grid;
|
|
GtkWidget *widget;
|
|
GtkListStore *store;
|
|
|
|
grid = gtk_grid_new ();
|
|
gtk_grid_set_column_spacing (GTK_GRID (grid), 1);
|
|
gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
|
|
gtk_widget_show (grid);
|
|
|
|
widget = pika_procedure_dialog_get_widget (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"number-of-repeats",
|
|
PIKA_TYPE_LABEL_SPIN);
|
|
gtk_grid_attach (GTK_GRID (grid), widget, 0, 0, 1, 1);
|
|
gtk_widget_set_visible (widget, TRUE);
|
|
|
|
widget = pika_procedure_dialog_get_widget (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"loop", GTK_TYPE_CHECK_BUTTON);
|
|
gtk_grid_attach (GTK_GRID (grid), widget, 1, 0, 1, 1);
|
|
|
|
pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"number-of-repeats", TRUE,
|
|
config, "loop",
|
|
TRUE);
|
|
|
|
widget = pika_procedure_dialog_get_widget (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"default-delay",
|
|
PIKA_TYPE_LABEL_SPIN);
|
|
gtk_grid_attach (GTK_GRID (grid), widget, 0, 1, 1, 1);
|
|
gtk_widget_set_visible (widget, TRUE);
|
|
|
|
widget = pika_procedure_dialog_get_label (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"milliseconds",
|
|
_("milliseconds"),
|
|
FALSE, FALSE);
|
|
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);
|
|
|
|
store = pika_int_store_new (_("I don't care"),
|
|
DISPOSE_UNSPECIFIED,
|
|
_("Cumulative layers (combine)"),
|
|
DISPOSE_COMBINE,
|
|
_("One frame per layer (replace)"),
|
|
DISPOSE_REPLACE,
|
|
NULL);
|
|
widget = pika_procedure_dialog_get_int_combo (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"default-dispose",
|
|
PIKA_INT_STORE (store));
|
|
gtk_grid_attach (GTK_GRID (grid), widget, 0, 2, 2, 1);
|
|
gtk_widget_set_visible (widget, TRUE);
|
|
|
|
vbox = pika_procedure_dialog_fill_box (PIKA_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);
|
|
|
|
pika_procedure_dialog_fill_frame (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"ani-frame", "as-animation", FALSE,
|
|
"animation-vbox");
|
|
|
|
pika_procedure_dialog_fill (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"ani-frame", NULL);
|
|
}
|
|
else
|
|
{
|
|
GtkWidget *hint;
|
|
|
|
/* Used to create vbox to store hintbox in */
|
|
pika_procedure_dialog_get_label (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"spacer", " ",
|
|
FALSE, FALSE);
|
|
pika_procedure_dialog_get_label (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"no-ani-title", _("Animated GIF"),
|
|
FALSE, FALSE);
|
|
|
|
vbox = pika_procedure_dialog_fill_box (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"no-animation-vbox", "spacer",
|
|
NULL);
|
|
|
|
hint = pika_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."));
|
|
gtk_box_pack_start (GTK_BOX (vbox), hint, FALSE, FALSE, 0);
|
|
gtk_widget_set_margin_bottom (vbox, 12);
|
|
gtk_widget_show (hint);
|
|
gtk_box_reorder_child (GTK_BOX (vbox), hint, 0);
|
|
|
|
pika_procedure_dialog_fill_frame (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"no-ani-frame", "no-ani-title", FALSE,
|
|
"no-animation-vbox");
|
|
|
|
pika_procedure_dialog_fill (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"no-ani-frame", NULL);
|
|
}
|
|
|
|
gtk_widget_show (dialog);
|
|
|
|
run = pika_procedure_dialog_run (PIKA_PROCEDURE_DIALOG (dialog));
|
|
|
|
gtk_widget_destroy (dialog);
|
|
|
|
return run;
|
|
}
|
|
|
|
static int
|
|
colors_to_bpp (gint colors)
|
|
{
|
|
gint bpp;
|
|
|
|
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
|
|
{
|
|
g_warning ("GIF: colors_to_bpp - Eep! too many colors: %d\n", colors);
|
|
return 8;
|
|
}
|
|
|
|
return bpp;
|
|
}
|
|
|
|
static int
|
|
bpp_to_colors (gint bpp)
|
|
{
|
|
gint colors;
|
|
|
|
if (bpp > 8)
|
|
{
|
|
g_warning ("GIF: bpp_to_colors - Eep! bpp==%d !\n", bpp);
|
|
return 256;
|
|
}
|
|
|
|
colors = 1 << bpp;
|
|
|
|
return colors;
|
|
}
|
|
|
|
|
|
|
|
static gint
|
|
get_pixel (gint x,
|
|
gint y)
|
|
{
|
|
return *(pixels + (rowstride * (long) y) + (long) x);
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* GIFENCODE.C - GIF Image compression interface
|
|
*
|
|
* GIFEncode( FName, GHeight, GWidth, GInterlace, Background, Transparent,
|
|
* BitsPerPixel, Red, Green, Blue, get_pixel )
|
|
*
|
|
*****************************************************************************/
|
|
|
|
static gint Width, Height;
|
|
static gint curx, cury;
|
|
static glong CountDown;
|
|
static gint Pass = 0;
|
|
|
|
/*
|
|
* Bump the 'curx' and 'cury' to point to the next pixel
|
|
*/
|
|
static void
|
|
bump_pixel (void)
|
|
{
|
|
/*
|
|
* Bump the current X position
|
|
*/
|
|
curx++;
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
cur_progress++;
|
|
|
|
if ((cur_progress % 20) == 0)
|
|
pika_progress_update ((gdouble) cur_progress / (gdouble) max_progress);
|
|
|
|
curx = 0;
|
|
|
|
if (! Interlace)
|
|
++cury;
|
|
else
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Return the next pixel from the image
|
|
*/
|
|
static gint
|
|
gif_next_pixel (ifunptr getpixel)
|
|
{
|
|
gint r;
|
|
|
|
if (CountDown == 0)
|
|
return EOF;
|
|
|
|
--CountDown;
|
|
|
|
r = (*getpixel) (curx, cury);
|
|
|
|
bump_pixel ();
|
|
|
|
return r;
|
|
}
|
|
|
|
/* public */
|
|
|
|
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)
|
|
{
|
|
gint B;
|
|
gint RWidth, RHeight;
|
|
gint Resolution;
|
|
gint ColorMapSize;
|
|
gint i;
|
|
|
|
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
|
|
*/
|
|
if (! put_string (output, gif89 ? "GIF89a" : "GIF87a", error))
|
|
return FALSE;
|
|
|
|
/*
|
|
* Write out the screen width and height
|
|
*/
|
|
if (! put_word (output, RWidth, error) ||
|
|
! put_word (output, RHeight, error))
|
|
return FALSE;
|
|
|
|
/*
|
|
* Indicate that there is a global color map
|
|
*/
|
|
B = 0x80; /* Yes, there is a color map */
|
|
|
|
/*
|
|
* OR in the resolution
|
|
*/
|
|
B |= (Resolution - 1) << 5;
|
|
|
|
/*
|
|
* OR in the Bits per Pixel
|
|
*/
|
|
B |= (BitsPerPixel - 1);
|
|
|
|
/*
|
|
* Write it out
|
|
*/
|
|
if (! put_byte (output, B, error))
|
|
return FALSE;
|
|
|
|
/*
|
|
* Write out the Background color
|
|
*/
|
|
if (! put_byte (output, Background, error))
|
|
return FALSE;
|
|
|
|
/*
|
|
* Byte of 0's (future expansion)
|
|
*/
|
|
if (! put_byte (output, 0, error))
|
|
return FALSE;
|
|
|
|
/*
|
|
* Write out the Global Color Map
|
|
*/
|
|
for (i = 0; i < ColorMapSize; i++)
|
|
{
|
|
if (! put_byte (output, Red[i], error) ||
|
|
! put_byte (output, Green[i], error) ||
|
|
! put_byte (output, Blue[i], error))
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
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)
|
|
{
|
|
Width = GWidth;
|
|
Height = GHeight;
|
|
|
|
/*
|
|
* 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 out extension for transparent color index, if necessary.
|
|
*/
|
|
if ( (Transparent >= 0) || (NumFramesInImage > 1) )
|
|
{
|
|
/* Extension Introducer - fixed. */
|
|
if (! put_byte (output, '!', error))
|
|
return FALSE;
|
|
|
|
/* Graphic Control Label - fixed. */
|
|
if (! put_byte (output, 0xf9, error))
|
|
return FALSE;
|
|
|
|
/* Block Size - fixed. */
|
|
if (! put_byte (output, 4, error))
|
|
return FALSE;
|
|
|
|
/* Packed Fields - XXXdddut (d=disposal, u=userInput, t=transFlag) */
|
|
/* s8421 */
|
|
if (! put_byte (output,
|
|
((Transparent >= 0) ? 0x01 : 0x00) /* TRANSPARENCY */
|
|
|
|
/* DISPOSAL */
|
|
| ((NumFramesInImage > 1) ? (Disposal << 2) : 0x00 ),
|
|
/* 0x03 or 0x01 build frames cumulatively */
|
|
/* 0x02 clears frame before drawing */
|
|
/* 0x00 'don't care' */
|
|
|
|
error))
|
|
return FALSE;
|
|
|
|
if (! put_word (output, Delay89, error))
|
|
return FALSE;
|
|
|
|
if (! put_byte (output, Transparent, error) ||
|
|
! put_byte (output, 0, error))
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
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)
|
|
{
|
|
gint LeftOfs, TopOfs;
|
|
gint InitCodeSize;
|
|
|
|
Interlace = GInterlace;
|
|
|
|
Width = GWidth;
|
|
Height = GHeight;
|
|
LeftOfs = (gint) offset_x;
|
|
TopOfs = (gint) offset_y;
|
|
|
|
/*
|
|
* Calculate number of bits we are expecting
|
|
*/
|
|
CountDown = (long) Width * (long) Height;
|
|
|
|
/*
|
|
* 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
|
|
*/
|
|
if (! put_byte (output, ',', error))
|
|
return FALSE;
|
|
|
|
/*
|
|
* Write the Image header
|
|
*/
|
|
|
|
if (! put_word (output, LeftOfs, error) ||
|
|
! put_word (output, TopOfs, error) ||
|
|
! put_word (output, Width, error) ||
|
|
! put_word (output, Height, error))
|
|
return FALSE;
|
|
|
|
/*
|
|
* Write out whether or not the image is interlaced
|
|
*/
|
|
if (Interlace)
|
|
{
|
|
if (! put_byte (output, 0x40, error))
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
if (! put_byte (output, 0x00, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Write out the initial code size
|
|
*/
|
|
if (! put_byte (output, InitCodeSize, error))
|
|
return FALSE;
|
|
|
|
/*
|
|
* Go and actually compress the data
|
|
*/
|
|
if (! compress (output, InitCodeSize + 1, get_pixel, error))
|
|
return FALSE;
|
|
|
|
/*
|
|
* Write out a Zero-length packet (to end the series)
|
|
*/
|
|
if (! put_byte (output, 0, error))
|
|
return FALSE;
|
|
|
|
#if 0
|
|
/***************************/
|
|
Interlace = GInterlace;
|
|
Width = GWidth;
|
|
Height = GHeight;
|
|
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
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
gif_encode_close (GOutputStream *output,
|
|
GError **error)
|
|
{
|
|
/*
|
|
* Write the GIF file terminator
|
|
*/
|
|
if (! put_byte (output, ';', error))
|
|
return FALSE;
|
|
|
|
/*
|
|
* And close the file
|
|
*/
|
|
return g_output_stream_close (output, NULL, error);
|
|
}
|
|
|
|
|
|
static gboolean
|
|
gif_encode_loop_ext (GOutputStream *output,
|
|
guint num_loops,
|
|
GError **error)
|
|
{
|
|
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));
|
|
|
|
/* NOTE: num_loops == 0 means 'loop infinitely' */
|
|
}
|
|
|
|
|
|
static gboolean
|
|
gif_encode_comment_ext (GOutputStream *output,
|
|
const gchar *comment,
|
|
GError **error)
|
|
{
|
|
if (!comment || !*comment)
|
|
return TRUE;
|
|
|
|
if (strlen (comment) > 240)
|
|
{
|
|
g_printerr ("GIF: warning:"
|
|
"comment too large - comment block not written.\n");
|
|
return TRUE;
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
|
|
/*
|
|
* Write stuff to the GIF file
|
|
*/
|
|
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)
|
|
{
|
|
return g_data_output_stream_put_string (G_DATA_OUTPUT_STREAM (output),
|
|
s, NULL, error);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
*
|
|
* GIFCOMPR.C - GIF Image compression routines
|
|
*
|
|
* Lempel-Ziv compression based on 'compress'. GIF modifications by
|
|
* David Rowley (mgardi@watdcsu.waterloo.edu)
|
|
*
|
|
***************************************************************************/
|
|
|
|
/*
|
|
* General DEFINEs
|
|
*/
|
|
|
|
#define GIF_BITS 12
|
|
|
|
#define HSIZE 5003 /* 80% occupancy */
|
|
|
|
/*
|
|
* 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)
|
|
*
|
|
*/
|
|
|
|
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 */
|
|
static gint maxmaxcode = (gint) 1 << GIF_BITS; /* should NEVER generate this code */
|
|
#ifdef COMPATIBLE /* But wrong! */
|
|
#define MAXCODE(Mn_bits) ((gint) 1 << (Mn_bits) - 1)
|
|
#else /*COMPATIBLE */
|
|
#define MAXCODE(Mn_bits) (((gint) 1 << (Mn_bits)) - 1)
|
|
#endif /*COMPATIBLE */
|
|
|
|
static glong htab[HSIZE];
|
|
static gushort codetab[HSIZE];
|
|
#define HashTabOf(i) htab[i]
|
|
#define CodeTabOf(i) codetab[i]
|
|
|
|
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. */
|
|
|
|
static gint free_ent = 0; /* first unused entry */
|
|
|
|
/*
|
|
* block compression parameters -- after all codes are used up,
|
|
* and compression rate changes, start over.
|
|
*/
|
|
static gint clear_flg = 0;
|
|
|
|
static gint offset;
|
|
static glong in_count = 1; /* length of input */
|
|
static glong out_count = 0; /* # of codes output (for debugging) */
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
|
|
static gint g_init_bits;
|
|
|
|
static gint ClearCode;
|
|
static gint EOFCode;
|
|
|
|
static gulong cur_accum;
|
|
static gint cur_bits;
|
|
|
|
static gulong masks[] =
|
|
{
|
|
0x0000, 0x0001, 0x0003, 0x0007,
|
|
0x000F, 0x001F, 0x003F, 0x007F,
|
|
0x00FF, 0x01FF, 0x03FF, 0x07FF,
|
|
0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF,
|
|
0xFFFF
|
|
};
|
|
|
|
|
|
static gboolean
|
|
compress (GOutputStream *output,
|
|
gint init_bits,
|
|
ifunptr ReadValue,
|
|
GError **error)
|
|
{
|
|
if (FALSE)
|
|
return no_compress (output, init_bits, ReadValue, error);
|
|
else if (FALSE)
|
|
return rle_compress (output, init_bits, ReadValue, error);
|
|
else
|
|
return normal_compress (output, init_bits, ReadValue, error);
|
|
}
|
|
|
|
static gboolean
|
|
no_compress (GOutputStream *output,
|
|
gint init_bits,
|
|
ifunptr ReadValue,
|
|
GError **error)
|
|
{
|
|
glong fcode;
|
|
gint i /* = 0 */ ;
|
|
gint c;
|
|
gint ent;
|
|
gint hsize_reg;
|
|
gint hshift;
|
|
|
|
/*
|
|
* 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();
|
|
|
|
ent = gif_next_pixel (ReadValue);
|
|
|
|
hshift = 0;
|
|
for (fcode = (long) hsize; fcode < 65536L; fcode *= 2L)
|
|
++hshift;
|
|
hshift = 8 - hshift; /* set hash code range bound */
|
|
|
|
hsize_reg = hsize;
|
|
cl_hash ((glong) hsize_reg); /* clear hash table */
|
|
|
|
if (! output_code (output, (gint) ClearCode, error))
|
|
return FALSE;
|
|
|
|
while ((c = gif_next_pixel (ReadValue)) != EOF)
|
|
{
|
|
++in_count;
|
|
|
|
fcode = (long) (((long) c << maxbits) + ent);
|
|
i = (((gint) c << hshift) ^ ent); /* xor hashing */
|
|
|
|
if (! output_code (output, (gint) ent, error))
|
|
return FALSE;
|
|
|
|
++out_count;
|
|
ent = c;
|
|
|
|
if (free_ent < maxmaxcode)
|
|
{
|
|
CodeTabOf (i) = free_ent++; /* code -> hashtable */
|
|
HashTabOf (i) = fcode;
|
|
}
|
|
else
|
|
{
|
|
if (! cl_block (output, error))
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Put out the final code.
|
|
*/
|
|
if (! output_code (output, (gint) ent, error))
|
|
return FALSE;
|
|
|
|
++out_count;
|
|
|
|
if (! output_code (output, (gint) EOFCode, error))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
rle_compress (GOutputStream *output,
|
|
gint init_bits,
|
|
ifunptr ReadValue,
|
|
GError **error)
|
|
{
|
|
glong fcode;
|
|
gint i /* = 0 */ ;
|
|
gint c, last;
|
|
gint ent;
|
|
gint disp;
|
|
gint hsize_reg;
|
|
gint hshift;
|
|
|
|
/*
|
|
* 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 ();
|
|
|
|
last = ent = gif_next_pixel (ReadValue);
|
|
|
|
hshift = 0;
|
|
for (fcode = (long) hsize; fcode < 65536L; fcode *= 2L)
|
|
++hshift;
|
|
hshift = 8 - hshift; /* set hash code range bound */
|
|
|
|
hsize_reg = hsize;
|
|
cl_hash ((glong) hsize_reg); /* clear hash table */
|
|
|
|
if (! output_code (output, (gint) ClearCode, error))
|
|
return FALSE;
|
|
|
|
|
|
while ((c = gif_next_pixel (ReadValue)) != EOF)
|
|
{
|
|
++in_count;
|
|
|
|
fcode = (long) (((long) c << maxbits) + ent);
|
|
i = (((gint) c << hshift) ^ ent); /* xor hashing */
|
|
|
|
|
|
if (last == c) {
|
|
if (HashTabOf (i) == fcode)
|
|
{
|
|
ent = CodeTabOf (i);
|
|
continue;
|
|
}
|
|
else if ((long) HashTabOf (i) < 0) /* empty slot */
|
|
goto nomatch;
|
|
disp = hsize_reg - i; /* secondary hash (after G. Knott) */
|
|
if (i == 0)
|
|
disp = 1;
|
|
probe:
|
|
if ((i -= disp) < 0)
|
|
i += hsize_reg;
|
|
|
|
if (HashTabOf (i) == fcode)
|
|
{
|
|
ent = CodeTabOf (i);
|
|
continue;
|
|
}
|
|
if ((long) HashTabOf (i) > 0)
|
|
goto probe;
|
|
}
|
|
nomatch:
|
|
if (! output_code (output, (gint) ent, error))
|
|
return FALSE;
|
|
|
|
++out_count;
|
|
last = ent = c;
|
|
if (free_ent < maxmaxcode)
|
|
{
|
|
CodeTabOf (i) = free_ent++; /* code -> hashtable */
|
|
HashTabOf (i) = fcode;
|
|
}
|
|
else
|
|
{
|
|
if (! cl_block (output, error))
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Put out the final code.
|
|
*/
|
|
if (! output_code (output, (gint) ent, error))
|
|
return FALSE;
|
|
|
|
++out_count;
|
|
|
|
if (! output_code (output, (gint) EOFCode, error))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
normal_compress (GOutputStream *output,
|
|
gint init_bits,
|
|
ifunptr ReadValue,
|
|
GError **error)
|
|
{
|
|
glong fcode;
|
|
gint i /* = 0 */ ;
|
|
gint c;
|
|
gint ent;
|
|
gint disp;
|
|
gint hsize_reg;
|
|
gint hshift;
|
|
|
|
/*
|
|
* 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();
|
|
|
|
ent = gif_next_pixel (ReadValue);
|
|
|
|
hshift = 0;
|
|
for (fcode = (long) hsize; fcode < 65536L; fcode *= 2L)
|
|
++hshift;
|
|
hshift = 8 - hshift; /* set hash code range bound */
|
|
|
|
hsize_reg = hsize;
|
|
cl_hash ((glong) hsize_reg); /* clear hash table */
|
|
|
|
if (! output_code (output, (gint) ClearCode, error))
|
|
return FALSE;
|
|
|
|
|
|
while ((c = gif_next_pixel (ReadValue)) != EOF)
|
|
{
|
|
++in_count;
|
|
|
|
fcode = (long) (((long) c << maxbits) + ent);
|
|
i = (((gint) c << hshift) ^ ent); /* xor hashing */
|
|
|
|
if (HashTabOf (i) == fcode)
|
|
{
|
|
ent = CodeTabOf (i);
|
|
continue;
|
|
}
|
|
else if ((long) HashTabOf (i) < 0) /* empty slot */
|
|
goto nomatch;
|
|
disp = hsize_reg - i; /* secondary hash (after G. Knott) */
|
|
if (i == 0)
|
|
disp = 1;
|
|
probe:
|
|
if ((i -= disp) < 0)
|
|
i += hsize_reg;
|
|
|
|
if (HashTabOf (i) == fcode)
|
|
{
|
|
ent = CodeTabOf (i);
|
|
continue;
|
|
}
|
|
if ((long) HashTabOf (i) > 0)
|
|
goto probe;
|
|
nomatch:
|
|
if (! output_code (output, (gint) ent, error))
|
|
return FALSE;
|
|
|
|
++out_count;
|
|
ent = c;
|
|
if (free_ent < maxmaxcode)
|
|
{
|
|
CodeTabOf (i) = free_ent++; /* code -> hashtable */
|
|
HashTabOf (i) = fcode;
|
|
}
|
|
else
|
|
{
|
|
if (! cl_block (output, error))
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Put out the final code.
|
|
*/
|
|
if (! output_code (output, (gint) ent, error))
|
|
return FALSE;
|
|
|
|
++out_count;
|
|
|
|
if (! output_code (output, (gint) EOFCode, error))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/*****************************************************************
|
|
* 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.
|
|
*/
|
|
|
|
static gboolean
|
|
output_code (GOutputStream *output,
|
|
gint code,
|
|
GError **error)
|
|
{
|
|
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)
|
|
{
|
|
if (! char_out (output, (guchar) (cur_accum & 0xff), error))
|
|
return FALSE;
|
|
|
|
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)
|
|
{
|
|
maxcode = MAXCODE (n_bits = g_init_bits);
|
|
clear_flg = 0;
|
|
}
|
|
else
|
|
{
|
|
++n_bits;
|
|
if (n_bits == maxbits)
|
|
maxcode = maxmaxcode;
|
|
else
|
|
maxcode = MAXCODE (n_bits);
|
|
}
|
|
}
|
|
|
|
if (code == EOFCode)
|
|
{
|
|
/*
|
|
* At EOF, write the rest of the buffer.
|
|
*/
|
|
while (cur_bits > 0)
|
|
{
|
|
if (! char_out (output, (guchar) (cur_accum & 0xff), error))
|
|
return FALSE;
|
|
|
|
cur_accum >>= 8;
|
|
cur_bits -= 8;
|
|
}
|
|
|
|
if (! char_flush (output, error))
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Clear out the hash table
|
|
*/
|
|
static gboolean
|
|
cl_block (GOutputStream *output,
|
|
GError **error) /* table clear for block compress */
|
|
{
|
|
cl_hash ((glong) hsize);
|
|
free_ent = ClearCode + 2;
|
|
clear_flg = 1;
|
|
|
|
return output_code (output, (gint) ClearCode, error);
|
|
}
|
|
|
|
static void
|
|
cl_hash (glong hsize) /* reset code table */
|
|
{
|
|
glong *htab_p = htab + hsize;
|
|
|
|
long i;
|
|
long m1 = -1;
|
|
|
|
i = hsize - 16;
|
|
do
|
|
{ /* might use Sys V memset(3) here */
|
|
*(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
|
|
char_init (void)
|
|
{
|
|
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.
|
|
*/
|
|
static gboolean
|
|
char_out (GOutputStream *output,
|
|
gint c,
|
|
GError **error)
|
|
{
|
|
accum[a_count++] = c;
|
|
|
|
if (a_count >= 254)
|
|
return char_flush (output, error);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Flush the packet to disk, and reset the accumulator
|
|
*/
|
|
static gboolean
|
|
char_flush (GOutputStream *output,
|
|
GError **error)
|
|
{
|
|
if (a_count > 0)
|
|
{
|
|
if (! put_byte (output, a_count, error))
|
|
return FALSE;
|
|
|
|
if (! g_output_stream_write_all (output, accum, a_count,
|
|
NULL, NULL, error))
|
|
return FALSE;
|
|
|
|
a_count = 0;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|