Logo Search packages:      
Sourcecode: mtpaint version File versions  Download package

ani.c

/*    ani.c
      Copyright (C) 2005-2008 Mark Tyler and Dmitry Groshev

      This file is part of mtPaint.

      mtPaint is free software; you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
      the Free Software Foundation; either version 3 of the License, or
      (at your option) any later version.

      mtPaint is distributed in the hope that it will be useful,
      but WITHOUT ANY WARRANTY; without even the implied warranty of
      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      GNU General Public License for more details.

      You should have received a copy of the GNU General Public License
      along with mtPaint in the file COPYING.
*/

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>

#include "global.h"

#include "mygtk.h"
#include "memory.h"
#include "ani.h"
#include "png.h"
#include "mainwindow.h"
#include "otherwindow.h"
#include "canvas.h"
#include "viewer.h"
#include "layer.h"
#include "spawn.h"
#include "inifile.h"
#include "mtlib.h"
#include "wu.h"

///   GLOBALS

int   ani_frame1 = 1, ani_frame2 = 1, ani_gif_delay = 10, ani_play_state = FALSE,
      ani_timer_state = 0;



///   FORM VARIABLES

static GtkWidget *animate_window = NULL, *ani_prev_win,
      *ani_entry_path, *ani_entry_prefix,
      *ani_spin[5],                 // start, end, delay
      *ani_text_pos, *ani_text_cyc, // Text input widgets
      *ani_prev_slider        // Slider widget on preview area
      ;

#define MAX_CYC_ITEMS 50
typedef struct {
      int frame0, frame1, len, layers[MAX_CYC_ITEMS];
} ani_cycle;

static ani_cycle ani_cycle_table[MAX_CYC_SLOTS];

static int
      ani_layer_data[MAX_LAYERS + 1][4],  // x, y, opacity, visible
      ani_currently_selected_layer;
static char ani_output_path[PATHBUF], ani_file_prefix[ANI_PREFIX_LEN+2];
static gboolean ani_use_gif, ani_show_main_state;



static void ani_win_read_widgets();




static void ani_widget_changed()    // Widget changed so flag the layers as changed
{
      layers_changed = 1;
}



static void ani_cyc_len_init()            // Initialize the cycle length array before doing any animating
{
      int i, k;

      for (i = 0; i < MAX_CYC_SLOTS; i++)
      {
            if (!ani_cycle_table[i].frame0) break;    // Last slot reached
            for (k = 0; (k < MAX_CYC_ITEMS) && ani_cycle_table[i].layers[k]; k++);
            // Must be a minimum of 1 for modulo use
            ani_cycle_table[i].len = k ? k : 1;
      }
}

static void set_layer_from_slot( int layer, int slot )            // Set layer x, y, opacity from slot
{
      ani_slot *ani = layer_table[layer].image->ani_pos + slot;
      layer_table[layer].x = ani->x;
      layer_table[layer].y = ani->y;
      layer_table[layer].opacity = ani->opacity;
}

static void set_layer_inbetween( int layer, int i, int frame, int effect )          // Calculate in between value for layer from slot i & (i+1) at given frame
{
      MT_Coor c[4], co_res, lenz;
      float p1, p2;
      int f0, f1, f2, f3, ii[4] = {i-1, i, i+1, i+2}, j;
      ani_slot *ani = layer_table[layer].image->ani_pos;


      f1 = ani[i].frame;
      f2 = ani[i + 1].frame;

      if (i > 0) f0 = ani[i - 1].frame;
      else
      {
            f0 = f1;
            ii[0] = ii[1];
      }

      if ((i >= MAX_POS_SLOTS - 2) || !(f3 = ani[i + 2].frame))
      {
            f3 = f2;
            ii[3] = ii[2];
      }

            // Linear in between
      p1 = ( (float) (f2-frame) ) / ( (float) (f2-f1) );    // % of (i-1) slot
      p2 = 1-p1;                                // % of i slot

      layer_table[layer].x = rint(p1 * ani[i].x + p2 * ani[i + 1].x);
      layer_table[layer].y = rint(p1 * ani[i].y + p2 * ani[i + 1].y);
      layer_table[layer].opacity = rint(p1 * ani[i].opacity +
            p2 * ani[i + 1].opacity);


      if ( effect == 1 )            // Interpolated smooth in between - use p2 value
      {
            for ( i=0; i<4; i++ ) c[i].z = 0;   // Unused plane
            lenz.x = f1 - f0;
            lenz.y = f2 - f1;             // Frames for each line
            lenz.z = f3 - f2;

            if ( lenz.x<1 ) lenz.x = 1;
            if ( lenz.y<1 ) lenz.y = 1;
            if ( lenz.z<1 ) lenz.z = 1;

            // Set up coords
            for ( j=0; j<4; j++ )
            {
                  c[j].x = ani[ii[j]].x;
                  c[j].y = ani[ii[j]].y;
            }
            co_res = MT_palin(p2, 0.35, c[0], c[1], c[2], c[3], lenz);

            layer_table[layer].x = co_res.x;
            layer_table[layer].y = co_res.y;
      }
}

