/* * Animation Optimizer plug-in version 1.1.2 * * (c) Adam D. Moss, 1997-2003 * adam@gimp.org * adam@foxbox.org * * This is part of the GIMP package and falls under the GPL. */ /* * REVISION HISTORY: * * 2003-11-23 : version 1.1.2 * Improved optimization for GIF and file formats using * compression on a line-by-line basis. See bug #66367. * (Raphaƫl Quinet) * * 2003-08-12 : version 1.1.1 * Disable the semi-broken background/foreground stuff * unless EXPERIMENTAL_BACKDROP_CODE is defined... * * 2001-04-28 : version 1.1.0 [ALPHA] * Support automated background (or foreground) removal. * It's half-broken. * Eliminated special optimized frame alignment cases -- * we're not trying to be real-time like animationplay * and it complicates the code. * * 2000-08-30 : version 1.0.4 * Change default frame duration from 125ms to 100ms for * consistancy. * * 2000-06-05 : version 1.0.3 * Fix old bug which could cause errors in evaluating the * final pixel of each composed layer. * * 2000-01-13 : version 1.0.2 * Collapse timing of completely optimized-away frames * onto previous surviving frame. Also be looser with * (XXXXX) tag parsing. * * 2000-01-07 : version 1.0.1 * PDB interface submitted by Andreas Jaekel * * * 98.05.17 : version 1.0.0 * Finally preserves frame timings / layer names. Has * a progress bar now. No longer beta, I suppose. * * 98.04.19 : version 0.70.0 * Plug-in doubles up as Animation UnOptimize too! (This * is somewhat more useful than it sounds.) * * 98.03.16 : version 0.61.0 * Support more rare opaque/transparent combinations. * * 97.12.09 : version 0.60.0 * Added support for INDEXED* and GRAY* images. * * 97.12.09 : version 0.52.0 * Fixed some bugs. * * 97.12.08 : version 0.51.0 * Relaxed bounding box on optimized layers marked * 'replace'. * * 97.12.07 : version 0.50.0 * Initial release. */ /* * BUGS: * ? */ /* * TODO: * User interface */ /* #define EXPERIMENTAL_BACKDROP_CODE */ #include "config.h" #include #include #include #include #include "libgimp/stdplugins-intl.h" 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); 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 ncolours; static operatingMode opmode; MAIN () static void query (void) { static GimpParamDef args[] = { { GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive" }, { GIMP_PDB_IMAGE, "image", "Input image" }, { GIMP_PDB_DRAWABLE, "drawable", "Input drawable (unused)" } }; static GimpParamDef return_args[] = { { GIMP_PDB_IMAGE, "result", "Resulting image" } }; gimp_install_procedure ("plug_in_animationoptimize", "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 D. Moss ", "1997-2003", N_("/Filters/Animation/Optimize (for _GIF)"), "RGB*, INDEXED*, GRAY*", GIMP_PLUGIN, G_N_ELEMENTS (args), G_N_ELEMENTS (return_args), args, return_args); gimp_install_procedure ("plug_in_animationoptimize_diff", "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 D. Moss ", "1997-2001", N_("/Filters/Animation/_Optimize (Difference)"), "RGB*, INDEXED*, GRAY*", GIMP_PLUGIN, G_N_ELEMENTS (args), G_N_ELEMENTS (return_args), args, return_args); gimp_install_procedure ("plug_in_animationunoptimize", "This procedure 'simplifies' a GIMP layer-based" " animation that has been AnimationOptimized. This" " makes the animation much easier to work with if," " for example, the optimized version is all you" " have.", "", "Adam D. Moss ", "Adam D. Moss ", "1997-2001", N_("/Filters/Animation/_UnOptimize"), "RGB*, INDEXED*, GRAY*", GIMP_PLUGIN, G_N_ELEMENTS (args), G_N_ELEMENTS (return_args), args, return_args); #ifdef EXPERIMENTAL_BACKDROP_CODE gimp_install_procedure ("plug_in_animation_remove_backdrop", "This procedure attempts to remove the backdrop" " from a GIMP layer-based animation, leaving" " the foreground animation over transparency.", "", "Adam D. Moss ", "Adam D. Moss ", "2001", N_("/Filters/Animation/_Remove Backdrop"), "RGB*, INDEXED*, GRAY*", GIMP_PLUGIN, G_N_ELEMENTS (args), G_N_ELEMENTS (return_args), args, return_args); gimp_install_procedure ("plug_in_animation_find_backdrop", "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 D. Moss ", "2001", N_("/Filters/Animation/_Find Backdrop"), "RGB*, INDEXED*, GRAY*", GIMP_PLUGIN, G_N_ELEMENTS (args), G_N_ELEMENTS (return_args), args, return_args); #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, "plug_in_animationoptimize") == 0) opmode = OPOPTIMIZE; else if (strcmp (name, "plug_in_animationoptimize_diff") == 0) { opmode = OPOPTIMIZE; diff_only = TRUE; } else if (strcmp (name, "plug_in_animationunoptimize") == 0) opmode = OPUNOPTIMIZE; else if (strcmp (name, "plug_in_animation_find_backdrop") == 0) opmode = OPBACKGROUND; else if (strcmp (name, "plug_in_animation_remove_backdrop") == 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=0 && i= 128) { for (j=0; j 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_numdrawable_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; yitrbox_right) rbox_right=xit; if (yitrbox_bottom) rbox_bottom=yit; } if (keep_pix) { if (xitbbox_right) bbox_right=xit; if (yitbbox_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= 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=length) || (!g_ascii_isdigit (str[offset]))) return 0; do { sum *= 10; sum += str[offset] - '0'; offset++; } while ((offset