gimp/plug-ins/common/animation-optimize.c

1329 lines
42 KiB
C

/*
* Animation Optimizer plug-in version 1.1.2
*
* (c) Adam D. Moss, 1997-2003
* adam@gimp.org
* adam@foxbox.org
*
* GIMP - The GNU Image Manipulation Program
* 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 <http://www.gnu.org/licenses/>.
*/
/*
#define EXPERIMENTAL_BACKDROP_CODE
*/
#include "config.h"
#include <string.h>
#include <libgimp/gimp.h>
#include "libgimp/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;
/* Declare local functions. */
static void query (void);
static void run (const gchar *name,
gint nparams,
const GimpParam *param,
gint *nreturn_vals,
GimpParam **return_vals);
static gint32 do_optimizations (GimpRunMode run_mode,
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);
const GimpPlugInInfo PLUG_IN_INFO =
{
NULL, /* init_proc */
NULL, /* quit_proc */
query, /* query_proc */
run, /* run_proc */
};
/* Global widgets'n'stuff */
static guint width, height;
static gint32 image_id;
static gint32 new_image_id;
static gint32 total_frames;
static gint32 *layers;
static GimpDrawable *drawable;
static GimpImageBaseType imagetype;
static GimpImageType drawabletype_alpha;
static guchar pixelstep;
static guchar *palette;
static gint ncolors;
static operatingMode opmode;
MAIN ()
static void
query (void)
{
static const GimpParamDef args[] =
{
{ GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
{ GIMP_PDB_IMAGE, "image", "Input image" },
{ GIMP_PDB_DRAWABLE, "drawable", "Input drawable (unused)" }
};
static const GimpParamDef return_args[] =
{
{ GIMP_PDB_IMAGE, "result", "Resulting image" }
};
gimp_install_procedure (OPTIMIZE_PROC,
N_("Modify image to reduce size when saved as GIF animation"),
"This procedure applies various optimizations to"
" a GIMP 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.",
"Adam D. Moss <adam@gimp.org>",
"Adam D. Moss <adam@gimp.org>",
"1997-2003",
N_("Optimize (for _GIF)"),
"RGB*, INDEXED*, GRAY*",
GIMP_PLUGIN,
G_N_ELEMENTS (args),
G_N_ELEMENTS (return_args),
args, return_args);
gimp_install_procedure (OPTIMIZE_DIFF_PROC,
N_("Reduce file size where combining layers is possible"),
"This procedure applies various optimizations to"
" a GIMP 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.",
"Adam D. Moss <adam@gimp.org>",
"Adam D. Moss <adam@gimp.org>",
"1997-2001",
N_("_Optimize (Difference)"),
"RGB*, INDEXED*, GRAY*",
GIMP_PLUGIN,
G_N_ELEMENTS (args),
G_N_ELEMENTS (return_args),
args, return_args);
gimp_install_procedure (UNOPTIMIZE_PROC,
N_("Remove optimization to make editing easier"),
"This procedure 'simplifies' a GIMP layer-based"
" animation that has been optimized for animation. "
"This makes editing the animation much easier.",
"Adam D. Moss <adam@gimp.org>",
"Adam D. Moss <adam@gimp.org>",
"1997-2001",
N_("_Unoptimize"),
"RGB*, INDEXED*, GRAY*",
GIMP_PLUGIN,
G_N_ELEMENTS (args),
G_N_ELEMENTS (return_args),
args, return_args);
gimp_plugin_menu_register (OPTIMIZE_PROC, "<Image>/Filters/Animation");
gimp_plugin_menu_register (OPTIMIZE_DIFF_PROC, "<Image>/Filters/Animation");
gimp_plugin_menu_register (UNOPTIMIZE_PROC, "<Image>/Filters/Animation");
#ifdef EXPERIMENTAL_BACKDROP_CODE
gimp_install_procedure (REMOVE_BACKDROP_PROC,
"This procedure attempts to remove the backdrop"
" from a GIMP layer-based animation, leaving"
" the foreground animation over transparency.",
"",
"Adam D. Moss <adam@gimp.org>",
"Adam D. Moss <adam@gimp.org>",
"2001",
N_("_Remove Backdrop"),
"RGB*, INDEXED*, GRAY*",
GIMP_PLUGIN,
G_N_ELEMENTS (args),
G_N_ELEMENTS (return_args),
args, return_args);
gimp_install_procedure (FIND_BACKDROP_PROC,
"This procedure attempts to remove the foreground"
" from a GIMP layer-based animation, leaving"
" a one-layered image containing only the"
" constant backdrop image.",
"",
"Adam D. Moss <adam@gimp.org>",
"Adam D. Moss <adam@gimp.org>",
"2001",
N_("_Find Backdrop"),
"RGB*, INDEXED*, GRAY*",
GIMP_PLUGIN,
G_N_ELEMENTS (args),
G_N_ELEMENTS (return_args),
args, return_args);
gimp_plugin_menu_register (REMOVE_BACKDROP_PROC, "<Image>/Filters/Animation");
gimp_plugin_menu_register (FIND_BACKDROP_PROC, "<Image>/Filters/Animation");
#endif
}
static void
run (const gchar *name,
gint n_params,
const GimpParam *param,
gint *nreturn_vals,
GimpParam **return_vals)
{
static GimpParam values[2];
GimpRunMode run_mode;
GimpPDBStatusType status = GIMP_PDB_SUCCESS;
gboolean diff_only = FALSE;
*nreturn_vals = 2;
*return_vals = values;
run_mode = param[0].data.d_int32;
INIT_I18N ();
if (run_mode == GIMP_RUN_NONINTERACTIVE && n_params != 3)
{
status = GIMP_PDB_CALLING_ERROR;
}
/* Check the procedure name we were called with, to decide
what needs to be done. */
if (strcmp (name, OPTIMIZE_PROC) == 0)
opmode = OPOPTIMIZE;
else if (strcmp (name, OPTIMIZE_DIFF_PROC) == 0)
{
opmode = OPOPTIMIZE;
diff_only = TRUE;
}
else if (strcmp (name, UNOPTIMIZE_PROC) == 0)
opmode = OPUNOPTIMIZE;
else if (strcmp (name, FIND_BACKDROP_PROC) == 0)
opmode = OPBACKGROUND;
else if (strcmp (name, REMOVE_BACKDROP_PROC) == 0)
opmode = OPFOREGROUND;
else
g_error("GAH!!!");
if (status == GIMP_PDB_SUCCESS)
{
image_id = param[1].data.d_image;
new_image_id = do_optimizations (run_mode, diff_only);
if (run_mode != GIMP_RUN_NONINTERACTIVE)
gimp_displays_flush();
}
values[0].type = GIMP_PDB_STATUS;
values[0].data.d_status = status;
values[1].type = GIMP_PDB_IMAGE;
values[1].data.d_image = new_image_id;
}
/* 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 void
compose_row (gint frame_num,
DisposeType dispose,
gint row_num,
guchar *dest,
gint dest_width,
GimpDrawable *drawable,
gboolean cleanup)
{
static guchar *line_buf = NULL;
guchar *srcptr;
GimpPixelRgn pixel_rgn;
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);
}
gimp_drawable_offsets (drawable->drawable_id,
&rawx,
&rawy);
rawheight = gimp_drawable_height (drawable->drawable_id);
/* this frame has nothing to give us for this row; return */
if (row_num >= rawheight + rawy ||
row_num < rawy)
return;
rawbpp = gimp_drawable_bpp (drawable->drawable_id);
rawwidth = gimp_drawable_width (drawable->drawable_id);
has_alpha = gimp_drawable_has_alpha (drawable->drawable_id);
if (line_buf)
{
g_free (line_buf);
line_buf = NULL;
}
line_buf = g_malloc (rawwidth * rawbpp);
/* Initialise and fetch the raw new frame row */
gimp_pixel_rgn_init (&pixel_rgn,
drawable,
0, row_num - rawy,
rawwidth, 1,
FALSE,
FALSE);
gimp_pixel_rgn_get_rect (&pixel_rgn,
line_buf,
0, row_num - rawy,
rawwidth, 1);
/* 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 gint32
do_optimizations (GimpRunMode run_mode,
gboolean diff_only)
{
GimpPixelRgn pixel_rgn;
static guchar *rawframe = NULL;
guchar *srcptr;
guchar *destptr;
gint row, this_frame_num;
guint32 frame_sizebytes;
gint32 new_layer_id;
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;
gint last_true_frame = -1;
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:
gimp_progress_init (_("Unoptimizing animation"));
break;
case OPFOREGROUND:
gimp_progress_init (_("Removing animation background"));
break;
case OPBACKGROUND:
gimp_progress_init (_("Finding animation background"));
break;
case OPOPTIMIZE:
default:
gimp_progress_init (_("Optimizing animation"));
break;
}
width = gimp_image_width (image_id);
height = gimp_image_height (image_id);
layers = gimp_image_get_layers (image_id, &total_frames);
imagetype = gimp_image_base_type (image_id);
pixelstep = (imagetype == GIMP_RGB) ? 4 : 2;
/* gimp_tile_cache_ntiles(total_frames * (width / gimp_tile_width() + 1) );*/
drawabletype_alpha = (imagetype == GIMP_RGB) ? GIMP_RGBA_IMAGE :
((imagetype == GIMP_INDEXED) ? GIMP_INDEXEDA_IMAGE : GIMP_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_id = gimp_image_new(width, height, imagetype);
gimp_image_undo_disable (new_image_id);
if (imagetype == GIMP_INDEXED)
{
palette = gimp_image_get_colormap (image_id, &ncolors);
gimp_image_set_colormap (new_image_id, 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,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++)
{
drawable =
gimp_drawable_get (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
);
gimp_drawable_detach(drawable);
}
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)
{
new_layer_id = gimp_layer_new (new_image_id,
"Backgroundx",
width, height,
drawabletype_alpha,
100.0,
gimp_image_get_default_new_layer_mode (new_image_id));
gimp_image_insert_layer (new_image_id, new_layer_id, -1, 0);
drawable = gimp_drawable_get (new_layer_id);
gimp_pixel_rgn_init (&pixel_rgn, drawable,
0, 0,
width, height,
TRUE, FALSE);
gimp_pixel_rgn_set_rect (&pixel_rgn, back_frame,
0, 0,
width, height);
gimp_drawable_flush (drawable);
gimp_drawable_detach (drawable);
}
else
{
for (this_frame_num=0; this_frame_num<total_frames; this_frame_num++)
{
/*
* BUILD THIS FRAME into our 'this_frame' buffer.
*/
drawable =
gimp_drawable_get (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 gimp_drawable_get() fails? */
if (gimp_drawable_width (drawable->drawable_id) == 0)
{
gimp_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
);
}
/* clean up */
gimp_drawable_detach(drawable);
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 =
gimp_item_get_name(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 = gimp_item_get_name (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)");
gimp_item_set_name (last_true_frame, newlayer_name);
g_free (newlayer_name);
}
else
{
cumulated_delay = this_delay;
last_true_frame =
new_layer_id = gimp_layer_new (new_image_id,
newlayer_name,
bbox_right-bbox_left,
bbox_bottom-bbox_top,
drawabletype_alpha,
100.0,
gimp_image_get_default_new_layer_mode (new_image_id));
g_free (newlayer_name);
gimp_image_insert_layer (new_image_id, new_layer_id, -1, 0);
drawable = gimp_drawable_get (new_layer_id);
gimp_pixel_rgn_init (&pixel_rgn, drawable, 0, 0,
bbox_right-bbox_left,
bbox_bottom-bbox_top,
TRUE, FALSE);
gimp_pixel_rgn_set_rect (&pixel_rgn, opti_frame, 0, 0,
bbox_right-bbox_left,
bbox_bottom-bbox_top);
gimp_drawable_flush (drawable);
gimp_drawable_detach (drawable);
gimp_item_transform_translate (new_layer_id, bbox_left, bbox_top);
}
gimp_progress_update (((gdouble) this_frame_num + 1.0) /
((gdouble) total_frames));
}
gimp_progress_update (1.0);
}
gimp_image_undo_enable (new_image_id);
if (run_mode != GIMP_RUN_NONINTERACTIVE)
gimp_display_new (new_image_id);
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_id;
}
/* Util. */
static DisposeType
get_frame_disposal (guint whichframe)
{
gchar *layer_name;
DisposeType disposal;
layer_name = gimp_item_get_name(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 = gimp_item_get_name(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';
}