static void ani_set_frame_state( int frame )
{
      int i, k, e, a, b, done, l;
      ani_slot *ani;

      for ( k=1; k<=layers_total; k++ )   // Set x, y, opacity for each layer
      {
            ani = layer_table[k].image->ani_pos;
            if (ani[0].frame > 0)
            {
                  for ( i=0; i<MAX_POS_SLOTS; i++ )         // Find first frame in position list that excedes or equals 'frame'
                  {
                        if (ani[i].frame <= 0) break;       // End of list
                        if (ani[i].frame >= frame) break;   // Exact match or one exceding it found
                  }

                  if ( i>=MAX_POS_SLOTS )       // All position slots < 'frame'
                  {
                        set_layer_from_slot( k, MAX_POS_SLOTS - 1 );
                              // Set layer pos/opac to last slot values
                  }
                  else
                  {
                        if (ani[i].frame == 0)        // All position slots < 'frame'
                        {
                              set_layer_from_slot( k, i - 1 );
                                    // Set layer pos/opac to last slot values
                        }
                        else
                        {
                              if (ani[i].frame == frame || i == 0)
                              {
                                    set_layer_from_slot( k, i );
                                          // If closest frame = requested frame, set all values to this
                                          // ditto if i=0, i.e. no better matches exist
                              }
                              else
                              {
                                    // i is currently pointing to slot that excedes 'frame', so in between this and the previous slot
                                    set_layer_inbetween( k, i-1, frame, ani[i - 1].effect );
                              }
                        }
                  }
            }     // If no slots have been defined leave the layer x, y, opacity as now
      }



      // Set visibility for each layer by processing cycle table

      for ( i=0; i<MAX_CYC_SLOTS; i++ )
      {
            a = ani_cycle_table[i].frame0;
            b = ani_cycle_table[i].frame1;
            if (!a) break;          // End of list reached

            if ( a==b && a<=frame )       // Special case for enabling/disabling en-masse
            {
                  for (k = 0; k < MAX_CYC_ITEMS; k++)
                  {
                        e = ani_cycle_table[i].layers[k];
                        if ( e==0 ) break;            // End delimeter encountered so stop
                        if ( e<0 )
                        {
                              if ( (-e) <= layers_total )         // If valid, hide layer
                                    layer_table[-e].visible = FALSE;
                        }
                        if ( e>0 )
                        {
                              if ( e <= layers_total )            // If valid, show layer
                                    layer_table[e].visible = TRUE;
                        }
                  }
            }
            if ( a<b && a<=frame && frame<=b )  // Frame is between these points so act
            {
                  done = -1;
                  l = ani_cycle_table[i].len;
                  for (k = 0; k < MAX_CYC_ITEMS; k++)
                  {
                        e = ani_cycle_table[i].layers[k];
                        if ( e==0 ) break;            // End delimeter encountered so stop
                        if ( e>0 && e<=layers_total && e!=done )
                        {
                              if ((frame - a) % l == k)
                              {
                                    layer_table[e].visible = TRUE;
                                    done = e;
                                    // Don't switch this off later in loop
                              }
                              else
                                    layer_table[e].visible = FALSE;
                              // Switch layer on or off according to frame position in cycle
                        }
                  }
            }
      }
}





static void ani_read_layer_data()         // Read current layer x/y/opacity data to table
{
      int i;

      for ( i=0; i<=MAX_LAYERS; i++ )
      {
            ani_layer_data[i][0] = layer_table[i].x;
            ani_layer_data[i][1] = layer_table[i].y;
            ani_layer_data[i][2] = layer_table[i].opacity;
            ani_layer_data[i][3] = layer_table[i].visible;
      }
}

static void ani_write_layer_data()        // Write current layer x/y/opacity data from table
{
      int i;

      for ( i=0; i<=MAX_LAYERS; i++ )
      {
            layer_table[i].x       = ani_layer_data[i][0];
            layer_table[i].y       = ani_layer_data[i][1];
            layer_table[i].opacity = ani_layer_data[i][2];
            layer_table[i].visible = ani_layer_data[i][3];
      }
}


static char *text_edit_widget_get(GtkWidget *w)       // Get text string from input widget
            // WARNING memory allocated for this so lose it with g_free(txt)
{
#if GTK_MAJOR_VERSION == 1
      return gtk_editable_get_chars( GTK_EDITABLE(w), 0, -1 );
#endif
#if GTK_MAJOR_VERSION == 2
      GtkTextIter begin, end;
      GtkTextBuffer *buffer = GTK_TEXT_VIEW(w)->buffer;

      gtk_text_buffer_get_start_iter( buffer, &begin );
      gtk_text_buffer_get_end_iter( buffer, &end );
      return gtk_text_buffer_get_text( buffer, &begin, &end, -1 );
#endif
}

static void empty_text_widget(GtkWidget *w)     // Empty the text widget
{
#if GTK_MAJOR_VERSION == 1
      gtk_text_set_point( GTK_TEXT(w), 0 );
      gtk_text_forward_delete( GTK_TEXT(w), gtk_text_get_length(GTK_TEXT(w)) );
#endif
#if GTK_MAJOR_VERSION == 2
      gtk_text_buffer_set_text( GTK_TEXT_VIEW(w)->buffer, "", 0 );
#endif
}

static void ani_cyc_refresh_txt()         // Refresh the text in the cycle text widget
{
      int i, j, k;
      char txt[128 + MAX_CYC_ITEMS * 6], *tmp;
#if GTK_MAJOR_VERSION == 2
      GtkTextIter iter;

      g_signal_handlers_block_by_func(GTK_TEXT_VIEW(ani_text_cyc)->buffer,
                  GTK_SIGNAL_FUNC(ani_widget_changed), NULL);
#endif
      empty_text_widget(ani_text_cyc);    // Clear the text in the widget

      for (i = 0; i < MAX_CYC_SLOTS; i++)
      {
            if (!ani_cycle_table[i].frame0) break;
            tmp = txt + sprintf(txt, "%i\t%i\t%i", ani_cycle_table[i].frame0,
                  ani_cycle_table[i].frame1, ani_cycle_table[i].layers[0]);
            for (j = 1; j < MAX_CYC_ITEMS; j++)
            {
                  k = ani_cycle_table[i].layers[j];
                  if (!k) break;
                  tmp += sprintf(tmp, ",%i", k);
            }
            strcpy(tmp, "\n");
#if GTK_MAJOR_VERSION == 1
            gtk_text_insert (GTK_TEXT (ani_text_cyc), NULL, NULL, NULL, txt, -1);
#endif
#if GTK_MAJOR_VERSION == 2
            gtk_text_buffer_get_end_iter( GTK_TEXT_VIEW(ani_text_cyc)->buffer, &iter );
            gtk_text_buffer_insert( GTK_TEXT_VIEW(ani_text_cyc)->buffer, &iter, txt, -1 );
#endif
      }

#if GTK_MAJOR_VERSION == 2
      g_signal_handlers_unblock_by_func(GTK_TEXT_VIEW(ani_text_cyc)->buffer,
                  GTK_SIGNAL_FUNC(ani_widget_changed), NULL);
      // We have to switch off then back on or it looks like the user changed it
#endif
}

