/* * Animation Optimizer plug-in version 1.1.0 [ALPHA] * * (c) Adam D. Moss, 1997-2001 * adam@gimp.org * adam@foxbox.org * * This is part of the GIMP package and falls under the GPL. */ /* * REVISION HISTORY: * * 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 */ #include "config.h" #include #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); /* tag util functions*/ static int parse_ms_tag (const char *str); static DisposeType parse_disposal_tag (const char *str); static DisposeType get_frame_disposal (const guint whichframe); static guint32 get_frame_duration (const guint whichframe); static void remove_disposal_tag (char* dest, char *src); static void remove_ms_tag (char* dest, char *src); static int is_disposal_tag (const char *str, DisposeType *disposal, int *taglength); static int is_ms_tag (const char *str, int *duration, int *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.", "", "Adam D. Moss ", "Adam D. Moss ", "1997-2001", N_("/Filters/Animation/_Optimize"), "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); 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); } 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; *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_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); 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(int frame_num, DisposeType dispose, int row_num, unsigned char *dest, int dest_width, GimpDrawable *drawable, const gboolean cleanup) { static unsigned char *line_buf = NULL; guchar *srcptr; GimpPixelRgn pixel_rgn; gint rawx, rawy, rawbpp, rawwidth, rawheight; int 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) { /* fprintf(stderr, "%d ", */ /* these_rows[this_frame_num][i * pixelstep + pixelstep -1]); */ 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);*/ } g_warning("stat fun over"); 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) { int xit, yit, byteit; g_warning("matcher"); 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++; /* * 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=length) || (!isdigit(str[offset]))) return 0; do { sum *= 10; sum += str[offset] - '0'; offset++; } while ((offset