Gimp::Fu animated Gifs

This is a little illustration about using / patching Gimp::Fu to make animated gifs, intended to follow on from Dov's tutorial. Please note that I know nothing about any of this stuff whatsoever; the most rudimentary of rudimentary perl knowledge, 2 days experience of Gimp::Fu, and the audacity to wade right in and hack everything with abandon are all I have to offer. But then, if I can get somewhere with this paltry knowledge, then so, in all probability, can you.

The tutorial also takes in batch processing the files, enabling you to, for example, use a script to automatically generate headers for a website.

This document describes a small perl plug-in called slider, which does this:

Ok, so the effect is something less than breathtaking - and it makes big cumbersome gifs as well. But hey, you can always go and roll your own if you don't like it.

As an introduction to the plugin, I'll show you the command line used to generate that example:

slider -o "GIF-F+L-D50:bl.gif" -text "sliding text" -font "-eraman-haettenschweiler-medium-r-normal-*-26-240-*-*-*-*-*-*" -fg_color "#00df00" -bg_color "#ffffff" -frames 15

Here's what it all means, blow by blow:

There are more options; slider --help will give some details.

This stuff was done with Gimp 1.1.13, Perl version 5.005_03, and Gimp-perl 1.16. Gimp-Perl lives here; much kudos to Marc Lehmann for it..

Sections: New options in Gimp::Fu::save_image    slider source    Batching perl-fu    Fu.pm patch

New options in Gimp::Fu::save_image

As mentioned in Dov's tutorial, you can specify various options when saving files using Gimp::Fu - for example, executing 'basic-logo -o "JPG-Q100:bl.jpg"' specifies that we want a jpg saved at quality 100. These options are described in the Gimp::Fu manpage. To make it easier to work with animated gifs, I've added a few options to the function. The details are below; for now, note that perldoc Gimp::Fu on my system includes
IMAGETYPE is one of GIF, JPG, JPEG, PNM or PNG, options include

 options valid for all images
 +F	flatten the image (default depends on the image)
 -F	do not flatten the image
 
 options for GIF and PNG images
 +I	do save as interlaced (GIF only)
 -I	do not save as interlaced (default)
 
 options for GIF animations (use with -F)
 +L     save as looping animation
 -L     save as non-looping animation (default)
 -Dn    default frame delay (default is 0)
 -Pn    frame disposal method: 0=don't care, 1 = combine, 2 = replace
 

 options for PNG images
 -Cn	use compression level n

 options for JPEG images
 -Qn	use quality "n" to save file (JPEG only)
 -S	do not smooth (default)
 +S	smooth before saving
So now, I can run

slider -o "GIF-F+L-D20:bl.gif" -text "Help!!"

to generate a looping animated gif, with a default frame delay of 20. The -F option specifies that the image should not be flattened; when saving a multi-layer gif, each layer is saved as a frame, so flattening the image would clobber your lovely gadgety gif more than somewhat.

Of course, hacking Fu.pm is unwise for the unenlightened; these changes work for me, but always keep a backup, yadda yadda yadda. And it's perfectly possible to generate animated gifs without these modifications, but I think they're handy.

slider source

You can download slider here. Of course, it won't be much use until you deface your Fu.pm.

Here's what slider --help gets you:


Usage: ./slider [gimp-args..] [interface-args..] [script-args..]
       gimp-arguments are
           -gimp            used internally only
           -h | -help | --help | -?   print some help
           -v | --verbose             be more verbose in what you do
           --host|--tcp HOST[:PORT]   connect to HOST (optionally using PORT)
                                      (for more info, see Gimp::Net(3))
       interface-arguments are
           -o | --output    write image to disk, don't display
           -i | --interact            let the user edit the values first
       script-arguments are
           -font XLFD                 font [-*-desdemona-*-*-*-*-*-190-*-*-*-*-*-*]
           -border integer            border [10]
           -text string               text [Hello world!]
           -bg_color colour           Background color [ARRAY(0x8227d6c)]
           -fg_color colour           Foreground color [ARRAY(0x8227df0)]
           -antialias integer         0=Don't antialias, 1= do [1]
           -frames integer            Number of frames [20]
           -stepsize integer          Pixels per frame [5]
           -delay integer             Wait on last frame [1000]
           -transparent integer       Tranparent background (0=no, 1=yes) [0]

Sometimes you've to combine script options with output options to get the effect you want. To get transparent backgrounds, for example, you'd issue a command like:

slider -o "GIF-F+L-D20-P2:bl.gif" -text "sliding text" -transparent 1 -antialias 0

The -P2 option (replace frames rather than combining them) is vital for transparent text. Also, the text looks better without antialiasing.