static void ani_pos_refresh_txt()         // Refresh the text in the position text widget
{
      char txt[256];
      int i = ani_currently_selected_layer, j;
      ani_slot *ani;
#if GTK_MAJOR_VERSION == 2
      GtkTextIter iter;

      g_signal_handlers_block_by_func(GTK_TEXT_VIEW(ani_text_pos)->buffer,
            GTK_SIGNAL_FUNC(ani_widget_changed), NULL);
#endif

      empty_text_widget(ani_text_pos);    // Clear the text in the widget

      if ( i > 0 )            // Must no be for background layer or negative => PANIC!
      {
            for (j = 0; j < MAX_POS_SLOTS; j++)
            {
                  ani = layer_table[i].image->ani_pos + j;
                  if (ani->frame <= 0) break;
                  // Add a line if one exists
                  snprintf(txt, 250, "%i\t%i\t%i\t%i\t%i\n",
                        ani->frame, ani->x, ani->y, ani->opacity, ani->effect);
#if GTK_MAJOR_VERSION == 1
                  gtk_text_insert (GTK_TEXT (ani_text_pos), NULL, NULL, NULL, txt, -1);
#endif
#if GTK_MAJOR_VERSION == 2
                  gtk_text_buffer_get_end_iter( GTK_TEXT_VIEW(ani_text_pos)->buffer, &iter );
                  gtk_text_buffer_insert( GTK_TEXT_VIEW(ani_text_pos)->buffer, &iter, txt,
                        strlen(txt) );

#endif
            }
      }
#if GTK_MAJOR_VERSION == 2
      g_signal_handlers_unblock_by_func(GTK_TEXT_VIEW(ani_text_pos)->buffer,
            GTK_SIGNAL_FUNC(ani_widget_changed), NULL);
      // We have to switch off then back on or it looks like the user changed it
#endif
}

void ani_init()               // Initialize variables/arrays etc. before loading or on startup
{
      int j;

      ani_frame1 = 1;
      ani_frame2 = 100;
      ani_gif_delay = 10;

      ani_cycle_table[0].frame0 = 0;

      if ( layers_total>0 )         // No position array malloc'd until layers>0
      {
            for (j = 0; j <= layers_total; j++)
                  layer_table[j].image->ani_pos[0].frame = 0;
      }

      strcpy(ani_output_path, "frames");
      strcpy(ani_file_prefix, "f");

      ani_use_gif = TRUE;
}



///   EXPORT ANIMATION FRAMES WINDOW


static void ani_win_set_pos()
{
      win_restore_pos(animate_window, "ani", 0, 0, 200, 200);
}

static void ani_fix_pos()
{
      ani_read_layer_data();
      layers_notify_changed();
}

static void ani_but_save()
{
      ani_win_read_widgets();
      ani_write_layer_data();
      layer_press_save();
}

static void delete_ani()
{
      win_store_pos(animate_window, "ani");
      ani_win_read_widgets();
      gtk_widget_destroy(animate_window);
      animate_window = NULL;
      ani_write_layer_data();
      layers_pastry_cut = FALSE;

      show_layers_main = ani_show_main_state;
      update_all_views();
}


static int parse_line_pos( char *txt, int layer, int row )  // Read in position row from some text
{
      ani_slot data = { -1, -1, -1, -1, -1 };
      char *tx, *eol;
      int tot;

      tx = strchr( txt, '\n' );                 // Find out length of this input line
      if ( tx == NULL ) tot = strlen(txt);
      else tot = tx - txt + 1;
      eol = txt + tot - 1;

      while ( txt[0] < 32 )                     // Skip non ascii chars
      {
            if ( txt[0] == 0 ) return -1;       // If we reach the end, tell the caller
            txt = txt + 1;
      }
      sscanf(txt, "%i\t%i\t%i\t%i\t%i", &data.frame, &data.x, &data.y,
            &data.opacity, &data.effect);

      layer_table[layer].image->ani_pos[row] = data;

      return tot;
}

static void ani_parse_store_positions()         // Read current positions in text input and store
{
      char *txt = text_edit_widget_get( ani_text_pos ), *tx;
      int i, j, layer = ani_currently_selected_layer;

      tx = txt;

      for ( i=0; i<MAX_POS_SLOTS; i++ )
      {
            j = parse_line_pos( txt, layer, i );
            if ( j<0 ) break;
            txt += j;
      }
      if ( i<MAX_POS_SLOTS ) layer_table[layer].image->ani_pos[i].frame = 0;  // End delimeter

      g_free(tx);
}

static int parse_line_cyc( char *txt, int row )       // Read in cycle row from some text
{
      char *tx, *eol;
      int a=-1, b=-1, c=-1, tot, i;

      tx = strchr( txt, '\n' );                 // Find out length of this input line
      if ( tx == NULL ) tot = strlen(txt);
      else tot = tx - txt + 1;

      eol = txt + tot - 1;

      while ( txt[0] < 32 )                     // Skip non ascii chars
      {
            if ( txt[0] == 0 ) return -1;       // If we reach the end, tell the caller
            txt = txt + 1;
      }
      sscanf( txt, "%i\t%i\t%i", &a, &b, &c );
      ani_cycle_table[row].frame0 = a;
      ani_cycle_table[row].frame1 = b;
      ani_cycle_table[row].layers[0] = c;

      // Read in a number after each comma
      for (i = 1; i < MAX_CYC_ITEMS; i++)
      {
            tx = strchr( txt, ',' );            // Get next comma
            if (!tx || (eol - tx < 2)) break;   // Bail out if no comma on this line
            a = -1;
            sscanf(tx + 1, "%i", &a);
            ani_cycle_table[row].layers[i] = a;
            txt = tx + 1;
      }
      // Terminate with zero if needed
      if (i < MAX_CYC_ITEMS) ani_cycle_table[row].layers[i] = 0;

      return tot;
}

