/* * Animation Optimizer plug-in version 0.70.0 * * by Adam D. Moss, 1997-98 * adam@gimp.org * adam@foxbox.org * * This is part of the GIMP package and falls under the GPL. */ /* * REVISION HISTORY: * * 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: * Probably a few */ /* * TODO: * User interface * PDB interface */ #include #include #include #include #include "libgimp/gimp.h" #include "gtk/gtk.h" typedef enum { DISPOSE_UNDEFINED = 0x00, DISPOSE_COMBINE = 0x01, DISPOSE_REPLACE = 0x02 } DisposeType; /* Declare local functions. */ static void query(void); static void run(char *name, int nparams, GParam * param, int *nreturn_vals, GParam ** return_vals); static void do_optimizations (void); /*static int parse_ms_tag (char *str);*/ static DisposeType parse_disposal_tag (char *str); static void window_close_callback (GtkWidget *widget, gpointer data); static DisposeType get_frame_disposal (guint whichframe); GPlugInInfo PLUG_IN_INFO = { NULL, /* init_proc */ NULL, /* quit_proc */ query, /* query_proc */ run, /* run_proc */ }; /* Global widgets'n'stuff */ GtkWidget* progress; guint width,height; gint32 image_id; gint32 new_image_id; gint32 total_frames; guint frame_number; gint32* layers; GDrawable* drawable; gboolean playing = FALSE; GImageType imagetype; GDrawableType drawabletype_alpha; guchar pixelstep; guchar* palette; gint ncolours; gboolean optimize; MAIN() static void query() { static GParamDef args[] = { {PARAM_INT32, "run_mode", "Interactive, non-interactive"}, {PARAM_IMAGE, "image", "Input image"}, {PARAM_DRAWABLE, "drawable", "Input drawable (unused)"}, }; static GParamDef *return_vals = NULL; static int nargs = sizeof(args) / sizeof(args[0]); static int nreturn_vals = 0; gimp_install_procedure("plug_in_animationoptimize", "This plugin applies various optimizations to" " a GIMP layer-based animation.", "", "Adam D. Moss ", "Adam D. Moss ", "1997-98", "/Filters/Animation/Animation Optimize", "RGB*, INDEXED*, GRAY*", PROC_PLUG_IN, nargs, nreturn_vals, args, return_vals); gimp_install_procedure("plug_in_animationunoptimize", "This plugin '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-98", "/Filters/Animation/Animation UnOptimize", "RGB*, INDEXED*, GRAY*", PROC_PLUG_IN, nargs, nreturn_vals, args, return_vals); } static void run(char *name, int n_params, GParam * param, int *nreturn_vals, GParam ** return_vals) { static GParam values[1]; GRunModeType run_mode; GStatusType status = STATUS_SUCCESS; *nreturn_vals = 1; *return_vals = values; run_mode = param[0].data.d_int32; if (run_mode == RUN_NONINTERACTIVE) { if (n_params != 3) { status = STATUS_CALLING_ERROR; } } /* Check the procedure name we were called with, to decide what needs to be done. */ if (strcmp(name,"plug_in_animationoptimize")==0) optimize = TRUE; else optimize = FALSE; /* UnOptimize */ if (status == STATUS_SUCCESS) { image_id = param[1].data.d_image; do_optimizations(); if (run_mode != RUN_NONINTERACTIVE) gimp_displays_flush(); } values[0].type = PARAM_STATUS; values[0].data.d_status = status; } #if 0 /* function is presently not used */ static int parse_ms_tag (char *str) { gint sum = 0; gint offset = 0; gint length; length = strlen(str); while ((offset=length) return(-1); if (!isdigit(str[++offset])) return(-2); do { sum *= 10; sum += str[offset] - '0'; offset++; } while ((offsetaction_area), button, TRUE, TRUE, 0); gtk_widget_grab_default (button); gtk_widget_show (button); { /* The 'playback' half of the dialog */ /* windowname = g_malloc(strlen("Playback: ")+strlen(imagename)+1); strcpy(windowname,"Playback: "); strcat(windowname,imagename); frame = gtk_frame_new (windowname); g_free(windowname); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN); gtk_container_border_width (GTK_CONTAINER (frame), 3); gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), frame, TRUE, TRUE, 0); { hbox = gtk_hbox_new (FALSE, 5); gtk_container_border_width (GTK_CONTAINER (hbox), 3); gtk_container_add (GTK_CONTAINER (frame), hbox); { vbox = gtk_vbox_new (FALSE, 5); gtk_container_border_width (GTK_CONTAINER (vbox), 3); gtk_container_add (GTK_CONTAINER (hbox), vbox); { progress = gtk_progress_bar_new (); gtk_widget_set_usize (progress, 150, 15); gtk_box_pack_start (GTK_BOX (vbox), progress, TRUE, TRUE, 0); gtk_widget_show (progress); hbox2 = gtk_hbox_new (FALSE, 0); gtk_container_border_width (GTK_CONTAINER (hbox2), 0); gtk_box_pack_start (GTK_BOX (vbox), hbox2, TRUE, TRUE, 0); { button = gtk_button_new_with_label ("Play/Stop"); gtk_signal_connect (GTK_OBJECT (button), "clicked", (GtkSignalFunc) playstop_callback, NULL); gtk_box_pack_start (GTK_BOX (hbox2), button, TRUE, TRUE, 0); gtk_widget_show (button); button = gtk_button_new_with_label ("Rewind"); gtk_signal_connect (GTK_OBJECT (button), "clicked", (GtkSignalFunc) rewind_callback, NULL); gtk_box_pack_start (GTK_BOX (hbox2), button, TRUE, TRUE, 0); gtk_widget_show (button); button = gtk_button_new_with_label ("Step"); gtk_signal_connect (GTK_OBJECT (button), "clicked", (GtkSignalFunc) step_callback, NULL); gtk_box_pack_start (GTK_BOX (hbox2), button, TRUE, TRUE, 0); gtk_widget_show (button); } if (total_frames<=1) gtk_widget_set_sensitive (hbox2, FALSE); gtk_widget_show(hbox2); frame2 = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (frame2), GTK_SHADOW_ETCHED_IN); gtk_box_pack_start (GTK_BOX (vbox), frame2, FALSE, FALSE, 0); { preview = gtk_preview_new (GTK_PREVIEW_COLOR); gtk_preview_size (GTK_PREVIEW (preview), width, height); gtk_container_add (GTK_CONTAINER (frame2), preview); gtk_widget_show(preview); } gtk_widget_show(frame2); } gtk_widget_show(vbox); } gtk_widget_show(hbox); } gtk_widget_show(frame); */ } gtk_widget_show(dlg); } #endif /* build_dialog */ /* 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 do_optimizations(void) { GPixelRgn pixel_rgn; static guchar* rawframe = NULL; static gint rawwidth=0, rawheight=0, rawbpp=0; gint rawx=0, rawy=0; guchar* srcptr; guchar* destptr; gint i,j,this_frame_num; guint32 frame_sizebytes; gint32 new_layer_id; DisposeType dispose; guchar* this_frame = NULL; guchar* last_frame = NULL; guchar* opti_frame = NULL; gboolean can_combine; gint32 bbox_top, bbox_bottom, bbox_left, bbox_right; gint32 rbox_top, rbox_bottom, rbox_left, rbox_right; 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 == RGB) ? 4 : 2; drawabletype_alpha = (imagetype == RGB) ? RGBA_IMAGE : ((imagetype == INDEXED) ? INDEXEDA_IMAGE : 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); total_alpha(this_frame, width*height, pixelstep); total_alpha(last_frame, width*height, pixelstep); new_image_id = gimp_image_new(width, height, imagetype); if (imagetype == INDEXED) { palette = gimp_image_get_cmap(image_id, &ncolours); gimp_image_set_cmap(new_image_id, palette, ncolours); } if ( (this_frame == NULL) || (last_frame == NULL) || (opti_frame == NULL) ) g_error("Not enough memory to allocate buffers for optimization.\n"); for (this_frame_num=0; this_frame_numid)==0) { window_close_callback(NULL, NULL); } dispose = get_frame_disposal(this_frame_num); if (dispose==DISPOSE_REPLACE) { total_alpha(this_frame, width*height, pixelstep); } /* only get a new 'raw' drawable-data buffer if this and the previous raw buffer were different sizes*/ if ((rawwidth*rawheight*rawbpp) != ((gimp_drawable_width(drawable->id)* gimp_drawable_height(drawable->id)* gimp_drawable_bpp(drawable->id)))) { if (rawframe != NULL) g_free(rawframe); rawframe = g_malloc((gimp_drawable_width(drawable->id)) * (gimp_drawable_height(drawable->id)) * (gimp_drawable_bpp(drawable->id))); } rawwidth = gimp_drawable_width(drawable->id); rawheight = gimp_drawable_height(drawable->id); rawbpp = gimp_drawable_bpp(drawable->id); /* Initialise and fetch the whole raw new frame */ gimp_pixel_rgn_init (&pixel_rgn, drawable, 0, 0, drawable->width, drawable->height, FALSE, FALSE); gimp_pixel_rgn_get_rect (&pixel_rgn, rawframe, 0, 0, drawable->width, drawable->height); /* gimp_pixel_rgns_register (1, &pixel_rgn);*/ gimp_drawable_offsets (drawable->id, &rawx, &rawy); /* render... */ switch (imagetype) { case RGB: if ((rawwidth==width) && (rawheight==height) && (rawx==0) && (rawy==0)) { /* --- These cases are for the best cases, in --- */ /* --- which this frame is the same size and position --- */ /* --- as the preview buffer itself --- */ if (gimp_drawable_has_alpha (drawable->id)) { /* alpha RGB, same size */ destptr = this_frame; srcptr = rawframe; i = rawwidth*rawheight; while (--i) { if (!((*(srcptr+3))&128)) { srcptr += 4; destptr += 4; continue; } *(destptr++) = *(srcptr++); *(destptr++) = *(srcptr++); *(destptr++) = *(srcptr++); *(destptr++) = 255; srcptr++; } } else /* RGB no alpha, same size */ { destptr = this_frame; srcptr = rawframe; i = rawwidth*rawheight; while (--i) { *(destptr++) = *(srcptr++); *(destptr++) = *(srcptr++); *(destptr++) = *(srcptr++); *(destptr++) = 255; } } } else { /* --- These are suboptimal catch-all cases for when --- */ /* --- this frame is bigger/smaller than the preview --- */ /* --- buffer, and/or offset within it. --- */ /* FIXME: FINISH ME! */ if (gimp_drawable_has_alpha (drawable->id)) { /* RGB alpha, diff size */ destptr = this_frame; srcptr = rawframe; for (j=rawy; j=0 && i=0 && j=0 && i=0 && jid)) { /* I alpha, same size */ destptr = this_frame; srcptr = rawframe; i = rawwidth*rawheight; while (--i) { if (!(*(srcptr+1))) { srcptr += 2; destptr += 2; continue; } *(destptr++) = *(srcptr); *(destptr++) = 255; srcptr+=2; } } else /* I, no alpha, same size */ { destptr = this_frame; srcptr = rawframe; i = rawwidth*rawheight; while (--i) { *(destptr++) = *(srcptr); *(destptr++) = 255; srcptr++; } } } else { /* --- These are suboptimal catch-all cases for when --- */ /* --- this frame is bigger/smaller than the preview --- */ /* --- buffer, and/or offset within it. --- */ /* FIXME: FINISH ME! */ if (gimp_drawable_has_alpha (drawable->id)) { /* I alpha, diff size */ srcptr = rawframe; for (j=rawy; j=0 && i=0 && j=0 && i=0 && jrbox_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