The code is mostly commented; feel free to mail me any queries, suggestions etc. You can see that we specify the delay only for the layer called background - the one which displays the text in readable form. All the others take the deafult delay, as specified in your -o argument.


#!/usr/bin/perl

#  This is slider, a Gimp-perl plugin by Hugh Denman, based on basic_logo by Dov Grobgeld
#  This program is under the Gnu Public License
#  The code comes with no warranty whatsoever; see the GPL for further details
#  Hugh Denman, November 29, 1999

use Gimp qw/:auto/;
use Gimp::Fu;


sub slider {
    my($font, $border, $text, $bgcolor, $fgcolor, $aa, $frames, $step, $delay, $trans) = @_;

    my ($textlayers, $bglayer, $i);

    # Create a new image of an arbitrary size
    $img = gimp_image_new(100, 100, RGB);

    if (! $trans)
      {
	# Create a new layer for the background and add it to the image
	my $background = gimp_layer_new($img, 100, 100,
					RGB, "Background", 100,
					NORMAL_MODE);
	gimp_image_add_layer($background, 1);
      }
    
    # Set color for text
    gimp_palette_set_foreground($fgcolor);

    # Create the text layer. Using -1 as the drawable creates a new layer.
    my $text_layer = gimp_text_fontname($img, -1, 0, 0, $text,
					$border, $aa, 
					xlfd_size($font), $font);

    # Get size of the text drawable and resize the image and the
    # background layer to this size.
    my($width, $height) = ($text_layer->width, $text_layer->height);
    gimp_image_resize($img, $width, $height, 0, 0);
    
    # Sort out background unless transparent
    if (! $trans)
      {
	gimp_layer_resize($background, $width, $height, 0, 0);
	gimp_palette_set_background($bgcolor);
	gimp_edit_fill($background);
	# Merge background and text
	$background = $img->merge_visible_layers(1);
      }
    else {$background=$text_layer;}

    # Set final delay. In gimp animations, you set the delay of a frame 
    # using the name of the corresponding layer.
    # For example, a layer called 'bground (1000ms)' will have a delay of 1 sec

    $background->set_name("bground (${delay}ms)");

    # Protect this layer from further merging
    $background->set_visible(0);

    # Create the subsequent frames
    for ($i=1; $i<$frames; $i++)
      {
	# the order in which layers are created here matters
	# the background has to be created first, so's it's below
	# the two text layers. Otherwise it obscurs them.

	# background layer for this frame (none if transparent)
	if (!$trans)
	  {
	    $bglayer = gimp_layer_new ($img,$width,$height,
				       RGB_IMAGE,"bg",100,NORMAL_MODE);
	    gimp_drawable_fill ($bglayer,BG_IMAGE_FILL);
	    gimp_image_add_layer($img, $bglayer, 0);
	  }

	# left hand side text
	$textlayers[$i] = gimp_text_fontname($img, -1, -($step*$i), 0, $text,
					     $border, $aa, 
					     xlfd_size($font), $font);
	
	# right hand side text
	$textlayers[$i+1] = gimp_text_fontname($img, -1, $step*$i, 0, $text,
					       $border, $aa, 
					       xlfd_size($font), $font);

	#merge both text layers, & frame background if there is one
	$textlayers[$i] = $img->merge_visible_layers(1);

	$img->lower_layer_to_bottom($textlayers[$i]);

	# protect from further merging
	$textlayers[$i]->set_visible(0);
      }
    

    # make all layers visible. There is a nicer way to do this
    # but it doesn't work for me :(

    for ($i=1;$i<$frames;$i++) {$textlayers[$i]->set_visible(1);}
    $background->set_visible(1);

    # some of the layers extend outside the image; crop 'em
    gimp_crop($img,$width,$height,0,0);

    return $img;
}

# register the script
register "slider", "sliding text", "sliding text",
    "Hugh Denman", "Hugh Denman",
    "1999-29-11",
    "/Xtns/Perl-Fu/Slider", 
    "*",
    [
     [PF_FONT,   "font",       "font",   "-*-desdemona-*-*-*-*-*-190-*-*-*-*-*-*"],
     [PF_INT,    "border",     "border", "10"],
     [PF_STRING, "text",       "text", "Hello world!"],
     [PF_COLOR,  "bg_color",   "Background color", [0,0,0]],
     [PF_COLOR,  "fg_color",   "Foreground color", [255,255,255]],
     [PF_INT,    "antialias",  "0=Don't antialias, 1= do", "1"],
     [PF_INT,    "frames",     "Number of frames", "20"],
     [PF_INT,    "stepsize",   "Pixels per frame", "5"],
     [PF_INT,    "delay",      "Wait on last frame", "1000"],
     [PF_INT,    "transparent","Tranparent background (0=no, 1=yes)", "0"],
    ],
    \&slider;