static void ani_parse_store_cycles()            // Read current cycles in text input and store
{
      char *txt = text_edit_widget_get( ani_text_cyc ), *tx;
      int i, j;

      tx = txt;

      for ( i=0; i<MAX_CYC_SLOTS; i++ )
      {
            j = parse_line_cyc( txt, i );
            if ( j<0 ) break;
            txt += j;
      }
      if ( i<MAX_CYC_SLOTS ) ani_cycle_table[i].frame0 = 0; // End delimeter

      g_free(tx);
}

static void ani_win_read_widgets()        // Read all widgets and set up relevant variables
{
      int   a = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON(ani_spin[0]) ),
            b = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON(ani_spin[1]) );


      ani_gif_delay = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON(ani_spin[2]) );

      ani_parse_store_positions();
      ani_parse_store_cycles();
      ani_pos_refresh_txt();        // Update 2 text widgets
      ani_cyc_refresh_txt();

      ani_frame1 = a < b ? a : b;
      ani_frame2 = a < b ? b : a;
      gtkncpy(ani_output_path, gtk_entry_get_text(GTK_ENTRY(ani_entry_path)), PATHBUF);
      gtkncpy(ani_file_prefix, gtk_entry_get_text(GTK_ENTRY(ani_entry_prefix)), ANI_PREFIX_LEN + 1);
      // GIF toggle is automatically set by callback
}


static gboolean ani_play_timer_call()
{
      int i;

      if ( ani_play_state == 0 )
      {
            ani_timer_state = 0;
            return FALSE;                 // Stop animating
      }
      else
      {
            i = ADJ2INT(SPINSLIDE_ADJUSTMENT(ani_prev_slider)) + 1;
            if (i > ani_frame2) i = ani_frame1;
            mt_spinslide_set_value(ani_prev_slider, i);
            return TRUE;
      }
}

static void ani_play_start()
{
      if ( ani_play_state == 0 )
      {
            ani_play_state = 1;
            if ( ani_timer_state == 0 )
                  ani_timer_state = g_timeout_add( ani_gif_delay*10, ani_play_timer_call, NULL );
      }
}

static void ani_play_stop()
{
      ani_play_state = 0;
}


///   PREVIEW WINDOW CALLBACKS

static void ani_but_playstop(GtkToggleButton *togglebutton, gpointer user_data)
{
      if (gtk_toggle_button_get_active(togglebutton)) ani_play_start();
      else ani_play_stop();
}

static void ani_frame_slider_moved(GtkAdjustment *adjustment, gpointer user_data)
{
      int x = 0, y = 0, w = mem_width, h = mem_height;

      ani_set_frame_state(ADJ2INT(adjustment));

      if (layer_selected)
      {
            x = -layer_table[layer_selected].x;
            y = -layer_table[layer_selected].y;
            w = layer_table[0].image->image_.width;
            h = layer_table[0].image->image_.height;
      }

      vw_update_area(x, y, w, h);   // Update only the area we need
}

static void ani_but_preview_close()
{
      ani_play_stop();                    // Stop animation playing if necessary

      win_store_pos(ani_prev_win, "ani_prev");
      gtk_widget_destroy( ani_prev_win );

      if ( animate_window != NULL )
      {
            ani_win_set_pos();
            gtk_widget_show (animate_window);
      }
      else
      {
            ani_write_layer_data();
            layers_pastry_cut = FALSE;
            update_all_views();
      }
}

void ani_but_preview()
{
      GtkWidget *hbox3, *button;
      GtkAccelGroup* ag = gtk_accel_group_new();


      if ( animate_window != NULL )
      {
            /* We need to remember this as we are hiding it */
            win_store_pos(animate_window, "ani");
            ani_win_read_widgets();       // Get latest values for the preview
      }
      else  ani_read_layer_data();

      ani_cyc_len_init();                 // Prepare the cycle index for the animation

      if ( !view_showing ) view_show();   // If not showing, show the view window

      ani_prev_win = add_a_window( GTK_WINDOW_TOPLEVEL,
                  _("Animation Preview"), GTK_WIN_POS_NONE, TRUE );
      gtk_container_set_border_width(GTK_CONTAINER(ani_prev_win), 5);

      win_restore_pos(ani_prev_win, "ani_prev", 0, 0, 200, -1);

      hbox3 = gtk_hbox_new (FALSE, 0);
      gtk_widget_show (hbox3);
      gtk_container_add (GTK_CONTAINER (ani_prev_win), hbox3);

      pack(hbox3, sig_toggle_button(_("Play"), FALSE, NULL,
            GTK_SIGNAL_FUNC(ani_but_playstop)));
      ani_play_state = FALSE;             // Stopped

      ani_prev_slider = mt_spinslide_new(-2, -2);
      xpack(hbox3, widget_align_minsize(ani_prev_slider, 200, -2));
      mt_spinslide_set_range(ani_prev_slider, ani_frame1, ani_frame2);
      mt_spinslide_set_value(ani_prev_slider, ani_frame1);
      mt_spinslide_connect(ani_prev_slider,
            GTK_SIGNAL_FUNC(ani_frame_slider_moved), NULL);

      if ( animate_window == NULL ) // If called via the menu have a fix button
      {
            button = add_a_button( _("Fix"), 5, hbox3, FALSE );
            gtk_signal_connect(GTK_OBJECT(button), "clicked",
                  GTK_SIGNAL_FUNC(ani_fix_pos), NULL);
      }

      button = add_a_button( _("Close"), 5, hbox3, FALSE );
      gtk_signal_connect(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(ani_but_preview_close), NULL);
      gtk_widget_add_accelerator (button, "clicked", ag, GDK_Escape, 0, (GtkAccelFlags) 0);

      gtk_signal_connect (GTK_OBJECT (ani_prev_win), "delete_event",
                  GTK_SIGNAL_FUNC(ani_but_preview_close), NULL);

      gtk_window_set_transient_for( GTK_WINDOW(ani_prev_win), GTK_WINDOW(main_window) );
      gtk_widget_show (ani_prev_win);
      gtk_window_add_accel_group(GTK_WINDOW (ani_prev_win), ag);

      if ( animate_window != NULL ) gtk_widget_hide (animate_window);
      else
      {
            layers_pastry_cut = TRUE;
            update_all_views();
      }

      gtk_adjustment_value_changed(SPINSLIDE_ADJUSTMENT(ani_prev_slider));
}

