/* * Animation Playback plug-in version 0.85.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.03.16 : version 0.85.0 * Implemented some more rare opaque/alpha combinations. * * 98.03.15 : version 0.84.0 * Tried to clear up the GTK object/cast warnings. Only * partially successful. Could use some help. * * 97.12.11 : version 0.83.0 * GTK's timer logic changed a little... adjusted * plugin to fit. * * 97.09.16 : version 0.81.7 * Fixed progress bar's off-by-one problem with * the new timing. Fixed erroneous black bars which * were sometimes visible when the first frame was * smaller than the image itself. Made playback * controls inactive when image doesn't have multiple * frames. Moved progress bar above control buttons, * it's less distracting there. More cosmetic stuff. * * 97.09.15 : version 0.81.0 * Now plays INDEXED and GRAY animations. * * 97.09.15 : version 0.75.0 * Next frame is generated ahead of time - results * in more precise timing. * * 97.09.14 : version 0.70.0 * Initial release. RGB only. */ /* * BUGS: * Leaks memory. Not sure why. * Gets understandably upset if the source image is deleted * while the animation is playing. * Decent solutions to either of the above are most welcome. * * Any more? Let me know! */ /* * TODO: * pdb interface - should we bother? * speedups (caching? most bottlenecks seem to be in pixelrgns) * write other half of the user 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_playback (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 void playstop_callback (GtkWidget *widget, gpointer data); static void rewind_callback (GtkWidget *widget, gpointer data); static void step_callback (GtkWidget *widget, gpointer data); static DisposeType get_frame_disposal (guint whichframe); static void render_frame (gint32 whichframe); static void show_frame (void); static void total_alpha_preview (void); static void init_preview_misc (void); GPlugInInfo PLUG_IN_INFO = { NULL, /* init_proc */ NULL, /* quit_proc */ query, /* query_proc */ run, /* run_proc */ }; /* Global widgets'n'stuff */ guchar* preview_data; static GtkPreview* preview = NULL; GtkProgressBar* progress; guint width,height; guchar* preview_alpha1_data; guchar* preview_alpha2_data; gint32 image_id; gint32 total_frames; guint frame_number; gint32* layers; GDrawable* drawable; gboolean playing = FALSE; int timer = 0; GImageType imagetype; guchar* palette; gint ncolours; 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_animationplay", "This plugin allows you to preview a GIMP layer-based animation.", "", "Adam D. Moss ", "Adam D. Moss ", "1997", "/Filters/Animation/Animation Playback", "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; } } if (status == STATUS_SUCCESS) { image_id = param[1].data.d_image; do_playback(); if (run_mode != RUN_NONINTERACTIVE) gimp_displays_flush(); } values[0].type = PARAM_STATUS; values[0].data.d_status = status; } 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 (gtk_progress_bar_new ()); gtk_widget_set_usize (GTK_WIDGET (progress), 150, 15); gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (progress), TRUE, TRUE, 0); gtk_widget_show (GTK_WIDGET (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 there aren't multiple frames, playback controls make no sense */ 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 (gtk_preview_new (GTK_PREVIEW_COLOR)); /* FIXME */ gtk_preview_size (preview, width, height); gtk_container_add (GTK_CONTAINER (frame2), GTK_WIDGET (preview)); gtk_widget_show(GTK_WIDGET (preview)); } gtk_widget_show(frame2); } gtk_widget_show(vbox); } gtk_widget_show(hbox); } gtk_widget_show(frame); } gtk_widget_show(dlg); } static void do_playback(void) { int i; 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); if (imagetype == INDEXED) palette = gimp_image_get_cmap(image_id, &ncolours); else if (imagetype == GRAY) { /* This is a bit sick, until this plugin ever gets real GRAY support (not worth it?) */ palette = g_malloc(768); for (i=0;i<256;i++) { palette[i*3] = palette[i*3+1] = palette[i*3+2] = i; } ncolours = 256; } frame_number = 0; /* cache hint "cache nothing", since we iterate over every tile in every layer. */ gimp_tile_cache_size (0); init_preview_misc(); build_dialog(gimp_image_base_type(image_id), gimp_image_get_filename(image_id)); /* Make sure that whole preview is dirtied with pure-alpha */ total_alpha_preview(); for (i=0;i= total_frames) { printf("playback: Asked for frame number %d in a %d-frame animation!\n", (int) (whichframe+1), (int) total_frames); exit(-1); } drawable = gimp_drawable_get (layers[total_frames-(whichframe+1)]); dispose = get_frame_disposal(frame_number); /* 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->id)==0) { window_close_callback(NULL, NULL); } if (((dispose==DISPOSE_REPLACE)||(whichframe==0)) && gimp_drawable_has_alpha(drawable->id)) { total_alpha_preview(); } /* 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 */ destptr = preview_data; srcptr = rawframe; i = rawwidth*rawheight; while (--i) { if (!(*(srcptr+3)&128)) { srcptr += 4; destptr += 3; continue; } *(destptr++) = *(srcptr++); *(destptr++) = *(srcptr++); *(destptr++) = *(srcptr++); srcptr++; } } else /* no alpha */ { if ((rawwidth==width)&&(rawheight==height)) { /*printf("quickie\n");fflush(stdout);*/ memcpy(preview_data, rawframe, width*height*3); } } /* Display the preview buffer... finally. */ for (i=0;iid)) { /* alpha */ srcptr = rawframe; for (j=rawy; j=0 && i=0 && j=0 && i=0 && j=0 && iid)) { /* alpha */ destptr = preview_data; srcptr = rawframe; i = rawwidth*rawheight; while (--i) { if (!(*(srcptr+1))) { srcptr += 2; destptr += 3; continue; } *(destptr++) = palette[3*(*(srcptr))]; *(destptr++) = palette[1+3*(*(srcptr))]; *(destptr++) = palette[2+3*(*(srcptr))]; srcptr+=2; } } else /* no alpha */ { destptr = preview_data; srcptr = rawframe; i = rawwidth*rawheight; while (--i) { *(destptr++) = palette[3*(*(srcptr))]; *(destptr++) = palette[1+3*(*(srcptr))]; *(destptr++) = palette[2+3*(*(srcptr))]; srcptr++; } } /* Display the preview buffer... finally. */ for (i=0;iid)) { /* alpha */ srcptr = rawframe; for (j=rawy; j=0 && i=0 && j=0 && i=0 && j=0 && i