# Handle over control to gimp
exit main();

Batch processing Gimp::Fu scripts

Ordinarily, calling a script in batch mode entails starting up the gimp for each file. This slows everything down far too much to be practical if you're working with lots of files. However, using the perl server, we can run the gimp once, in the background, as a server; the scripts will then connect to the server instance of the gimp instead of creating their own. The bash command I use to do this is:

gimp -n -b '(extension-perl-server 1 0 0)' >&/dev/null &

Note that all output is thrown away here; when debugging things, you might be better off ommitting '>&/dev/null'.

The magic part is that the Gimp::Fu scripts automatically detect the server and talk to it. You just run them from the command line as normal. To kill the server when you're done processing scripts, execute

kill %"gimp -n"

(again, that's a bash command). This works by matching 'gimp -n' against the command lines of currently running background jobs; do a 'man bash' for more details.

diff output between my Fu.pm and the original

The command I ran was diff -C 2 /mnt/new/src/gimp-1.1.13/plug-ins/perl/Gimp/Fu.pm /usr/lib/perl5/site_perl/5.005/i386-linux/Gimp/Fu.pm You can grab it here. You could then conceivably issue patch Fu.pm < animatedgif.patch, but you'd have to be a wild impetuous fool to do anything like that.


*** /mnt/new/src/gimp-1.1.13/plug-ins/perl/Gimp/Fu.pm	Wed Nov 17 21:15:08 1999
--- /usr/lib/perl5/site_perl/5.005/i386-linux/Gimp/Fu.pm	Mon Nov 29 02:32:28 1999
***************
*** 798,801 ****
--- 798,807 ----
   -I	do not save as interlaced (default)
   
+  options for GIF animations (use with -F)
+  +L     save as looping animation
+  -L     save as non-looping animation (default)
+  -Dn	default frame delay (default is 0)
+  -Pn    frame disposal method: 0=don't care, 1 = combine, 2 = replace
+  
   options for PNG images
   -Cn	use compression level n
***************
*** 820,824 ****
  sub save_image($$) {
     my($img,$path)=@_;
!    my($interlace,$flatten,$quality,$type,$smooth,$compress);
     
     $interlace=0;
--- 826,830 ----
  sub save_image($$) {
     my($img,$path)=@_;
!    my($interlace,$flatten,$quality,$type,$smooth,$compress,$loop,$dispose);
     
     $interlace=0;
***************
*** 826,829 ****
--- 832,838 ----
     $smooth=0;
     $compress=7;
+    $loop=0;
+    $delay=0;
+    $dispose=0;
     
     $_=$path=~s/^([^:]+):// ? $1 : "";
***************
*** 836,839 ****
--- 845,851 ----
        $quality=$1*0.01,		next if s/^-[qQ](\d+)//;
        $compress=$1,		next if s/^-[cC](\d+)//;
+       $loop=$1 eq "+",		next if s/^([-+])[lL]//;
+       $delay=$1,		next if s/^-[dD](\d+)//;
+       $dispose=$1,		next if s/^-[pP](\d+)//;
        croak __"$_: unknown/illegal file-save option";
     }
***************
*** 849,857 ****
        $layer->file_jpeg_save($path,$path,$quality,$smooth,1,$interlace,"",0,1,0,0) if $@;
     } elsif ($type eq "GIF") {
!       unless ($layer->indexed) {
           eval { $img->convert_indexed(1,256) };
           $img->convert_indexed(2,&Gimp::MAKE_PALETTE,256,1,1,"") if $@;
        }
!       $layer->file_gif_save($path,$path,$interlace,0,0,0);
     } elsif ($type eq "PNG") {
        $layer->file_png_save($path,$path,$interlace,$compress);
--- 861,869 ----
        $layer->file_jpeg_save($path,$path,$quality,$smooth,1,$interlace,"",0,1,0,0) if $@;
     } elsif ($type eq "GIF") {
!       unless (Gimp::gimp_drawable_is_indexed($layer)) {    # HD: changed from ($layer->indexed) {
           eval { $img->convert_indexed(1,256) };
           $img->convert_indexed(2,&Gimp::MAKE_PALETTE,256,1,1,"") if $@;
        }
!       $layer->file_gif_save($path,$path,$interlace,$loop,$delay,$dispose);
     } elsif ($type eq "PNG") {
        $layer->file_png_save($path,$path,$interlace,$compress);

Hugh Denman
Last modified: Mon Nov 29 12:03:35 GMT 1999