static void create_frames_ani()
{
      image_info *image;
      image_state *state;
      ls_settings settings;
      png_color pngpal[256], *trans;
      unsigned char *layer_rgb, *irgb = NULL;
      char output_path[PATHBUF], *command, *wild_path;
      int a, b, k, i, cols, layer_w, layer_h, npt, l = 0;


      ani_win_read_widgets();
      ani_cyc_len_init();           // Prepare the cycle index for the animation

      gtk_widget_hide(animate_window);

      ani_write_layer_data();
      layer_press_save();           // Save layers data file

      command = strrchr(layers_filename, DIR_SEP);
      if (command) l = command - layers_filename + 1;

      if (ani_output_path[0]) // Output path used?
      {
            snprintf(output_path, PATHBUF, "%.*s%s",
                  l, layers_filename, ani_output_path);

#ifdef WIN32
            if (mkdir(output_path))
#else
            if (mkdir(output_path, 0777))
#endif
            {
                  if ( errno != EEXIST )
                  {
                        alert_box(_("Error"), _("Unable to create output directory"),
                              _("OK"), NULL, NULL );
                        goto failure;                 // Failure to create directory
                  }
            }
      }

            // Create output path and pointer for first char of filename

      a = ani_frame1 < ani_frame2 ? ani_frame1 : ani_frame2;
      b = ani_frame1 < ani_frame2 ? ani_frame2 : ani_frame1;

      if (layer_selected == 0) image = &mem_image , state = &mem_state;
      else image = &layer_table[0].image->image_ , state = &layer_table[0].image->state_;

      layer_w = image->width;
      layer_h = image->height;
      layer_rgb = malloc( layer_w * layer_h * 3);     // Primary layer image for RGB version

      if (!layer_rgb)
      {
            memory_errors(1);
            goto failure;
      }

      /* Prepare settings */
      init_ls_settings(&settings, NULL);
      settings.mode = FS_COMPOSITE_SAVE;
      settings.width = layer_w;
      settings.height = layer_h;
      settings.colors = 256;
      settings.silent = TRUE;
      if (ani_use_gif)
      {
            irgb = malloc(layer_w * layer_h);   // Resulting indexed image
            if (!irgb)
            {
                  free(layer_rgb);
                  memory_errors(1);
                  goto failure;
            }
            settings.ftype = FT_GIF;
            settings.img[CHN_IMAGE] = irgb;
            settings.bpp = 1;
            settings.pal = pngpal;
      }
      else
      {
            settings.ftype = FT_PNG;
            settings.img[CHN_IMAGE] = layer_rgb;
            settings.bpp = 3;
            /* Background transparency */
            settings.xpm_trans = state->xpm_trans;
            settings.rgb_trans = settings.xpm_trans < 0 ? -1 :
                  PNG_2_INT(image->pal[settings.xpm_trans]);
      }

      progress_init(_("Creating Animation Frames"), 1);
      for ( k=a; k<=b; k++ )              // Create each frame and save it as a PNG or GIF image
      {
            if (progress_update(b == a ? 0.0 : (k - a) / (float)(b - a)))
                  break;

            ani_set_frame_state(k);       // Change layer positions
            view_render_rgb( layer_rgb, 0, 0, layer_w, layer_h, 1 );    // Render layer

            snprintf(output_path, PATHBUF, "%.*s%s%c%s%05d.%s", 
                  l, layers_filename, ani_output_path, DIR_SEP,
                  ani_file_prefix, k, ani_use_gif ? "gif" : "png");

            if ( ani_use_gif )      // Prepare palette
            {
                  cols = mem_cols_used_real(layer_rgb, layer_w, layer_h, 258, 0);
                                          // Count colours in image

                  if ( cols <= 256 )      // If <=256 convert directly
                        mem_cols_found(pngpal); // Get palette
                  else              // If >256 use Wu to quantize
                  {
                        cols = 256;
                        if (wu_quant(layer_rgb, layer_w, layer_h, cols,
                              pngpal)) goto failure2;
                  }

                  // Create new indexed image
                  if (mem_dumb_dither(layer_rgb, irgb, pngpal,
                        layer_w, layer_h, cols, FALSE)) goto failure2;

                  settings.xpm_trans = -1;      // Default is no transparency
                  if (state->xpm_trans >= 0)    // Background has transparency
                  {
                        trans = image->pal + state->xpm_trans;
                        npt = PNG_2_INT(*trans);
                        for (i = 0; i < cols; i++)
                        {     // Does it exist in the composite frame?
                              if (PNG_2_INT(pngpal[i]) != npt) continue;
                              // Transparency found so note it
                              settings.xpm_trans = i;
                              break;
                        }
                  }
            }

            if (save_image(output_path, &settings) < 0)
            {
                  alert_box( _("Error"), _("Unable to save image"), _("OK"), NULL, NULL );
                  goto failure2;
            }
      }

      if ( ani_use_gif )      // all GIF files created OK so lets give them to gifsicle
      {
            snprintf(output_path, PATHBUF, "%.*s%s%c%s?????.gif",
                  l, layers_filename, ani_output_path, DIR_SEP,
                  ani_file_prefix);
            wild_path = quote_spaces(output_path);
            snprintf(output_path, PATHBUF, "%.*s%s%c%s.gif",
                  l, layers_filename, ani_output_path, DIR_SEP,
                  ani_file_prefix);

            run_def_action(DA_GIF_CREATE, wild_path, output_path, ani_gif_delay);
            run_def_action(DA_GIF_PLAY, output_path, NULL, 0);
            free(wild_path);
      }

failure2:
      progress_end();
      free( layer_rgb );

failure:
      free( irgb );

      gtk_widget_show(animate_window);
}

