1424 lines
46 KiB
C
1424 lines
46 KiB
C
/*
|
|
* Animation Optimizer plug-in version 1.1.2
|
|
*
|
|
* (c) Adam D. Moss, 1997-2003
|
|
* adam@gimp.org
|
|
* adam@foxbox.org
|
|
*
|
|
* PIKA - Photo and Image Kooker Application
|
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/*
|
|
#define EXPERIMENTAL_BACKDROP_CODE
|
|
*/
|
|
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include <libpika/pika.h>
|
|
|
|
#include "libpika/stdplugins-intl.h"
|
|
|
|
|
|
#define OPTIMIZE_PROC "plug-in-animationoptimize"
|
|
#define OPTIMIZE_DIFF_PROC "plug-in-animationoptimize-diff"
|
|
#define UNOPTIMIZE_PROC "plug-in-animationunoptimize"
|
|
#define REMOVE_BACKDROP_PROC "plug-in-animation-remove-backdrop"
|
|
#define FIND_BACKDROP_PROC "plug-in-animation-find-backdrop"
|
|
|
|
|
|
typedef enum
|
|
{
|
|
DISPOSE_UNDEFINED = 0x00,
|
|
DISPOSE_COMBINE = 0x01,
|
|
DISPOSE_REPLACE = 0x02
|
|
} DisposeType;
|
|
|
|
|
|
typedef enum
|
|
{
|
|
OPOPTIMIZE = 0L,
|
|
OPUNOPTIMIZE = 1L,
|
|
OPFOREGROUND = 2L,
|
|
OPBACKGROUND = 3L
|
|
} operatingMode;
|
|
|
|
|
|
typedef struct _Optimize Optimize;
|
|
typedef struct _OptimizeClass OptimizeClass;
|
|
|
|
struct _Optimize
|
|
{
|
|
PikaPlugIn parent_instance;
|
|
};
|
|
|
|
struct _OptimizeClass
|
|
{
|
|
PikaPlugInClass parent_class;
|
|
};
|
|
|
|
|
|
#define OPTIMIZE_TYPE (optimize_get_type ())
|
|
#define OPTIMIZE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), OPTIMIZE_TYPE, Optimize))
|
|
|
|
GType optimize_get_type (void) G_GNUC_CONST;
|
|
|
|
static GList * optimize_query_procedures (PikaPlugIn *plug_in);
|
|
static PikaProcedure * optimize_create_procedure (PikaPlugIn *plug_in,
|
|
const gchar *name);
|
|
|
|
static PikaValueArray * optimize_run (PikaProcedure *procedure,
|
|
PikaRunMode run_mode,
|
|
PikaImage *image,
|
|
gint n_drawables,
|
|
PikaDrawable **drawables,
|
|
PikaProcedureConfig *config,
|
|
gpointer run_data);
|
|
|
|
static PikaImage * do_optimizations (PikaRunMode run_mode,
|
|
PikaImage *image,
|
|
gboolean diff_only);
|
|
|
|
/* tag util functions*/
|
|
static gint parse_ms_tag (const gchar *str);
|
|
static DisposeType parse_disposal_tag (const gchar *str);
|
|
static DisposeType get_frame_disposal (guint whichframe);
|
|
static guint32 get_frame_duration (guint whichframe);
|
|
static void remove_disposal_tag (gchar *dest,
|
|
gchar *src);
|
|
static void remove_ms_tag (gchar *dest,
|
|
gchar *src);
|
|
static gboolean is_disposal_tag (const gchar *str,
|
|
DisposeType *disposal,
|
|
gint *taglength);
|
|
static gboolean is_ms_tag (const gchar *str,
|
|
gint *duration,
|
|
gint *taglength);
|
|
|
|
|
|
G_DEFINE_TYPE (Optimize, optimize, PIKA_TYPE_PLUG_IN)
|
|
|
|
PIKA_MAIN (OPTIMIZE_TYPE)
|
|
DEFINE_STD_SET_I18N
|
|
|
|
|
|
/* Global widgets'n'stuff */
|
|
static guint width, height;
|
|
static gint32 total_frames;
|
|
static PikaLayer **layers;
|
|
static guchar pixelstep;
|
|
static guchar *palette;
|
|
static gint ncolors;
|
|
static operatingMode opmode;
|
|
|
|
|
|
static void
|
|
optimize_class_init (OptimizeClass *klass)
|
|
{
|
|
PikaPlugInClass *plug_in_class = PIKA_PLUG_IN_CLASS (klass);
|
|
|
|
plug_in_class->query_procedures = optimize_query_procedures;
|
|
plug_in_class->create_procedure = optimize_create_procedure;
|
|
plug_in_class->set_i18n = STD_SET_I18N;
|
|
}
|
|
|
|
static void
|
|
optimize_init (Optimize *optimize)
|
|
{
|
|
}
|
|
|
|
static GList *
|
|
optimize_query_procedures (PikaPlugIn *plug_in)
|
|
{
|
|
GList *list = NULL;
|
|
|
|
list = g_list_append (list, g_strdup (OPTIMIZE_PROC));
|
|
list = g_list_append (list, g_strdup (OPTIMIZE_DIFF_PROC));
|
|
list = g_list_append (list, g_strdup (UNOPTIMIZE_PROC));
|
|
#ifdef EXPERIMENTAL_BACKDROP_CODE
|
|
list = g_list_append (list, g_strdup (REMOVE_BACKDROP_PROC));
|
|
list = g_list_append (list, g_strdup (FIND_BACKDROP_PROC));
|
|
#endif
|
|
|
|
return list;
|
|
}
|
|
|
|
static PikaProcedure *
|
|
optimize_create_procedure (PikaPlugIn *plug_in,
|
|
const gchar *name)
|
|
{
|
|
PikaProcedure *procedure = NULL;
|
|
|
|
if (! strcmp (name, OPTIMIZE_PROC))
|
|
{
|
|
procedure = pika_image_procedure_new (plug_in, name,
|
|
PIKA_PDB_PROC_TYPE_PLUGIN,
|
|
optimize_run, NULL, NULL);
|
|
|
|
pika_procedure_set_sensitivity_mask (procedure,
|
|
PIKA_PROCEDURE_SENSITIVE_DRAWABLE |
|
|
PIKA_PROCEDURE_SENSITIVE_DRAWABLES |
|
|
PIKA_PROCEDURE_SENSITIVE_NO_DRAWABLES);
|
|
|
|
pika_procedure_set_menu_label (procedure, _("Optimize (for _GIF)"));
|
|
|
|
pika_procedure_set_documentation (procedure,
|
|
_("Modify image to reduce size when "
|
|
"saved as GIF animation"),
|
|
"This procedure applies various "
|
|
"optimizations to a PIKA layer-based "
|
|
"animation in an attempt to reduce the "
|
|
"final file size. If a frame of the"
|
|
"animation can use the 'combine' "
|
|
"mode, this procedure attempts to "
|
|
"maximize the number of ajdacent "
|
|
"pixels having the same color, which"
|
|
"improves the compression for some "
|
|
"image formats such as GIF or MNG.",
|
|
name);
|
|
}
|
|
else if (! strcmp (name, OPTIMIZE_DIFF_PROC))
|
|
{
|
|
procedure = pika_image_procedure_new (plug_in, name,
|
|
PIKA_PDB_PROC_TYPE_PLUGIN,
|
|
optimize_run, NULL, NULL);
|
|
|
|
pika_procedure_set_sensitivity_mask (procedure,
|
|
PIKA_PROCEDURE_SENSITIVE_DRAWABLE |
|
|
PIKA_PROCEDURE_SENSITIVE_DRAWABLES |
|
|
PIKA_PROCEDURE_SENSITIVE_NO_DRAWABLES);
|
|
|
|
pika_procedure_set_menu_label (procedure, _("_Optimize (Difference)"));
|
|
|
|
pika_procedure_set_documentation (procedure,
|
|
_("Reduce file size where "
|
|
"combining layers is possible"),
|
|
"This procedure applies various "
|
|
"optimizations to a PIKA layer-based "
|
|
"animation in an attempt to reduce "
|
|
"the final file size. If a frame of "
|
|
"the animation can use the 'combine' "
|
|
"mode, this procedure uses a simple "
|
|
"difference between the frames.",
|
|
name);
|
|
}
|
|
else if (! strcmp (name, UNOPTIMIZE_PROC))
|
|
{
|
|
procedure = pika_image_procedure_new (plug_in, name,
|
|
PIKA_PDB_PROC_TYPE_PLUGIN,
|
|
optimize_run, NULL, NULL);
|
|
|
|
pika_procedure_set_sensitivity_mask (procedure,
|
|
PIKA_PROCEDURE_SENSITIVE_DRAWABLE |
|
|
PIKA_PROCEDURE_SENSITIVE_DRAWABLES |
|
|
PIKA_PROCEDURE_SENSITIVE_NO_DRAWABLES);
|
|
|
|
pika_procedure_set_menu_label (procedure, _("_Unoptimize"));
|
|
|
|
pika_procedure_set_documentation (procedure,
|
|
_("Remove optimization to make "
|
|
"editing easier"),
|
|
"This procedure 'simplifies' a PIKA "
|
|
"layer-based animation that has been "
|
|
"optimized for animation. This makes "
|
|
"editing the animation much easier.",
|
|
name);
|
|
}
|
|
else if (! strcmp (name, REMOVE_BACKDROP_PROC))
|
|
{
|
|
procedure = pika_image_procedure_new (plug_in, name,
|
|
PIKA_PDB_PROC_TYPE_PLUGIN,
|
|
optimize_run, NULL, NULL);
|
|
|
|
pika_procedure_set_sensitivity_mask (procedure,
|
|
PIKA_PROCEDURE_SENSITIVE_DRAWABLE |
|
|
PIKA_PROCEDURE_SENSITIVE_DRAWABLES |
|
|
PIKA_PROCEDURE_SENSITIVE_NO_DRAWABLES);
|
|
|
|
pika_procedure_set_menu_label (procedure, _("_Remove Backdrop"));
|
|
|
|
pika_procedure_set_documentation (procedure,
|
|
"This procedure attempts to remove "
|
|
"the backdrop from a PIKA layer-based "
|
|
"animation, leaving the foreground "
|
|
"animation over transparency.",
|
|
NULL,
|
|
name);
|
|
}
|
|
else if (! strcmp (name, FIND_BACKDROP_PROC))
|
|
{
|
|
procedure = pika_image_procedure_new (plug_in, name,
|
|
PIKA_PDB_PROC_TYPE_PLUGIN,
|
|
optimize_run, NULL, NULL);
|
|
|
|
pika_procedure_set_sensitivity_mask (procedure,
|
|
PIKA_PROCEDURE_SENSITIVE_DRAWABLE |
|
|
PIKA_PROCEDURE_SENSITIVE_DRAWABLES |
|
|
PIKA_PROCEDURE_SENSITIVE_NO_DRAWABLES);
|
|
|
|
pika_procedure_set_menu_label (procedure, _("_Find Backdrop"));
|
|
|
|
pika_procedure_set_documentation (procedure,
|
|
"This procedure attempts to remove "
|
|
"the foreground from a PIKA "
|
|
"layer-based animation, leaving"
|
|
" a one-layered image containing only "
|
|
"the constant backdrop image.",
|
|
NULL,
|
|
name);
|
|
}
|
|
|
|
if (procedure)
|
|
{
|
|
pika_procedure_set_image_types (procedure, "*");
|
|
|
|
pika_procedure_add_menu_path (procedure, "<Image>/Filters/Animation");
|
|
|
|
pika_procedure_set_attribution (procedure,
|
|
"Adam D. Moss <adam@gimp.org>",
|
|
"Adam D. Moss <adam@gimp.org>",
|
|
"1997-2003");
|
|
|
|
PIKA_PROC_VAL_IMAGE (procedure, "result",
|
|
"Result",
|
|
"Resultimg image",
|
|
FALSE,
|
|
G_PARAM_READWRITE);
|
|
}
|
|
|
|
return procedure;
|
|
}
|
|
|
|
static PikaValueArray *
|
|
optimize_run (PikaProcedure *procedure,
|
|
PikaRunMode run_mode,
|
|
PikaImage *image,
|
|
gint n_drawables,
|
|
PikaDrawable **drawables,
|
|
PikaProcedureConfig *config,
|
|
gpointer run_data)
|
|
{
|
|
PikaValueArray *return_vals;
|
|
const gchar *name = pika_procedure_get_name (procedure);
|
|
gboolean diff_only = FALSE;
|
|
|
|
gegl_init (NULL, NULL);
|
|
|
|
if (! strcmp (name, OPTIMIZE_PROC))
|
|
{
|
|
opmode = OPOPTIMIZE;
|
|
}
|
|
else if (! strcmp (name, OPTIMIZE_DIFF_PROC))
|
|
{
|
|
opmode = OPOPTIMIZE;
|
|
diff_only = TRUE;
|
|
}
|
|
else if (! strcmp (name, UNOPTIMIZE_PROC))
|
|
{
|
|
opmode = OPUNOPTIMIZE;
|
|
}
|
|
else if (strcmp (name, FIND_BACKDROP_PROC))
|
|
{
|
|
opmode = OPBACKGROUND;
|
|
}
|
|
else if (strcmp (name, REMOVE_BACKDROP_PROC))
|
|
{
|
|
opmode = OPFOREGROUND;
|
|
}
|
|
|
|
image = do_optimizations (run_mode, image, diff_only);
|
|
|
|
if (run_mode != PIKA_RUN_NONINTERACTIVE)
|
|
pika_displays_flush ();
|
|
|
|
return_vals = pika_procedure_new_return_values (procedure,
|
|
PIKA_PDB_SUCCESS,
|
|
NULL);
|
|
|
|
PIKA_VALUES_SET_IMAGE (return_vals, 1, image);
|
|
|
|
return return_vals;
|
|
}
|
|
|
|
|
|
|
|
/* Rendering Functions */
|
|
|
|
static void
|
|
total_alpha (guchar *imdata,
|
|
guint32 numpix,
|
|
guchar bytespp)
|
|
{
|
|
/* Set image to total-transparency w/black
|
|
*/
|
|
|
|
memset (imdata, 0, numpix * bytespp);
|
|
}
|
|
|
|
static const Babl *
|
|
get_format (PikaDrawable *drawable)
|
|
{
|
|
if (pika_drawable_is_rgb (drawable))
|
|
{
|
|
if (pika_drawable_has_alpha (drawable))
|
|
return babl_format ("R'G'B'A u8");
|
|
else
|
|
return babl_format ("R'G'B' u8");
|
|
}
|
|
else if (pika_drawable_is_gray (drawable))
|
|
{
|
|
if (pika_drawable_has_alpha (drawable))
|
|
return babl_format ("Y'A u8");
|
|
else
|
|
return babl_format ("Y' u8");
|
|
}
|
|
|
|
return pika_drawable_get_format (drawable);
|
|
}
|
|
|
|
static void
|
|
compose_row (gint frame_num,
|
|
DisposeType dispose,
|
|
gint row_num,
|
|
guchar *dest,
|
|
gint dest_width,
|
|
PikaDrawable *drawable,
|
|
gboolean cleanup)
|
|
{
|
|
static guchar *line_buf = NULL;
|
|
GeglBuffer *src_buffer;
|
|
const Babl *format;
|
|
guchar *srcptr;
|
|
gint rawx, rawy, rawbpp, rawwidth, rawheight;
|
|
gint i;
|
|
gboolean has_alpha;
|
|
|
|
if (cleanup)
|
|
{
|
|
if (line_buf)
|
|
{
|
|
g_free (line_buf);
|
|
line_buf = NULL;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (dispose == DISPOSE_REPLACE)
|
|
{
|
|
total_alpha (dest, dest_width, pixelstep);
|
|
}
|
|
|
|
pika_drawable_get_offsets (drawable, &rawx, &rawy);
|
|
|
|
rawwidth = pika_drawable_get_width (drawable);
|
|
rawheight = pika_drawable_get_height (drawable);
|
|
|
|
/* this frame has nothing to give us for this row; return */
|
|
if (row_num >= rawheight + rawy ||
|
|
row_num < rawy)
|
|
return;
|
|
|
|
format = get_format (drawable);
|
|
|
|
has_alpha = pika_drawable_has_alpha (drawable);
|
|
rawbpp = babl_format_get_bytes_per_pixel (format);
|
|
|
|
if (line_buf)
|
|
{
|
|
g_free (line_buf);
|
|
line_buf = NULL;
|
|
}
|
|
line_buf = g_malloc (rawwidth * rawbpp);
|
|
|
|
/* Initialise and fetch the raw new frame row */
|
|
|
|
src_buffer = pika_drawable_get_buffer (drawable);
|
|
|
|
gegl_buffer_get (src_buffer, GEGL_RECTANGLE (0, row_num - rawy,
|
|
rawwidth, 1), 1.0,
|
|
format, line_buf,
|
|
GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
|
|
|
|
g_object_unref (src_buffer);
|
|
|
|
/* render... */
|
|
|
|
srcptr = line_buf;
|
|
|
|
for (i=rawx; i<rawwidth+rawx; i++)
|
|
{
|
|
if (i>=0 && i<dest_width)
|
|
{
|
|
if ((!has_alpha) || ((*(srcptr+rawbpp-1))&128))
|
|
{
|
|
gint pi;
|
|
|
|
for (pi = 0; pi < pixelstep-1; pi++)
|
|
{
|
|
dest[i*pixelstep +pi] = *(srcptr + pi);
|
|
}
|
|
|
|
dest[i*pixelstep + pixelstep - 1] = 255;
|
|
}
|
|
}
|
|
|
|
srcptr += rawbpp;
|
|
}
|
|
}
|
|
|
|
|
|
static PikaImage *
|
|
do_optimizations (PikaRunMode run_mode,
|
|
PikaImage *image,
|
|
gboolean diff_only)
|
|
{
|
|
PikaImage *new_image;
|
|
PikaImageBaseType imagetype;
|
|
PikaImageType drawabletype_alpha;
|
|
static guchar *rawframe = NULL;
|
|
guchar *srcptr;
|
|
guchar *destptr;
|
|
gint row, this_frame_num;
|
|
guint32 frame_sizebytes;
|
|
PikaLayer *new_layer;
|
|
DisposeType dispose;
|
|
guchar *this_frame = NULL;
|
|
guchar *last_frame = NULL;
|
|
guchar *opti_frame = NULL;
|
|
guchar *back_frame = NULL;
|
|
|
|
gint this_delay;
|
|
gint cumulated_delay = 0;
|
|
PikaLayer *last_true_frame = NULL;
|
|
gint buflen;
|
|
|
|
gchar *oldlayer_name;
|
|
gchar *newlayer_name;
|
|
|
|
gboolean can_combine;
|
|
|
|
gint32 bbox_top, bbox_bottom, bbox_left, bbox_right;
|
|
gint32 rbox_top, rbox_bottom, rbox_left, rbox_right;
|
|
|
|
switch (opmode)
|
|
{
|
|
case OPUNOPTIMIZE:
|
|
pika_progress_init (_("Unoptimizing animation"));
|
|
break;
|
|
case OPFOREGROUND:
|
|
pika_progress_init (_("Removing animation background"));
|
|
break;
|
|
case OPBACKGROUND:
|
|
pika_progress_init (_("Finding animation background"));
|
|
break;
|
|
case OPOPTIMIZE:
|
|
default:
|
|
pika_progress_init (_("Optimizing animation"));
|
|
break;
|
|
}
|
|
|
|
width = pika_image_get_width (image);
|
|
height = pika_image_get_height (image);
|
|
layers = pika_image_get_layers (image, &total_frames);
|
|
imagetype = pika_image_get_base_type (image);
|
|
pixelstep = (imagetype == PIKA_RGB) ? 4 : 2;
|
|
|
|
drawabletype_alpha = (imagetype == PIKA_RGB) ? PIKA_RGBA_IMAGE :
|
|
((imagetype == PIKA_INDEXED) ? PIKA_INDEXEDA_IMAGE : PIKA_GRAYA_IMAGE);
|
|
|
|
frame_sizebytes = width * height * pixelstep;
|
|
|
|
this_frame = g_malloc (frame_sizebytes);
|
|
last_frame = g_malloc (frame_sizebytes);
|
|
opti_frame = g_malloc (frame_sizebytes);
|
|
|
|
if (opmode == OPBACKGROUND ||
|
|
opmode == OPFOREGROUND)
|
|
back_frame = g_malloc (frame_sizebytes);
|
|
|
|
total_alpha (this_frame, width*height, pixelstep);
|
|
total_alpha (last_frame, width*height, pixelstep);
|
|
|
|
new_image = pika_image_new (width, height, imagetype);
|
|
pika_image_undo_disable (new_image);
|
|
|
|
if (imagetype == PIKA_INDEXED)
|
|
{
|
|
palette = pika_image_get_colormap (image, NULL, &ncolors);
|
|
pika_image_set_colormap (new_image, palette, ncolors);
|
|
}
|
|
|
|
#if 1
|
|
if (opmode == OPBACKGROUND ||
|
|
opmode == OPFOREGROUND)
|
|
{
|
|
/* iterate through all rows of all frames, find statistical
|
|
mode for each pixel position. */
|
|
gint i;
|
|
guint j;
|
|
guchar **these_rows;
|
|
guchar **red;
|
|
guchar **green;
|
|
guchar **blue;
|
|
guint **count;
|
|
guint *num_colors;
|
|
|
|
these_rows = g_new (guchar *, total_frames);
|
|
red = g_new (guchar *, total_frames);
|
|
green = g_new (guchar *, total_frames);
|
|
blue = g_new (guchar *, total_frames);
|
|
count = g_new (guint *, total_frames);
|
|
|
|
num_colors = g_new (guint, width);
|
|
|
|
for (this_frame_num=0; this_frame_num<total_frames; this_frame_num++)
|
|
{
|
|
these_rows[this_frame_num] = g_malloc (width * pixelstep);
|
|
|
|
red[this_frame_num] = g_new (guchar, width);
|
|
green[this_frame_num] = g_new (guchar, width);
|
|
blue[this_frame_num] = g_new (guchar, width);
|
|
|
|
count[this_frame_num] = g_new0(guint, width);
|
|
}
|
|
|
|
for (row = 0; row < height; row++)
|
|
{
|
|
memset (num_colors, 0, width * sizeof (guint));
|
|
|
|
for (this_frame_num=0; this_frame_num<total_frames; this_frame_num++)
|
|
{
|
|
PikaDrawable *drawable =
|
|
PIKA_DRAWABLE (layers[total_frames-(this_frame_num+1)]);
|
|
|
|
dispose = get_frame_disposal (this_frame_num);
|
|
|
|
compose_row (this_frame_num,
|
|
dispose,
|
|
row,
|
|
these_rows[this_frame_num],
|
|
width,
|
|
drawable,
|
|
FALSE);
|
|
}
|
|
|
|
for (this_frame_num=0; this_frame_num<total_frames; this_frame_num++)
|
|
{
|
|
for (i=0; i<width; i++)
|
|
{
|
|
if (these_rows[this_frame_num][i * pixelstep + pixelstep -1]
|
|
>= 128)
|
|
{
|
|
for (j=0; j<num_colors[i]; j++)
|
|
{
|
|
|
|
switch (pixelstep)
|
|
{
|
|
case 4:
|
|
if (these_rows[this_frame_num][i * 4 +0] ==
|
|
red[j][i] &&
|
|
these_rows[this_frame_num][i * 4 +1] ==
|
|
green[j][i] &&
|
|
these_rows[this_frame_num][i * 4 +2] ==
|
|
blue[j][i])
|
|
{
|
|
(count[j][i])++;
|
|
goto same;
|
|
}
|
|
break;
|
|
case 2:
|
|
if (these_rows[this_frame_num][i * 2 +0] ==
|
|
red[j][i])
|
|
{
|
|
(count[j][i])++;
|
|
goto same;
|
|
}
|
|
break;
|
|
default:
|
|
g_error ("Eeep!");
|
|
break;
|
|
}
|
|
}
|
|
|
|
count[num_colors[i]][i] = 1;
|
|
red[num_colors[i]][i] =
|
|
these_rows[this_frame_num][i * pixelstep];
|
|
if (pixelstep == 4)
|
|
{
|
|
green[num_colors[i]][i] =
|
|
these_rows[this_frame_num][i * 4 +1];
|
|
blue[num_colors[i]][i] =
|
|
these_rows[this_frame_num][i * 4 +2];
|
|
}
|
|
num_colors[i]++;
|
|
}
|
|
same:
|
|
/* nop */;
|
|
}
|
|
}
|
|
|
|
for (i=0; i<width; i++)
|
|
{
|
|
guint best_count = 0;
|
|
guchar best_r = 255, best_g = 0, best_b = 255;
|
|
|
|
for (j=0; j<num_colors[i]; j++)
|
|
{
|
|
if (count[j][i] > best_count)
|
|
{
|
|
best_count = count[j][i];
|
|
best_r = red[j][i];
|
|
best_g = green[j][i];
|
|
best_b = blue[j][i];
|
|
}
|
|
}
|
|
|
|
back_frame[width * pixelstep * row +i*pixelstep + 0] = best_r;
|
|
if (pixelstep == 4)
|
|
{
|
|
back_frame[width * pixelstep * row +i*pixelstep + 1] =
|
|
best_g;
|
|
back_frame[width * pixelstep * row +i*pixelstep + 2] =
|
|
best_b;
|
|
}
|
|
back_frame[width * pixelstep * row +i*pixelstep +pixelstep-1] =
|
|
(best_count == 0) ? 0 : 255;
|
|
|
|
if (best_count == 0)
|
|
g_warning ("yayyyy!");
|
|
}
|
|
/* memcpy (&back_frame[width * pixelstep * row],
|
|
these_rows[0],
|
|
width * pixelstep);*/
|
|
}
|
|
|
|
for (this_frame_num=0; this_frame_num<total_frames; this_frame_num++)
|
|
{
|
|
g_free (these_rows[this_frame_num]);
|
|
g_free (red[this_frame_num]);
|
|
g_free (green[this_frame_num]);
|
|
g_free (blue[this_frame_num]);
|
|
g_free (count[this_frame_num]);
|
|
}
|
|
|
|
g_free (these_rows);
|
|
g_free (red);
|
|
g_free (green);
|
|
g_free (blue);
|
|
g_free (count);
|
|
g_free (num_colors);
|
|
}
|
|
#endif
|
|
|
|
if (opmode == OPBACKGROUND)
|
|
{
|
|
GeglBuffer *buffer;
|
|
const Babl *format;
|
|
|
|
new_layer = pika_layer_new (new_image,
|
|
"Backgroundx",
|
|
width, height,
|
|
drawabletype_alpha,
|
|
100.0,
|
|
pika_image_get_default_new_layer_mode (new_image));
|
|
|
|
pika_image_insert_layer (new_image, new_layer, NULL, 0);
|
|
|
|
buffer = pika_drawable_get_buffer (PIKA_DRAWABLE (new_layer));
|
|
|
|
format = get_format (PIKA_DRAWABLE (new_layer));
|
|
|
|
gegl_buffer_set (buffer, GEGL_RECTANGLE (0, 0, width, height), 0,
|
|
format, back_frame,
|
|
GEGL_ABYSS_NONE);
|
|
|
|
g_object_unref (buffer);
|
|
}
|
|
else
|
|
{
|
|
for (this_frame_num=0; this_frame_num<total_frames; this_frame_num++)
|
|
{
|
|
/*
|
|
* BUILD THIS FRAME into our 'this_frame' buffer.
|
|
*/
|
|
|
|
PikaDrawable *drawable =
|
|
PIKA_DRAWABLE (layers[total_frames-(this_frame_num+1)]);
|
|
|
|
/* Image has been closed/etc since we got the layer list? */
|
|
/* FIXME - How do we tell if a pika_drawable_get () fails? */
|
|
if (pika_drawable_get_width (drawable) == 0)
|
|
{
|
|
pika_quit ();
|
|
}
|
|
|
|
this_delay = get_frame_duration (this_frame_num);
|
|
dispose = get_frame_disposal (this_frame_num);
|
|
|
|
for (row = 0; row < height; row++)
|
|
{
|
|
compose_row (this_frame_num,
|
|
dispose,
|
|
row,
|
|
&this_frame[pixelstep*width * row],
|
|
width,
|
|
drawable,
|
|
FALSE
|
|
);
|
|
}
|
|
|
|
if (opmode == OPFOREGROUND)
|
|
{
|
|
gint xit, yit, byteit;
|
|
|
|
for (yit=0; yit<height; yit++)
|
|
{
|
|
for (xit=0; xit<width; xit++)
|
|
{
|
|
for (byteit=0; byteit<pixelstep-1; byteit++)
|
|
{
|
|
if (back_frame[yit*width*pixelstep + xit*pixelstep
|
|
+ byteit]
|
|
!=
|
|
this_frame[yit*width*pixelstep + xit*pixelstep
|
|
+ byteit])
|
|
{
|
|
goto enough;
|
|
}
|
|
}
|
|
this_frame[yit*width*pixelstep + xit*pixelstep
|
|
+ pixelstep - 1] = 0;
|
|
enough:
|
|
/* nop */;
|
|
}
|
|
}
|
|
}
|
|
|
|
can_combine = FALSE;
|
|
bbox_left = 0;
|
|
bbox_top = 0;
|
|
bbox_right = width;
|
|
bbox_bottom = height;
|
|
rbox_left = 0;
|
|
rbox_top = 0;
|
|
rbox_right = width;
|
|
rbox_bottom = height;
|
|
|
|
/* copy 'this' frame into a buffer which we can safely molest */
|
|
memcpy (opti_frame, this_frame, frame_sizebytes);
|
|
/*
|
|
*
|
|
* OPTIMIZE HERE!
|
|
*
|
|
*/
|
|
if (
|
|
(this_frame_num != 0) /* Can't delta bottom frame! */
|
|
&& (opmode == OPOPTIMIZE)
|
|
)
|
|
{
|
|
gint xit, yit, byteit;
|
|
|
|
can_combine = TRUE;
|
|
|
|
/*
|
|
* SEARCH FOR BOUNDING BOX
|
|
*/
|
|
bbox_left = width;
|
|
bbox_top = height;
|
|
bbox_right = 0;
|
|
bbox_bottom = 0;
|
|
rbox_left = width;
|
|
rbox_top = height;
|
|
rbox_right = 0;
|
|
rbox_bottom = 0;
|
|
|
|
for (yit=0; yit<height; yit++)
|
|
{
|
|
for (xit=0; xit<width; xit++)
|
|
{
|
|
gboolean keep_pix;
|
|
gboolean opaq_pix;
|
|
|
|
/* Check if 'this' and 'last' are transparent */
|
|
if (!(this_frame[yit*width*pixelstep + xit*pixelstep
|
|
+ pixelstep-1]&128)
|
|
&&
|
|
!(last_frame[yit*width*pixelstep + xit*pixelstep
|
|
+ pixelstep-1]&128))
|
|
{
|
|
keep_pix = FALSE;
|
|
opaq_pix = FALSE;
|
|
goto decided;
|
|
}
|
|
/* Check if just 'this' is transparent */
|
|
if ((last_frame[yit*width*pixelstep + xit*pixelstep
|
|
+ pixelstep-1]&128)
|
|
&&
|
|
!(this_frame[yit*width*pixelstep + xit*pixelstep
|
|
+ pixelstep-1]&128))
|
|
{
|
|
keep_pix = TRUE;
|
|
opaq_pix = FALSE;
|
|
can_combine = FALSE;
|
|
goto decided;
|
|
}
|
|
/* Check if just 'last' is transparent */
|
|
if (!(last_frame[yit*width*pixelstep + xit*pixelstep
|
|
+ pixelstep-1]&128)
|
|
&&
|
|
(this_frame[yit*width*pixelstep + xit*pixelstep
|
|
+ pixelstep-1]&128))
|
|
{
|
|
keep_pix = TRUE;
|
|
opaq_pix = TRUE;
|
|
goto decided;
|
|
}
|
|
/* If 'last' and 'this' are opaque, we have
|
|
* to check if they're the same color - we
|
|
* only have to keep the pixel if 'last' or
|
|
* 'this' are opaque and different.
|
|
*/
|
|
keep_pix = FALSE;
|
|
opaq_pix = TRUE;
|
|
for (byteit=0; byteit<pixelstep-1; byteit++)
|
|
{
|
|
if ((last_frame[yit*width*pixelstep + xit*pixelstep
|
|
+ byteit]
|
|
!=
|
|
this_frame[yit*width*pixelstep + xit*pixelstep
|
|
+ byteit])
|
|
)
|
|
{
|
|
keep_pix = TRUE;
|
|
goto decided;
|
|
}
|
|
}
|
|
decided:
|
|
if (opaq_pix)
|
|
{
|
|
if (xit<rbox_left) rbox_left=xit;
|
|
if (xit>rbox_right) rbox_right=xit;
|
|
if (yit<rbox_top) rbox_top=yit;
|
|
if (yit>rbox_bottom) rbox_bottom=yit;
|
|
}
|
|
if (keep_pix)
|
|
{
|
|
if (xit<bbox_left) bbox_left=xit;
|
|
if (xit>bbox_right) bbox_right=xit;
|
|
if (yit<bbox_top) bbox_top=yit;
|
|
if (yit>bbox_bottom) bbox_bottom=yit;
|
|
}
|
|
else
|
|
{
|
|
/* pixel didn't change this frame - make
|
|
* it transparent in our optimized buffer!
|
|
*/
|
|
opti_frame[yit*width*pixelstep + xit*pixelstep
|
|
+ pixelstep-1] = 0;
|
|
}
|
|
} /* xit */
|
|
} /* yit */
|
|
|
|
if (!can_combine)
|
|
{
|
|
bbox_left = rbox_left;
|
|
bbox_top = rbox_top;
|
|
bbox_right = rbox_right;
|
|
bbox_bottom = rbox_bottom;
|
|
}
|
|
|
|
bbox_right++;
|
|
bbox_bottom++;
|
|
|
|
if (can_combine && !diff_only)
|
|
{
|
|
/* Try to optimize the pixel data for RLE or LZW compression
|
|
* by making some transparent pixels non-transparent if they
|
|
* would have the same color as the adjacent pixels. This
|
|
* gives a better compression if the algorithm compresses
|
|
* the image line by line.
|
|
* See: http://bugzilla.gnome.org/show_bug.cgi?id=66367
|
|
* It may not be very efficient to add two additional passes
|
|
* over the pixels, but this hopefully makes the code easier
|
|
* to maintain and less error-prone.
|
|
*/
|
|
for (yit = bbox_top; yit < bbox_bottom; yit++)
|
|
{
|
|
/* Compare with previous pixels from left to right */
|
|
for (xit = bbox_left + 1; xit < bbox_right; xit++)
|
|
{
|
|
if (!(opti_frame[yit*width*pixelstep
|
|
+ xit*pixelstep
|
|
+ pixelstep-1]&128)
|
|
&& (opti_frame[yit*width*pixelstep
|
|
+ (xit-1)*pixelstep
|
|
+ pixelstep-1]&128)
|
|
&& (last_frame[yit*width*pixelstep
|
|
+ xit*pixelstep
|
|
+ pixelstep-1]&128))
|
|
{
|
|
for (byteit=0; byteit<pixelstep-1; byteit++)
|
|
{
|
|
if (opti_frame[yit*width*pixelstep
|
|
+ (xit-1)*pixelstep
|
|
+ byteit]
|
|
!=
|
|
last_frame[yit*width*pixelstep
|
|
+ xit*pixelstep
|
|
+ byteit])
|
|
{
|
|
goto skip_right;
|
|
}
|
|
}
|
|
/* copy the color and alpha */
|
|
for (byteit=0; byteit<pixelstep; byteit++)
|
|
{
|
|
opti_frame[yit*width*pixelstep
|
|
+ xit*pixelstep
|
|
+ byteit]
|
|
= last_frame[yit*width*pixelstep
|
|
+ xit*pixelstep
|
|
+ byteit];
|
|
}
|
|
}
|
|
skip_right:
|
|
/* nop */;
|
|
} /* xit */
|
|
|
|
/* Compare with next pixels from right to left */
|
|
for (xit = bbox_right - 2; xit >= bbox_left; xit--)
|
|
{
|
|
if (!(opti_frame[yit*width*pixelstep
|
|
+ xit*pixelstep
|
|
+ pixelstep-1]&128)
|
|
&& (opti_frame[yit*width*pixelstep
|
|
+ (xit+1)*pixelstep
|
|
+ pixelstep-1]&128)
|
|
&& (last_frame[yit*width*pixelstep
|
|
+ xit*pixelstep
|
|
+ pixelstep-1]&128))
|
|
{
|
|
for (byteit=0; byteit<pixelstep-1; byteit++)
|
|
{
|
|
if (opti_frame[yit*width*pixelstep
|
|
+ (xit+1)*pixelstep
|
|
+ byteit]
|
|
!=
|
|
last_frame[yit*width*pixelstep
|
|
+ xit*pixelstep
|
|
+ byteit])
|
|
{
|
|
goto skip_left;
|
|
}
|
|
}
|
|
/* copy the color and alpha */
|
|
for (byteit=0; byteit<pixelstep; byteit++)
|
|
{
|
|
opti_frame[yit*width*pixelstep
|
|
+ xit*pixelstep
|
|
+ byteit]
|
|
= last_frame[yit*width*pixelstep
|
|
+ xit*pixelstep
|
|
+ byteit];
|
|
}
|
|
}
|
|
skip_left:
|
|
/* nop */;
|
|
} /* xit */
|
|
} /* yit */
|
|
}
|
|
|
|
/*
|
|
* Collapse opti_frame data down such that the data
|
|
* which occupies the bounding box sits at the start
|
|
* of the data (for convenience with ..set_rect ()).
|
|
*/
|
|
destptr = opti_frame;
|
|
/*
|
|
* If can_combine, then it's safe to use our optimized
|
|
* alpha information. Otherwise, an opaque pixel became
|
|
* transparent this frame, and we'll have to use the
|
|
* actual true frame's alpha.
|
|
*/
|
|
if (can_combine)
|
|
srcptr = opti_frame;
|
|
else
|
|
srcptr = this_frame;
|
|
for (yit=bbox_top; yit<bbox_bottom; yit++)
|
|
{
|
|
for (xit=bbox_left; xit<bbox_right; xit++)
|
|
{
|
|
for (byteit=0; byteit<pixelstep; byteit++)
|
|
{
|
|
*(destptr++) = srcptr[yit*pixelstep*width +
|
|
pixelstep*xit + byteit];
|
|
}
|
|
}
|
|
}
|
|
} /* !bot frame? */
|
|
else
|
|
{
|
|
memcpy (opti_frame, this_frame, frame_sizebytes);
|
|
}
|
|
|
|
/*
|
|
*
|
|
* REMEMBER THE ANIMATION STATUS TO DELTA AGAINST NEXT TIME
|
|
*
|
|
*/
|
|
memcpy (last_frame, this_frame, frame_sizebytes);
|
|
|
|
|
|
/*
|
|
*
|
|
* PUT THIS FRAME INTO A NEW LAYER IN THE NEW IMAGE
|
|
*
|
|
*/
|
|
|
|
oldlayer_name =
|
|
pika_item_get_name (PIKA_ITEM (layers[total_frames-(this_frame_num+1)]));
|
|
|
|
buflen = strlen (oldlayer_name) + 40;
|
|
|
|
newlayer_name = g_malloc (buflen);
|
|
|
|
remove_disposal_tag (newlayer_name, oldlayer_name);
|
|
g_free (oldlayer_name);
|
|
|
|
oldlayer_name = g_malloc (buflen);
|
|
|
|
remove_ms_tag (oldlayer_name, newlayer_name);
|
|
|
|
g_snprintf (newlayer_name, buflen, "%s(%dms)%s",
|
|
oldlayer_name, this_delay,
|
|
(this_frame_num == 0) ? "" :
|
|
can_combine ? "(combine)" : "(replace)");
|
|
|
|
g_free (oldlayer_name);
|
|
|
|
/* Empty frame! */
|
|
if (bbox_right <= bbox_left ||
|
|
bbox_bottom <= bbox_top)
|
|
{
|
|
cumulated_delay += this_delay;
|
|
|
|
g_free (newlayer_name);
|
|
|
|
oldlayer_name = pika_item_get_name (PIKA_ITEM (last_true_frame));
|
|
|
|
buflen = strlen (oldlayer_name) + 40;
|
|
|
|
newlayer_name = g_malloc (buflen);
|
|
|
|
remove_disposal_tag (newlayer_name, oldlayer_name);
|
|
g_free (oldlayer_name);
|
|
|
|
oldlayer_name = g_malloc (buflen);
|
|
|
|
remove_ms_tag (oldlayer_name, newlayer_name);
|
|
|
|
g_snprintf (newlayer_name, buflen, "%s(%dms)%s",
|
|
oldlayer_name, cumulated_delay,
|
|
(this_frame_num == 0) ? "" :
|
|
can_combine ? "(combine)" : "(replace)");
|
|
|
|
pika_item_set_name (PIKA_ITEM (last_true_frame), newlayer_name);
|
|
|
|
g_free (newlayer_name);
|
|
g_free (oldlayer_name);
|
|
}
|
|
else
|
|
{
|
|
GeglBuffer *buffer;
|
|
const Babl *format;
|
|
|
|
cumulated_delay = this_delay;
|
|
|
|
last_true_frame =
|
|
new_layer = pika_layer_new (new_image,
|
|
newlayer_name,
|
|
bbox_right-bbox_left,
|
|
bbox_bottom-bbox_top,
|
|
drawabletype_alpha,
|
|
100.0,
|
|
pika_image_get_default_new_layer_mode (new_image));
|
|
g_free (newlayer_name);
|
|
|
|
pika_image_insert_layer (new_image, new_layer, NULL, 0);
|
|
|
|
buffer = pika_drawable_get_buffer (PIKA_DRAWABLE (new_layer));
|
|
|
|
format = get_format (PIKA_DRAWABLE (new_layer));
|
|
|
|
gegl_buffer_set (buffer,
|
|
GEGL_RECTANGLE (0, 0,
|
|
bbox_right-bbox_left,
|
|
bbox_bottom-bbox_top), 0,
|
|
format, opti_frame,
|
|
GEGL_AUTO_ROWSTRIDE);
|
|
|
|
g_object_unref (buffer);
|
|
pika_item_transform_translate (PIKA_ITEM (new_layer), bbox_left, bbox_top);
|
|
}
|
|
|
|
pika_progress_update (((gdouble) this_frame_num + 1.0) /
|
|
((gdouble) total_frames));
|
|
}
|
|
|
|
pika_progress_update (1.0);
|
|
}
|
|
|
|
pika_image_undo_enable (new_image);
|
|
|
|
if (run_mode != PIKA_RUN_NONINTERACTIVE)
|
|
pika_display_new (new_image);
|
|
|
|
g_free (rawframe);
|
|
rawframe = NULL;
|
|
|
|
g_free (last_frame);
|
|
last_frame = NULL;
|
|
|
|
g_free (this_frame);
|
|
this_frame = NULL;
|
|
|
|
g_free (opti_frame);
|
|
opti_frame = NULL;
|
|
|
|
g_free (back_frame);
|
|
back_frame = NULL;
|
|
|
|
return new_image;
|
|
}
|
|
|
|
/* Util. */
|
|
|
|
static DisposeType
|
|
get_frame_disposal (guint whichframe)
|
|
{
|
|
gchar *layer_name;
|
|
DisposeType disposal;
|
|
|
|
layer_name = pika_item_get_name (PIKA_ITEM (layers[total_frames-(whichframe+1)]));
|
|
disposal = parse_disposal_tag (layer_name);
|
|
g_free (layer_name);
|
|
|
|
return disposal;
|
|
}
|
|
|
|
static guint32
|
|
get_frame_duration (guint whichframe)
|
|
{
|
|
gchar* layer_name;
|
|
gint duration = 0;
|
|
|
|
layer_name = pika_item_get_name (PIKA_ITEM (layers[total_frames-(whichframe+1)]));
|
|
if (layer_name)
|
|
{
|
|
duration = parse_ms_tag (layer_name);
|
|
g_free (layer_name);
|
|
}
|
|
|
|
if (duration < 0) duration = 100; /* FIXME for default-if-not-said */
|
|
if (duration == 0) duration = 100; /* FIXME - 0-wait is nasty */
|
|
|
|
return (guint32) duration;
|
|
}
|
|
|
|
static gboolean
|
|
is_ms_tag (const gchar *str,
|
|
gint *duration,
|
|
gint *taglength)
|
|
{
|
|
gint sum = 0;
|
|
gint offset;
|
|
gint length;
|
|
|
|
length = strlen (str);
|
|
|
|
if (str[0] != '(')
|
|
return FALSE;
|
|
|
|
offset = 1;
|
|
|
|
/* eat any spaces between open-parenthesis and number */
|
|
while ((offset<length) && (str[offset] == ' '))
|
|
offset++;
|
|
|
|
if ((offset>=length) || (!g_ascii_isdigit (str[offset])))
|
|
return 0;
|
|
|
|
do
|
|
{
|
|
sum *= 10;
|
|
sum += str[offset] - '0';
|
|
offset++;
|
|
}
|
|
while ((offset<length) && (g_ascii_isdigit (str[offset])));
|
|
|
|
if (length-offset <= 2)
|
|
return FALSE;
|
|
|
|
/* eat any spaces between number and 'ms' */
|
|
while ((offset<length) && (str[offset] == ' '))
|
|
offset++;
|
|
|
|
if ((length-offset <= 2) ||
|
|
(g_ascii_toupper (str[offset]) != 'M') ||
|
|
(g_ascii_toupper (str[offset+1]) != 'S'))
|
|
return FALSE;
|
|
|
|
offset += 2;
|
|
|
|
/* eat any spaces between 'ms' and close-parenthesis */
|
|
while ((offset<length) && (str[offset] == ' '))
|
|
offset++;
|
|
|
|
if ((length-offset < 1) || (str[offset] != ')'))
|
|
return FALSE;
|
|
|
|
offset++;
|
|
|
|
*duration = sum;
|
|
*taglength = offset;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static int
|
|
parse_ms_tag (const char *str)
|
|
{
|
|
gint i;
|
|
gint rtn;
|
|
gint dummy;
|
|
gint length;
|
|
|
|
length = strlen (str);
|
|
|
|
for (i = 0; i < length; i++)
|
|
{
|
|
if (is_ms_tag (&str[i], &rtn, &dummy))
|
|
return rtn;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static gboolean
|
|
is_disposal_tag (const gchar *str,
|
|
DisposeType *disposal,
|
|
gint *taglength)
|
|
{
|
|
if (strlen (str) != 9)
|
|
return FALSE;
|
|
|
|
if (strncmp (str, "(combine)", 9) == 0)
|
|
{
|
|
*taglength = 9;
|
|
*disposal = DISPOSE_COMBINE;
|
|
return TRUE;
|
|
}
|
|
else if (strncmp (str, "(replace)", 9) == 0)
|
|
{
|
|
*taglength = 9;
|
|
*disposal = DISPOSE_REPLACE;
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static DisposeType
|
|
parse_disposal_tag (const gchar *str)
|
|
{
|
|
DisposeType rtn;
|
|
gint i, dummy;
|
|
gint length;
|
|
|
|
length = strlen (str);
|
|
|
|
for (i=0; i<length; i++)
|
|
{
|
|
if (is_disposal_tag (&str[i], &rtn, &dummy))
|
|
{
|
|
return rtn;
|
|
}
|
|
}
|
|
|
|
return DISPOSE_UNDEFINED; /* FIXME */
|
|
}
|
|
|
|
static void
|
|
remove_disposal_tag (gchar *dest,
|
|
gchar *src)
|
|
{
|
|
gint offset = 0;
|
|
gint destoffset = 0;
|
|
gint length;
|
|
int taglength;
|
|
DisposeType dummy;
|
|
|
|
length = strlen (src);
|
|
|
|
strcpy (dest, src);
|
|
|
|
while (offset<=length)
|
|
{
|
|
if (is_disposal_tag (&src[offset], &dummy, &taglength))
|
|
{
|
|
offset += taglength;
|
|
}
|
|
dest[destoffset] = src[offset];
|
|
destoffset++;
|
|
offset++;
|
|
}
|
|
|
|
dest[offset] = '\0';
|
|
}
|
|
|
|
static void
|
|
remove_ms_tag (gchar *dest,
|
|
gchar *src)
|
|
{
|
|
gint offset = 0;
|
|
gint destoffset = 0;
|
|
gint length;
|
|
gint taglength;
|
|
gint dummy;
|
|
|
|
length = strlen (src);
|
|
|
|
strcpy (dest, src);
|
|
|
|
while (offset<=length)
|
|
{
|
|
if (is_ms_tag (&src[offset], &dummy, &taglength))
|
|
{
|
|
offset += taglength;
|
|
}
|
|
dest[destoffset] = src[offset];
|
|
destoffset++;
|
|
offset++;
|
|
}
|
|
|
|
dest[offset] = '\0';
|
|
}
|