void pressed_remove_key_frames()
{
      int i, j;

      i = alert_box( _("Warning"), _("Do you really want to clear all of the position and cycle data for all of the layers?"), _("No"), _("Yes"), NULL );
      if ( i==2 )
      {
            for (j = 0; j <= layers_total; j++)
                  layer_table[j].image->ani_pos[0].frame = 0;
                                          // Flush array in each layer
            ani_cycle_table[0].frame0 = 0;
      }
}

static void ani_set_key_frame(int key)          // Set key frame postions & cycles as per current layers
{
      ani_slot *ani;
      int i, j, k, l;


      for ( k=1; k<=layers_total; k++ )   // Add current position for each layer
      {
            ani = layer_table[k].image->ani_pos;
            // Find first occurence of 0 or frame # < 'key'
            for ( i=0; i<MAX_POS_SLOTS; i++ )
            {
                  if (ani[i].frame > key || ani[i].frame == 0) break;
            }

            if ( i>=MAX_POS_SLOTS ) i=MAX_POS_SLOTS-1;

            //  Shift remaining data down a slot
            for ( j=MAX_POS_SLOTS-1; j>i; j-- )
            {
                  ani[j] = ani[j - 1];
            }

            //  Enter data for the current state
            ani[i].frame = key;
            ani[i].x = layer_table[k].x;
            ani[i].y = layer_table[k].y;
            ani[i].opacity = layer_table[k].opacity;
            ani[i].effect = 0;                  // No effect
      }

      // Find first occurence of 0 or frame # < 'key'
      for ( i=0; i<MAX_CYC_SLOTS; i++ )
      {
            if ( ani_cycle_table[i].frame0 > key ||
                  ani_cycle_table[i].frame0 == 0 )
                        break;
      }

      if ( i>=MAX_CYC_SLOTS ) i=MAX_CYC_SLOTS-1;

      //  Shift remaining data down a slot
      for ( j=MAX_CYC_SLOTS-1; j>i; j-- )
            ani_cycle_table[j] = ani_cycle_table[j - 1];

      //  Enter data for the current state
      ani_cycle_table[i].frame0 = ani_cycle_table[i].frame1 = key;
      for ( j=1; j<=layers_total; j++ )
      {
            if (j > MAX_CYC_ITEMS) break; // More layers than free items so bail out
            if ( layer_table[j].visible ) l=j; else l=-j;
            ani_cycle_table[i].layers[j - 1] = l;
      }
      // Add terminator if needed
      if (j <= MAX_CYC_ITEMS) ani_cycle_table[i].layers[j - 1] = 0;
}

static void ani_tog_gif(GtkToggleButton *togglebutton, gpointer user_data)
{
      ani_use_gif = gtk_toggle_button_get_active(togglebutton);
      ani_widget_changed();
}

static void ani_layer_select( GtkList *list, GtkWidget *widget )
{
      int j = layers_total - gtk_list_child_position(list, widget);

      if ( j<1 || j>layers_total ) return;            // Item not found

      if ( ani_currently_selected_layer != -1 ) // Only if not first click
      {
            ani_parse_store_positions();        // Parse & store text inputs
      }

      ani_currently_selected_layer = j;
      ani_pos_refresh_txt();                    // Refresh the text in the widget
}

static int do_set_key_frame(GtkWidget *spin, gpointer fdata)
{
      int i;

      i = read_spin(spin);
      ani_set_key_frame(i);
      layers_notify_changed();

      return TRUE;
}

void pressed_set_key_frame()
{
      GtkWidget *spin = add_a_spin(ani_frame1, ani_frame1, ani_frame2);
      filter_window(_("Set Key Frame"), spin, do_set_key_frame, NULL, FALSE);
}

static GtkWidget *ani_text(GtkWidget **textptr)
{
      GtkWidget *scroll, *text;

#if GTK_MAJOR_VERSION == 1
      text = gtk_text_new(NULL, NULL);
      gtk_text_set_editable(GTK_TEXT(text), TRUE);

      gtk_signal_connect(GTK_OBJECT(text), "changed",
                  GTK_SIGNAL_FUNC(ani_widget_changed), NULL);

      scroll = gtk_scrolled_window_new(NULL, GTK_TEXT(text)->vadj);
#else /* #if GTK_MAJOR_VERSION == 2 */
      GtkTextBuffer *texbuf = gtk_text_buffer_new(NULL);

      text = gtk_text_view_new_with_buffer(texbuf);

      g_signal_connect(texbuf, "changed", GTK_SIGNAL_FUNC(ani_widget_changed), NULL);

      scroll = gtk_scrolled_window_new(GTK_TEXT_VIEW(text)->hadjustment,
            GTK_TEXT_VIEW(text)->vadjustment);
#endif
      gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
            GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
      gtk_container_add(GTK_CONTAINER(scroll), text);

      *textptr = text;
      return (scroll);
}

void pressed_animate_window()
{
      GtkWidget *table, *label, *button, *notebook1, *scrolledwindow;
      GtkWidget *ani_toggle_gif, *ani_list_layers, *list_data;
      GtkWidget *hbox4, *hbox2, *vbox1, *vbox3, *vbox4;
      GtkAccelGroup* ag = gtk_accel_group_new();
      char txt[PATHTXT];
      int i;


      if ( layers_total < 1 )                         // Only background layer available
      {
            alert_box(_("Error"), _("You must have at least 2 layers to create an animation"),
                  _("OK"), NULL, NULL );
            return;
      }

      if (!layers_filename[0])
      {
            alert_box(_("Error"), _("You must save your layers file before creating an animation"),
                  _("OK"), NULL, NULL );
            return;
      }

      delete_layers_window(); // Lose the layers window if its up

      ani_read_layer_data();

      ani_currently_selected_layer = -1;

      animate_window = add_a_window( GTK_WINDOW_TOPLEVEL, _("Configure Animation"),
                              GTK_WIN_POS_NONE, TRUE );

      ani_win_set_pos();

      vbox1 = gtk_vbox_new (FALSE, 0);
      gtk_container_add (GTK_CONTAINER (animate_window), vbox1);

      notebook1 = xpack(vbox1, gtk_notebook_new());
      gtk_container_set_border_width(GTK_CONTAINER(notebook1), 5);

      vbox4 = add_new_page(notebook1, _("Output Files"));
      table = xpack(vbox4, gtk_table_new(5, 3, FALSE));

      label = add_to_table( _("Start frame"), table, 0, 0, 5 );
      add_to_table( _("End frame"), table, 1, 0, 5 );

      add_to_table( _("Delay"), table, 2, 0, 5 );
      add_to_table( _("Output path"), table, 3, 0, 5 );
      add_to_table( _("File prefix"), table, 4, 0, 5 );

      ani_spin[0] = spin_to_table(table, 0, 1, 5, ani_frame1, 1, MAX_FRAME);  // Start
      ani_spin[1] = spin_to_table(table, 1, 1, 5, ani_frame2, 1, MAX_FRAME);  // End
      ani_spin[2] = spin_to_table(table, 2, 1, 5, ani_gif_delay, 1, MAX_DELAY);     // Delay

      spin_connect(ani_spin[0], GTK_SIGNAL_FUNC(ani_widget_changed), NULL);
      spin_connect(ani_spin[1], GTK_SIGNAL_FUNC(ani_widget_changed), NULL);
      spin_connect(ani_spin[2], GTK_SIGNAL_FUNC(ani_widget_changed), NULL);

      ani_entry_path = gtk_entry_new_with_max_length(PATHBUF);
      gtk_table_attach (GTK_TABLE (table), ani_entry_path, 1, 2, 3, 4,
            (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
            (GtkAttachOptions) (0), 0, 0);
      gtkuncpy(txt, ani_output_path, PATHTXT);
      gtk_entry_set_text(GTK_ENTRY(ani_entry_path), txt);
      gtk_signal_connect( GTK_OBJECT(ani_entry_path), "changed",
                  GTK_SIGNAL_FUNC(ani_widget_changed), NULL);

      ani_entry_prefix = gtk_entry_new_with_max_length(ANI_PREFIX_LEN);
      gtk_table_attach (GTK_TABLE (table), ani_entry_prefix, 1, 2, 4, 5,
            (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
            (GtkAttachOptions) (0), 0, 0);
      gtkuncpy(txt, ani_file_prefix, PATHTXT);
      gtk_entry_set_text(GTK_ENTRY(ani_entry_prefix), txt);
      gtk_signal_connect( GTK_OBJECT(ani_entry_prefix), "changed",
                  GTK_SIGNAL_FUNC(ani_widget_changed), NULL);

      ani_toggle_gif = pack(vbox4, sig_toggle(_("Create GIF frames"),
            ani_use_gif, NULL, GTK_SIGNAL_FUNC(ani_tog_gif)));

///   LAYERS TABLES

      hbox4 = gtk_hbox_new(FALSE, 0);
      label = gtk_label_new(_("Positions"));
      gtk_notebook_append_page(GTK_NOTEBOOK(notebook1), hbox4, label);

      scrolledwindow = pack(hbox4, gtk_scrolled_window_new(NULL, NULL));
      gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow),
                  GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);

      ani_list_layers = gtk_list_new ();
      gtk_signal_connect( GTK_OBJECT(ani_list_layers), "select_child",
                  GTK_SIGNAL_FUNC(ani_layer_select), NULL );
      gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolledwindow), ani_list_layers);

      gtk_widget_set_usize (ani_list_layers, 150, -2);
      gtk_container_set_border_width (GTK_CONTAINER (ani_list_layers), 5);

      for ( i=layers_total; i>0; i-- )
      {
            hbox2 = gtk_hbox_new( FALSE, 3 );

            list_data = gtk_list_item_new();
            gtk_container_add( GTK_CONTAINER(ani_list_layers), list_data );
            gtk_container_add( GTK_CONTAINER(list_data), hbox2 );

            sprintf(txt, "%i", i);                          // Layer number
            label = pack(hbox2, gtk_label_new(txt));
            gtk_widget_set_usize (label, 40, -2);
            gtk_misc_set_alignment( GTK_MISC(label), 0.5, 0.5 );

            label = xpack(hbox2, gtk_label_new(layer_table[i].name)); // Layer name
            gtk_misc_set_alignment( GTK_MISC(label), 0, 0.5 );
      }

      vbox3 = xpack(hbox4, gtk_vbox_new(FALSE, 0));
      xpack(vbox3, ani_text(&ani_text_pos));

///   CYCLES TAB

      vbox3 = add_new_page(notebook1, _("Cycling"));
      xpack(vbox3, ani_text(&ani_text_cyc));

      ani_cyc_refresh_txt();

///   MAIN BUTTONS

      hbox2 = pack(vbox1, gtk_hbox_new(FALSE, 0));

      button = add_a_button(_("Close"), 5, hbox2, TRUE);
      gtk_signal_connect(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(delete_ani), NULL);
      gtk_widget_add_accelerator (button, "clicked", ag, GDK_Escape, 0, (GtkAccelFlags) 0);

      button = add_a_button(_("Save"), 5, hbox2, TRUE);
      gtk_signal_connect(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(ani_but_save), NULL);

      button = add_a_button(_("Preview"), 5, hbox2, TRUE);
      gtk_signal_connect(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(ani_but_preview), NULL);

      button = add_a_button(_("Create Frames"), 5, hbox2, TRUE);
      gtk_signal_connect(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(create_frames_ani), NULL);

      gtk_signal_connect_object (GTK_OBJECT (animate_window), "delete_event",
            GTK_SIGNAL_FUNC (delete_ani), NULL);

      ani_show_main_state = show_layers_main;   // Remember old state
      show_layers_main = FALSE;           // Don't show all layers in main window - too messy

      gtk_window_set_transient_for( GTK_WINDOW(animate_window), GTK_WINDOW(main_window) );

      gtk_list_select_item( GTK_LIST(ani_list_layers), 0 );

      gtk_widget_show_all(animate_window);
      gtk_window_add_accel_group(GTK_WINDOW (animate_window), ag);

      layers_pastry_cut = TRUE;
      update_all_views();
}



///   FILE HANDLING

void ani_read_file( FILE *fp )                  // Read data from layers file already opened
{
      int i, j, k, tot;
      char tin[2048];

      ani_init();
      do
      {
            if (!fgets(tin, 2000, fp)) return;        // BAILOUT - invalid line
            string_chop( tin );
      } while ( strcmp( tin, ANIMATION_HEADER ) != 0 );     // Look for animation header

      i = read_file_num(fp, tin);
      if ( i<0 ) return;                        // BAILOUT - invalid #
      ani_frame1 = i;

      i = read_file_num(fp, tin);
      if ( i<0 ) return;                        // BAILOUT - invalid #
      ani_frame2 = i;

      if (!fgets(tin, 2000, fp)) return;        // BAILOUT - invalid line
      string_chop( tin );
      strncpy0(ani_output_path, tin, PATHBUF);

      if (!fgets(tin, 2000, fp)) return;        // BAILOUT - invalid #
      string_chop( tin );
      strncpy0(ani_file_prefix, tin, ANI_PREFIX_LEN + 1);

      i = read_file_num(fp, tin);
      if ( i<0 )
      {
            ani_use_gif = FALSE;
            ani_gif_delay = -i;
      }
      else
      {
            ani_use_gif = TRUE;
            ani_gif_delay = i;
      }

///   CYCLE DATA

      i = read_file_num(fp, tin);
      if ( i<0 || i>MAX_CYC_SLOTS ) return;                 // BAILOUT - invalid #

      tot = i;
      for ( j=0; j<tot; j++ )                         // Read each cycle line
      {
            if (!fgets(tin, 2000, fp)) break;         // BAILOUT - invalid line

            parse_line_cyc( tin, j );
      }
      if ( j<MAX_CYC_SLOTS ) ani_cycle_table[j].frame0 = 0; // Mark end

///   POSITION DATA

      for ( k=0; k<=layers_total; k++ )
      {
            i = read_file_num(fp, tin);
            if ( i<0 || i>MAX_POS_SLOTS ) return;                 // BAILOUT - invalid #

            tot = i;
            for ( j=0; j<tot; j++ )                         // Read each position line
            {
                  if (!fgets(tin, 2000, fp)) break;         // BAILOUT - invalid line

                  parse_line_pos( tin, k, j );
            }
            if ( j<MAX_POS_SLOTS )
                  layer_table[k].image->ani_pos[j].frame = 0;     // Mark end
      }
}

void ani_write_file( FILE *fp )                 // Write data to layers file already opened
{
      int gifcode = ani_gif_delay, i, j, k, l;

      if ( layers_total == 0 ) return;    // No layers memory allocated so bail out


      if ( !ani_use_gif ) gifcode = -gifcode;

      // HEADER

      fprintf( fp, "%s\n", ANIMATION_HEADER );
      fprintf( fp, "%i\n%i\n%s\n%s\n%i\n", ani_frame1, ani_frame2,
                  ani_output_path, ani_file_prefix, gifcode );

      // CYCLE INFO

      // Count number of cycles, and output this data (if any)
      for ( i=0; i<MAX_CYC_SLOTS; i++ )
      {
            if (!ani_cycle_table[i].frame0) break;    // Bail out at 1st 0
      }

      fprintf( fp, "%i\n", i );

      for ( k=0; k<i; k++ )
      {
            fprintf(fp, "%i\t%i\t%i", ani_cycle_table[k].frame0,
                  ani_cycle_table[k].frame1, ani_cycle_table[k].layers[0]);
            for (j = 1; j < MAX_CYC_ITEMS; j++)
            {
                  l = ani_cycle_table[k].layers[j];
                  if (!l) break;
                  fprintf( fp, ",%i", l);
            }
            fprintf( fp, "\n" );
      }

      // POSITION INFO

      // NOTE - we are saving data for layer 0 even though its never used during animation.
      // This is because the user may shift this layer up/down and bring it into play

      for ( k=0; k<=layers_total; k++ )         // Write position table for each layer
      {
            ani_slot *ani = layer_table[k].image->ani_pos;
            for ( i=0; i<MAX_POS_SLOTS; i++ )   // Count how many lines are in the table
            {
                  if (ani[i].frame == 0) break;
            }
            fprintf( fp, "%i\n", i );           // Number of position lines for this layer
            if ( i>0 )
            {
                  for ( j=0; j<i; j++ )
                  {
                        fprintf( fp, "%i\t%i\t%i\t%i\t%i\n",
                              ani[j].frame, ani[j].x, ani[j].y,
                              ani[j].opacity, ani[j].effect);
                  }
            }
      }
}

Generated by  Doxygen 1.6.0   Back to index