/* gap_mov_exec.c
 * 1997.11.06 hof (Wolfgang Hofer)
 *
 * GAP ... Gimp Animation Plugins
 *
 * Move : procedures for copying source layer(s) to multiple frames
 * (varying Koordinates, opacity, size ...)
 *
 */
/* The GIMP -- an image manipulation program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/* revision history:
 * gimp    1.1.29b; 2000/11/20  hof: FRAME based Stepmodes, bugfixes for path calculation
 * gimp    1.1.23a; 2000/06/03  hof: bugfix anim_preview < 100% did not work
 *                                   (the layer tattoos in a duplicated image may differ from the original !!)
 * gimp    1.1.20a; 2000/04/25  hof: support for keyframes, anim_preview
 * version 0.93.04              hof: Window with Info Message if no Source Image was selected in MovePath
 * version 0.90.00;             hof: 1.st (pre) release 14.Dec.1997
 */
#include "config.h"

/* SYTEM (UNIX) includes */ 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

/* GIMP includes */
#include "gtk/gtk.h"
#include "config.h"
#include "libgimp/stdplugins-intl.h"
#include "libgimp/gimp.h"

/* GAP includes */
#include "gap_layer_copy.h"
#include "gap_lib.h"
#include "gap_mov_dialog.h"
#include "gap_mov_exec.h"
#include "gap_pdb_calls.h"

extern      int gap_debug; /* ==0  ... dont print debug infos */

static gint p_mov_call_render(t_mov_data *mov_ptr, t_mov_current *cur_ptr, gint apv_layerstack);
static void p_mov_advance_src_layer(t_mov_current *cur_ptr, int src_stepmode);
static void p_mov_advance_src_frame(t_mov_current *cur_ptr, t_mov_values  *pvals);
static long   p_mov_execute(t_mov_data *mov_ptr);
static gdouble  p_calc_angle(gint p1x, gint p1y, gint p2x, gint p2y);
static gdouble  p_rotatate_less_than_180(gdouble angle, gdouble angle_new, gint *turns);

/* ============================================================================
 * p_mov_call_render
 *  load current frame, render and save back to disk
 *  for animted_preview
 * ============================================================================
 */

gint
p_mov_call_render(t_mov_data *mov_ptr, t_mov_current *cur_ptr, gint apv_layerstack)
{
  t_anim_info *ainfo_ptr;
  gint32  l_tmp_image_id;
  gint32  l_layer_id;
  int     l_rc;
  char    *l_fname;
  char    *l_name;

  l_rc = 0;
  ainfo_ptr = mov_ptr->dst_ainfo_ptr;


  if(mov_ptr->val_ptr->apv_mlayer_image < 0)
  {
    /* We are generating the Animation on the ORIGINAL FRAMES */
    if(ainfo_ptr->new_filename != NULL) g_free(ainfo_ptr->new_filename);
    ainfo_ptr->new_filename = p_alloc_fname(ainfo_ptr->basename,
                                	cur_ptr->dst_frame_nr,
                                	ainfo_ptr->extension);
    if(ainfo_ptr->new_filename == NULL)
       return -1;

    /* load next frame to render */
    l_tmp_image_id = p_load_image(ainfo_ptr->new_filename);
    if(l_tmp_image_id < 0)
      return -1;

    /* call render procedure for current image */
    if(0 == p_mov_render(l_tmp_image_id, mov_ptr->val_ptr, cur_ptr))
    {
      /* if OK: save the rendered frame back to disk */
      if(p_save_named_frame(l_tmp_image_id, ainfo_ptr->new_filename) < 0)
	l_rc = -1;
    }
    else l_rc = -1;
  }
  else
  {
    /* We are generating an ANIMATED PREVIEW multilayer image */
    if(mov_ptr->val_ptr->apv_src_frame >= 0)
    {
       /* anim preview uses one constant (prescaled) frame */
       l_tmp_image_id = gimp_image_duplicate(mov_ptr->val_ptr->apv_src_frame);
    }
    else
    {
       /* anim preview exact mode uses original frames */
       if(ainfo_ptr->new_filename != NULL) g_free(ainfo_ptr->new_filename);
       ainfo_ptr->new_filename = p_alloc_fname(ainfo_ptr->basename,
                                	   cur_ptr->dst_frame_nr,
                                	   ainfo_ptr->extension);
       l_tmp_image_id = p_load_image(ainfo_ptr->new_filename);
       if(l_tmp_image_id < 0)
	 return -1;

       if((mov_ptr->val_ptr->apv_scalex != 100.0) || (mov_ptr->val_ptr->apv_scaley != 100.0))
       {
         GimpParam     *l_params;
         gint        l_retvals;
	 gint32      l_size_x, l_size_y;
       
         l_size_x = (gimp_image_width(l_tmp_image_id) * mov_ptr->val_ptr->apv_scalex) / 100;
         l_size_y = (gimp_image_height(l_tmp_image_id) * mov_ptr->val_ptr->apv_scaley) / 100;
       
         l_params = gimp_run_procedure ("gimp_image_scale",
			         &l_retvals,
			         GIMP_PDB_IMAGE,    l_tmp_image_id,
			         GIMP_PDB_INT32,    l_size_x,
			         GIMP_PDB_INT32,    l_size_y,
			         GIMP_PDB_END);
       }
    }
    
    /* call render procedure for current image */
    if(0 == p_mov_render(l_tmp_image_id, mov_ptr->val_ptr, cur_ptr))
    {
      /* if OK and optional save to gap_paste-buffer */
      if(mov_ptr->val_ptr->apv_gap_paste_buff != NULL)
      {
         l_fname = p_alloc_fname(mov_ptr->val_ptr->apv_gap_paste_buff,
                                 cur_ptr->dst_frame_nr,
                                 ".xcf");
         p_save_named_frame(l_tmp_image_id, l_fname);
      }
      
      /* flatten the rendered frame */
      l_layer_id = gimp_image_flatten(l_tmp_image_id);
      if(l_layer_id < 0)
      {
        if(gap_debug) printf("p_mov_call_render: flattened layer_id:%d\n", (int)l_layer_id);
        /* hof:
	 * if invisible layers are flattened on an empty image
	 * we do not get a resulting layer (returned l_layer_id == -1)
	 *
	 *  I'm not sure if is this a bug, but here is a workaround:
	 *
	 * In that case I add a dummy layer 1x1 pixel (at offest -1,-1)
	 * and flatten again, and it works (tested with gimp-1.1.19)
	 */
        l_layer_id = gimp_layer_new(l_tmp_image_id, "dummy",
                                 1, 
				 1,
				 ((gint)(gimp_image_base_type(l_tmp_image_id)) * 2),
                                 100.0,     /* Opacity full opaque */     
                                 0);        /* NORMAL */
         gimp_image_add_layer(l_tmp_image_id, l_layer_id, 0);
	 gimp_layer_set_offsets(l_layer_id, -1, -1);
        l_layer_id = gimp_image_flatten(l_tmp_image_id);
	
      }
      gimp_layer_add_alpha(l_layer_id);
      
      if(gap_debug)
      {
        printf("p_mov_call_render: flattened layer_id:%d\n", (int)l_layer_id);
        printf("p_mov_call_render: tmp_image_id:%d  apv_mlayer_image:%d\n",
	        (int)l_tmp_image_id, (int)mov_ptr->val_ptr->apv_mlayer_image);
      }

      /* set layername (including delay for the framerate) */
      l_name = g_strdup_printf("frame_%04d (%dms)"
                              , (int) cur_ptr->dst_frame_nr
                              , (int)(1000/mov_ptr->val_ptr->apv_framerate));
      gimp_layer_set_name(l_layer_id, l_name);
      g_free(l_name);
      
      /* remove (its only) layer from source */
      gimp_image_remove_layer(l_tmp_image_id, l_layer_id);

      /* and set the dst_image as it's new Master */
      p_gimp_drawable_set_image(l_layer_id, mov_ptr->val_ptr->apv_mlayer_image);

      /* add the layer to the anim preview multilayer image */
      gimp_image_add_layer (mov_ptr->val_ptr->apv_mlayer_image, l_layer_id, apv_layerstack);
    }
    else l_rc = -1;
  }
    

  /* destroy the tmp image */
  gimp_image_delete(l_tmp_image_id);

  return l_rc;
}	/* end p_mov_call_render */
 

/* ============================================================================
 * p_mov_advance_src_layer
 * advance layer index according to stepmode
 * ============================================================================
 */
void p_mov_advance_src_layer(t_mov_current *cur_ptr, int src_stepmode)
{
  static int l_ping = -1;

  if(gap_debug) printf("p_mov_advance_src_layer: stepmode=%d last_layer=%d idx=%d\n",
                       (int)src_stepmode,
                       (int)cur_ptr->src_last_layer,
                       (int)cur_ptr->src_layer_idx
                      ); 

  /* note: top layer has index 0
   *       therfore reverse loops have to count up
   */
  if((cur_ptr->src_last_layer > 0 ) && (src_stepmode != GAP_STEP_NONE))
  {
    switch(src_stepmode)
    {
      case GAP_STEP_ONCE_REV:
        cur_ptr->src_layer_idx++;
        if(cur_ptr->src_layer_idx > cur_ptr->src_last_layer)
        {
           cur_ptr->src_layer_idx = cur_ptr->src_last_layer;
        }
        break;
      case GAP_STEP_ONCE:
        cur_ptr->src_layer_idx--;
        if(cur_ptr->src_layer_idx < 0)
        {
           cur_ptr->src_layer_idx = 0;
        }
        break;
      case GAP_STEP_PING_PONG:
        cur_ptr->src_layer_idx += l_ping;
        if(l_ping < 0)
        {
          if(cur_ptr->src_layer_idx < 0)
          {
             cur_ptr->src_layer_idx = 1;
             l_ping = 1;
          }
        }
        else
        {
          if(cur_ptr->src_layer_idx > cur_ptr->src_last_layer)
          {
             cur_ptr->src_layer_idx = cur_ptr->src_last_layer - 1;
             l_ping = -1;
          }
        }

        break;
      case GAP_STEP_LOOP_REV:
        cur_ptr->src_layer_idx++;
        if(cur_ptr->src_layer_idx > cur_ptr->src_last_layer)
        {
           cur_ptr->src_layer_idx = 0;
        }
        break;
      case GAP_STEP_LOOP:
      default:
        cur_ptr->src_layer_idx--;
        if(cur_ptr->src_layer_idx < 0)
        {
           cur_ptr->src_layer_idx = cur_ptr->src_last_layer;
        }
        break;

    }
  }
}	/* end  p_advance_src_layer */



/* ============================================================================
 * p_mov_advance_src_frame
 *   advance chached image to next source frame according to FRAME based pvals->stepmode
 * ============================================================================
 */
static void
p_mov_advance_src_frame(t_mov_current *cur_ptr, t_mov_values  *pvals)
{
  static int l_ping = 1;

  if(pvals->src_stepmode != GAP_STEP_FRAME_NONE)
  {
    if(pvals->cache_ainfo_ptr == NULL )
    {
      pvals->cache_ainfo_ptr =  p_alloc_ainfo(pvals->src_image_id, GIMP_RUN_NONINTERACTIVE);
    }

    if(pvals->cache_ainfo_ptr->first_frame_nr < 0)
    {
       p_dir_ainfo(pvals->cache_ainfo_ptr);
    }
  }

  if(gap_debug) printf("p_mov_advance_src_frame: stepmode=%d frame_cnt=%d first_frame=%d last_frame=%d idx=%d\n",
                       (int)pvals->src_stepmode,
                       (int)pvals->cache_ainfo_ptr->frame_cnt,
                       (int)pvals->cache_ainfo_ptr->first_frame_nr,
                       (int)pvals->cache_ainfo_ptr->last_frame_nr,
                       (int)cur_ptr->src_frame_idx
                      ); 

  if((pvals->cache_ainfo_ptr->frame_cnt > 1 ) && (pvals->src_stepmode != GAP_STEP_FRAME_NONE))
  {
    switch(pvals->src_stepmode)
    {
      case GAP_STEP_FRAME_ONCE_REV:
        cur_ptr->src_frame_idx--;
        if(cur_ptr->src_frame_idx < pvals->cache_ainfo_ptr->first_frame_nr)
        {
           cur_ptr->src_frame_idx = pvals->cache_ainfo_ptr->first_frame_nr;
        }
        break;
      case GAP_STEP_FRAME_ONCE:
        cur_ptr->src_frame_idx++;
        if(cur_ptr->src_frame_idx > pvals->cache_ainfo_ptr->last_frame_nr)
        {
           cur_ptr->src_frame_idx = pvals->cache_ainfo_ptr->last_frame_nr;
        }
        break;
      case GAP_STEP_FRAME_PING_PONG:
        cur_ptr->src_frame_idx += l_ping;
        if(l_ping < 0)
        {
          if(cur_ptr->src_frame_idx < pvals->cache_ainfo_ptr->first_frame_nr)
          {
             cur_ptr->src_frame_idx = pvals->cache_ainfo_ptr->first_frame_nr + 1;
             l_ping = 1;
          }
        }
        else
        {
          if(cur_ptr->src_frame_idx > pvals->cache_ainfo_ptr->last_frame_nr)
          {
             cur_ptr->src_frame_idx = pvals->cache_ainfo_ptr->last_frame_nr - 1;
             l_ping = -1;
          }
        }
        break;
      case GAP_STEP_FRAME_LOOP_REV:
        cur_ptr->src_frame_idx--;
        if(cur_ptr->src_frame_idx < pvals->cache_ainfo_ptr->first_frame_nr)
        {
           cur_ptr->src_frame_idx = pvals->cache_ainfo_ptr->last_frame_nr;
        }
        break;
      case GAP_STEP_FRAME_LOOP:
      default:
        cur_ptr->src_frame_idx++;
        if(cur_ptr->src_frame_idx > pvals->cache_ainfo_ptr->last_frame_nr)
        {
           cur_ptr->src_frame_idx = pvals->cache_ainfo_ptr->first_frame_nr;
        }
        break;

    }
    
    p_fetch_src_frame(pvals, cur_ptr->src_frame_idx);
  }
}	/* end  p_advance_src_frame */


/* ============================================================================
 * p_mov_execute
 * Copy layer(s) from Sourceimage to given destination frame range,
 * varying koordinates and opacity of the copied layer.
 * To each affected destination frame exactly one copy of a source layer is added.
 * The source layer is iterated through all layers of the sourceimage
 * according to stemmode parameter.
 * For the placement the layers act as if their size is equal to their
 * Sourceimages size.
 * ============================================================================
 */

/* TODO: add keyframe support */

long
p_mov_execute(t_mov_data *mov_ptr)
{
/* MIX_VALUE  0.0 <= factor <= 1.0
 *  result is a  for factor 0.0
 *            b  for factor 1.0
 *            mix for factors inbetween
 */
#define MIX_VALUE(factor, a, b) ((a * (1.0 - factor)) +  (b * factor))
  gint l_idx;
   t_mov_current l_current_data;
   t_mov_current *cur_ptr;
   t_mov_values  *val_ptr;

   gdouble  l_percentage;  
   gdouble  l_fpl;             /* frames_per_line */
   gdouble  l_flt_posfactor;
   long     l_frame_step;
   gdouble  l_frames;
   long     l_cnt;
   long     l_points;
   long     l_ptidx;
   long     l_prev_keyptidx;
   long     l_fridx;
   gdouble  l_flt_count;
   gint     l_rc;
   gint     l_nlayers;
   gint     l_idk;
   gint     l_prev_keyframe;
   gint     l_apv_layerstack;
   gdouble  l_flt_timing[GAP_MOV_MAX_POINT];   /* timing table in relative frame numbers (0.0 == the first handled frame) */
   
   
   if(mov_ptr->val_ptr->src_image_id < 0)
   {
      p_msg_win(mov_ptr->dst_ainfo_ptr->run_mode,
        _("No Source Image was selected.\n"
	  "Please open a 2nd Image of the same type before opening Move Path."));
      return -1;
   }

  l_apv_layerstack = 0;
  l_percentage = 0.0;  
  if(mov_ptr->dst_ainfo_ptr->run_mode == GIMP_RUN_INTERACTIVE)
  { 
    if(mov_ptr->val_ptr->apv_mlayer_image < 0)
    {
      gimp_progress_init( _("Copying Layers into Frames..."));
    }
    else
    {
      gimp_progress_init( _("Generating Animated Preview..."));
    }
  }

  if(gap_debug)
  {
    printf("p_mov_execute: values got from dialog:\n");
    printf("apv_mlayer_image: %ld\n", (long)mov_ptr->val_ptr->apv_mlayer_image);
    printf("apv_mode: %ld\n", (long)mov_ptr->val_ptr->apv_mode);
    printf("apv_scale x: %f y:%f\n", (float)mov_ptr->val_ptr->apv_scalex, (float)mov_ptr->val_ptr->apv_scaley);
    if(mov_ptr->val_ptr->apv_gap_paste_buff)
    {
      printf("apv_gap_paste_buf: %s\n", mov_ptr->val_ptr->apv_gap_paste_buff);
    }
    else
    {
      printf("apv_gap_paste_buf: ** IS NULL ** (do not copy to paste buffer)\n");
    }
    printf("src_image_id :%ld\n", (long)mov_ptr->val_ptr->src_image_id);
    printf("src_layer_id :%ld\n", (long)mov_ptr->val_ptr->src_layer_id);
    printf("src_handle :%d\n", mov_ptr->val_ptr->src_handle);
    printf("src_stepmode :%d\n", mov_ptr->val_ptr->src_stepmode);
    printf("src_paintmode :%d\n", mov_ptr->val_ptr->src_paintmode);
    printf("clip_to_img :%d\n", mov_ptr->val_ptr->clip_to_img);
    printf("dst_range_start :%d\n", (int)mov_ptr->val_ptr->dst_range_start);
    printf("dst_range_end :%d\n", (int)mov_ptr->val_ptr->dst_range_end);
    printf("dst_layerstack :%d\n", (int)mov_ptr->val_ptr->dst_layerstack);
    for(l_idx = 0; l_idx <= mov_ptr->val_ptr->point_idx_max; l_idx++)
    {
      printf("p_x[%d] :%d\n", l_idx, mov_ptr->val_ptr->point[l_idx].p_x);
      printf("p_y[%d] : :%d\n", l_idx, mov_ptr->val_ptr->point[l_idx].p_y);
      printf("opacity[%d] :%d\n", l_idx, mov_ptr->val_ptr->point[l_idx].opacity);
      printf("w_resize[%d] :%d\n", l_idx, mov_ptr->val_ptr->point[l_idx].w_resize);
      printf("h_resize[%d] :%d\n", l_idx, mov_ptr->val_ptr->point[l_idx].h_resize);
      printf("rotation[%d] :%d\n", l_idx, mov_ptr->val_ptr->point[l_idx].rotation);
      printf("keyframe[%d] :%d\n", l_idx, mov_ptr->val_ptr->point[l_idx].keyframe);
      printf("keyframe_abs[%d] :%d\n", l_idx, mov_ptr->val_ptr->point[l_idx].keyframe_abs);
    }
    printf("\n");
  }

   l_rc    = 0;
   cur_ptr = &l_current_data;
   val_ptr = mov_ptr->val_ptr;

   /* set offsets (in cur_ptr)  according to handle mode and src_img dimension */
   p_set_handle_offsets(val_ptr, cur_ptr);
    
   
   /* test for invers range */
   if(val_ptr->dst_range_start > val_ptr->dst_range_end)
   {
      /* step down */
      l_frame_step = -1;
      l_cnt = 1 + (val_ptr->dst_range_start - val_ptr->dst_range_end);
   }
   else
   {
      l_frame_step = 1;
      l_cnt = 1 + (val_ptr->dst_range_end - val_ptr->dst_range_start);
   }

   l_frames = (gdouble)l_cnt;              /* nr. of affected frames */
   l_points = val_ptr->point_idx_max +1;   /* nr. of available points */
   
   if(l_points > l_frames)
   {
      /* cut off some points if we got more than frames */
      l_points = l_cnt;
   }

   if(l_points < 2)
   {
      /* copy point[0] to point [1] because we need at least 2
       * points for the algorithms below to work. 
       * (simulates a line with lenght 0, to move along)
       */
      if(gap_debug) printf("p_mov_execute: added a 2nd Point\n");
      val_ptr->point[1].p_x = val_ptr->point[0].p_x;
      val_ptr->point[1].p_y = val_ptr->point[0].p_y;
      val_ptr->point[1].opacity = val_ptr->point[0].opacity;
      val_ptr->point[1].w_resize = val_ptr->point[0].w_resize;
      val_ptr->point[1].h_resize = val_ptr->point[0].h_resize;
      val_ptr->point[1].rotation = val_ptr->point[0].rotation;
      l_points = 2;
   }
   
   
   cur_ptr->dst_frame_nr = val_ptr->dst_range_start;
   cur_ptr->src_layers = NULL;

   if(mov_ptr->val_ptr->src_stepmode < GAP_STEP_FRAME)
   {   
     cur_ptr->src_layers = gimp_image_get_layers (val_ptr->src_image_id, &l_nlayers);
     if(cur_ptr->src_layers == NULL)
     {
       printf("ERROR (in p_mov_execute): Got no layers from SrcImage\n");
       return -1;
     }
     if(l_nlayers < 1)
     {
       printf("ERROR (in p_mov_execute): Source Image has no layers\n");
       return -1;
     }
     cur_ptr->src_last_layer = l_nlayers -1;

     /* findout index of src_layer_id */
     for(cur_ptr->src_layer_idx = 0; 
	 cur_ptr->src_layer_idx  < l_nlayers;
	 cur_ptr->src_layer_idx++)
     {
	if(cur_ptr->src_layers[cur_ptr->src_layer_idx] == val_ptr->src_layer_id)
           break;
     }
     cur_ptr->src_last_layer = l_nlayers -1;   /* index of last layer */
     }
   else
   {
     /* for FRAME stepmodes we use flattened Sorce frames
      * (instead of one multilayer source image )
      */
     p_fetch_src_frame (val_ptr, -1);  /* negative value fetches the selected frame number */
     cur_ptr->src_frame_idx = val_ptr->cache_ainfo_ptr->curr_frame_nr;
     
     if((val_ptr->cache_ainfo_ptr->first_frame_nr < 0)
     && (val_ptr->src_stepmode != GAP_STEP_FRAME_NONE))
     {
        p_dir_ainfo(val_ptr->cache_ainfo_ptr);
     }

     /* set offsets (in cur_ptr)  according to handle mode and cache_tmp_img dimension */
     p_set_handle_offsets(val_ptr, cur_ptr);
   }
   
   cur_ptr->currX   = (gdouble)val_ptr->point[0].p_x;
   cur_ptr->currY   = (gdouble)val_ptr->point[0].p_y;
   cur_ptr->currOpacity  = (gdouble)val_ptr->point[0].opacity;
   cur_ptr->currWidth    = (gdouble)val_ptr->point[0].w_resize;
   cur_ptr->currHeight   = (gdouble)val_ptr->point[0].h_resize;
   cur_ptr->currRotation = (gdouble)val_ptr->point[0].rotation;

   /* RENDER add current src_layer to current frame */
   l_rc = p_mov_call_render(mov_ptr, cur_ptr, l_apv_layerstack);

   if(gap_debug) printf("p_mov_execute: l_points=%d\n", (int)l_points);
   
   /* how many frames are affected from one line of the moving path */   
   l_fpl = ((gdouble)l_frames - 1.0) / ((gdouble)(l_points -1));
   if(gap_debug) printf("p_mov_execute: initial l_fpl=%f\n", l_fpl);

   /* calculate l_flt_timing controlpoint timing table considering keyframes */
   l_prev_keyptidx = 0;
   l_prev_keyframe = 0;
   l_flt_timing[0] = 0.0;
   l_flt_timing[l_points -1] = l_frames -1;
   l_flt_count = 0.0;
   for(l_ptidx=1;  l_ptidx < l_points - 1; l_ptidx++)
   {
     /* search for keyframes */
     if(l_ptidx > l_prev_keyptidx)
     {
       for(l_idk = l_ptidx; l_idk < l_points; l_idk++)
       {
          if(l_idk == l_points -1)
	  {
	    /* last point is always an implicite  keyframe */
            l_fpl = ((gdouble)((l_frames -1) - l_prev_keyframe)) / ((gdouble)((l_idk -  l_ptidx) +1));
            l_prev_keyframe = l_frames -1;

            l_prev_keyptidx = l_idk;
            if(gap_debug) printf("p_mov_execute: last point is implicite keyframe l_fpl=%f\n", l_fpl);
            break;
	  }
	  else
	  {
	    if (val_ptr->point[l_idk].keyframe > 0)
	    {
	      /* found a keyframe, have to recalculate frames_per_line */
              l_fpl = ((gdouble)(val_ptr->point[l_idk].keyframe - l_prev_keyframe)) / ((gdouble)((l_idk -  l_ptidx) +1));
              l_prev_keyframe = val_ptr->point[l_idk].keyframe;

              l_prev_keyptidx = l_idk;
              if(gap_debug) printf("p_mov_execute: keyframe l_fpl=%f\n", l_fpl);
              break;
	    }
	  }
       }
     }
     l_flt_count += l_fpl;
     l_flt_timing[l_ptidx] = l_flt_count;

     if(l_fpl < 1.0)
     {
	printf("p_mov_execute: ** Error frames per line at point[%d] = %f  (is less than 1.0 !!)\n",
	  (int)l_ptidx, (float)l_fpl);
     }
   }

   if(gap_debug)
   {
     printf("p_mov_execute: --- CONTROLPOINT relative frametiming TABLE -----\n");
     for(l_ptidx=0;  l_ptidx < l_points; l_ptidx++)
     {
       printf("p_mov_execute: l_flt_timing[%02d] = %f\n", (int)l_ptidx, (float)l_flt_timing[l_ptidx]);
     }
   }

    
  /* loop for each frame within the range (may step up or down) */
  l_ptidx = 1;
  cur_ptr->dst_frame_nr = val_ptr->dst_range_start;
  for(l_fridx = 1; l_fridx < l_cnt; l_fridx++)
  {
     
     if(gap_debug) printf("\np_mov_execute: l_fridx=%ld, l_flt_timing[l_ptidx]=%f, l_rc=%d l_ptidx=%d, l_prev_keyptidx=%d\n",
                           l_fridx, (float)l_flt_timing[l_ptidx], (int)l_rc, (int)l_ptidx, (int)l_prev_keyptidx);

     if(l_rc != 0) break;

      /* advance frame_nr, (1st frame was done outside this loop) */
      cur_ptr->dst_frame_nr += l_frame_step;  /* +1  or -1 */

      if((gdouble)l_fridx > l_flt_timing[l_ptidx])
      {
         /* change deltas for next line of the move path */
	 if(l_ptidx < l_points-1)
	 {
           l_ptidx++;
	   if(gap_debug)
	   {
	      printf("p_mov_execute: advance to controlpoint l_ptidx=%d, l_flt_timing[l_ptidx]=%f\n"
	             , (int)l_ptidx, (float)l_flt_timing[l_ptidx]);
	   }
	 }
	 else
	 {
	   if(gap_debug) printf("p_mov_execute: ** ERROR overflow l_ptidx=%d\n", (int)l_ptidx);
	 }
      }
      
      l_fpl = (l_flt_timing[l_ptidx] - l_flt_timing[l_ptidx -1]); /* float frames per line */
      if(l_fpl != 0.0)
      {
        l_flt_posfactor  = ((gdouble)l_fridx - l_flt_timing[l_ptidx -1]) / l_fpl;
      }
      else
      {
         l_flt_posfactor = 1.0;
         if(gap_debug) printf("p_mov_execute: ** ERROR l_fpl is 0.0 frames per line\n");
      }
      
 
      if(gap_debug) printf("p_mov_execute: l_fpl=%f, l_flt_posfactor=%f\n", (float)l_fpl, (float)l_flt_posfactor);

      l_flt_posfactor = CLAMP (l_flt_posfactor, 0.0, 1.0);
     
      cur_ptr->currX  =       MIX_VALUE(l_flt_posfactor, (gdouble)val_ptr->point[l_ptidx -1].p_x,      (gdouble)val_ptr->point[l_ptidx].p_x);
      cur_ptr->currY  =       MIX_VALUE(l_flt_posfactor, (gdouble)val_ptr->point[l_ptidx -1].p_y,      (gdouble)val_ptr->point[l_ptidx].p_y);
      cur_ptr->currOpacity =  MIX_VALUE(l_flt_posfactor, (gdouble)val_ptr->point[l_ptidx -1].opacity,  (gdouble)val_ptr->point[l_ptidx].opacity);
      cur_ptr->currWidth =    MIX_VALUE(l_flt_posfactor, (gdouble)val_ptr->point[l_ptidx -1].w_resize, (gdouble)val_ptr->point[l_ptidx].w_resize);
      cur_ptr->currHeight =   MIX_VALUE(l_flt_posfactor, (gdouble)val_ptr->point[l_ptidx -1].h_resize, (gdouble)val_ptr->point[l_ptidx].h_resize);
      cur_ptr->currRotation = MIX_VALUE(l_flt_posfactor, (gdouble)val_ptr->point[l_ptidx -1].rotation, (gdouble)val_ptr->point[l_ptidx].rotation);

      if(gap_debug)
      {
          printf("ROTATE [%02d] %d    [%02d] %d       MIX: %f\n"
          , (int)l_ptidx-1,  (int)val_ptr->point[l_ptidx -1].rotation
          , (int)l_ptidx,    (int)val_ptr->point[l_ptidx].rotation
	  , (float)cur_ptr->currRotation);
      }

       if(val_ptr->src_stepmode < GAP_STEP_FRAME )
       {
         /* advance settings for next src layer */
         p_mov_advance_src_layer(cur_ptr, val_ptr->src_stepmode);
       }

       else
       {
         /* advance settings for next source frame */
         p_mov_advance_src_frame(cur_ptr, val_ptr);
       }

       if(l_frame_step < 0)
       {
         /* if we step down, we have to insert the layer
	  * as lowest layer in the existing layerstack
	  * of the animated preview multilayer image.
	  * (if we step up, we always use 0 as l_apv_layerstack,
	  *  that means always insert on top of the layerstack)
	  */
         l_apv_layerstack++;
       }
       /* RENDER add current src_layer to current frame */
       l_rc = p_mov_call_render(mov_ptr, cur_ptr, l_apv_layerstack);

       /* show progress */
       if(mov_ptr->dst_ainfo_ptr->run_mode == GIMP_RUN_INTERACTIVE)
       { 
         l_percentage = (gdouble)l_fridx / (gdouble)(l_cnt -1);
         gimp_progress_update (l_percentage);
       }
           
   }

   if(cur_ptr->src_layers != NULL) g_free(cur_ptr->src_layers);
   
   cur_ptr->src_layers = NULL;
   
   
   return l_rc;  

}	/* end p_mov_execute */


/* ============================================================================
 * p_mov_anim_preview
 *   Generate an animate preview for the move path
 * ============================================================================
 */
gint32 
p_mov_anim_preview(t_mov_values *pvals_orig, t_anim_info *ainfo_ptr, gint preview_frame_nr)
{
  t_mov_data    apv_mov_data;
  t_mov_values  apv_mov_vals;
  t_mov_data    *l_mov_ptr;
  t_mov_values  *l_pvals;
  gint        l_idx;
  gint32      l_size_x, l_size_y;
  gint32      l_tmp_image_id;
  gint32      l_tmp_frame_id;
  gint32      l_mlayer_image_id;
  GimpParam     *l_params;
  gint        l_retvals;
  GimpImageBaseType  l_type;
  guint       l_width, l_height;
  gint32      l_stackpos;
  gint        l_nlayers;
  gint32     *l_src_layers;
  gint        l_rc;
  
  l_mov_ptr = &apv_mov_data;
  l_pvals = &apv_mov_vals;
  
  /* copy settings */
  memcpy(l_pvals, pvals_orig, sizeof(t_mov_values));
  l_mov_ptr->val_ptr = l_pvals;
  l_mov_ptr->dst_ainfo_ptr = ainfo_ptr;
  
  /* init local cached src image for anim preview generation.
   * (never mix cached src image for normal and anim preview
   *  because anim previews are often scaled down)
   */
  l_pvals->cache_src_image_id  = -1;  
  l_pvals->cache_tmp_image_id  = -1;
  l_pvals->cache_tmp_layer_id  = -1;
  l_pvals->cache_frame_number  = -1;
  l_pvals->cache_ainfo_ptr = NULL;

  /* -1 assume no tmp_image (use unscaled original source) */
  l_tmp_image_id = -1;
  l_stackpos = 0;

  /* Scale (down) needed ? */
  if((l_pvals->apv_scalex != 100.0) || (l_pvals->apv_scaley != 100.0))
  {
    /* scale the controlpoint koords */
    for(l_idx = 0; l_idx <= l_pvals->point_idx_max; l_idx++)
    {
      l_pvals->point[l_idx].p_x  = (l_pvals->point[l_idx].p_x * l_pvals->apv_scalex) / 100;
      l_pvals->point[l_idx].p_y  = (l_pvals->point[l_idx].p_y * l_pvals->apv_scaley) / 100;
    }

    /* for the FRAME based step modes we cant Scale here,
     * we have to scale later (at fetch time of the frame)
     */
    if(l_pvals->src_stepmode < GAP_STEP_FRAME)
    {
      /* copy and scale the source object image */
      l_tmp_image_id = gimp_image_duplicate(pvals_orig->src_image_id);
      l_pvals->src_image_id = l_tmp_image_id;

      l_size_x = MAX(1, (gimp_image_width(l_tmp_image_id) * l_pvals->apv_scalex) / 100);
      l_size_y = MAX(1, (gimp_image_height(l_tmp_image_id) * l_pvals->apv_scaley) / 100);
      l_params = gimp_run_procedure ("gimp_image_scale",
  		                   &l_retvals,
			           GIMP_PDB_IMAGE,    l_tmp_image_id,
			           GIMP_PDB_INT32,    l_size_x,
			           GIMP_PDB_INT32,    l_size_y,
			           GIMP_PDB_END);

       /* findout the src_layer id in the scaled copy by stackpos index */
       l_pvals->src_layer_id = -1;
       l_src_layers = gimp_image_get_layers (pvals_orig->src_image_id, &l_nlayers);
       if(l_src_layers == NULL)
       {
	 printf("ERROR: p_mov_anim_preview GOT no src_layers (original image_id %d)\n",
        	 (int)pvals_orig->src_image_id);
       }
       else
       {
	 for(l_stackpos = 0; 
	     l_stackpos  < l_nlayers;
	     l_stackpos++)
	 {
	    if(l_src_layers[l_stackpos] == pvals_orig->src_layer_id)
               break;
	 }
	 g_free(l_src_layers);

	 l_src_layers = gimp_image_get_layers (l_tmp_image_id, &l_nlayers);
	 if(l_src_layers == NULL)
	 {
           printf("ERROR: p_mov_anim_preview GOT no src_layers (scaled copy image_id %d)\n",
        	  (int)l_tmp_image_id);
	 }
	 else
	 {
            l_pvals->src_layer_id = l_src_layers[l_stackpos];
            g_free(l_src_layers);
	 }

       }

      if(gap_debug)
      {
	printf("p_mov_anim_preview: orig  src_image_id:%d src_layer:%d, stackpos:%d\n"
               ,(int)pvals_orig->src_image_id
	       ,(int)pvals_orig->src_layer_id
	       ,(int)l_stackpos);
	printf("   Scaled src_image_id:%d scaled_src_layer:%d\n"
               ,(int)l_tmp_image_id
	       ,(int)l_pvals->src_layer_id );
      }
    }

    
  }


  if(gap_debug)
  {
    printf("p_mov_anim_preview: src_image_id %d (orig:%d)\n"
           , (int) l_pvals->src_image_id
	   , (int) pvals_orig->src_image_id);
  }
  
  /* create the animated preview multilayer image in (scaled) framesize */
  l_width  = (gimp_image_width(ainfo_ptr->image_id) * l_pvals->apv_scalex) / 100;
  l_height = (gimp_image_height(ainfo_ptr->image_id) * l_pvals->apv_scaley) / 100;
  l_type   = gimp_image_base_type(ainfo_ptr->image_id);
  
  l_mlayer_image_id = gimp_image_new(l_width, l_height,l_type);
  l_pvals->apv_mlayer_image = l_mlayer_image_id;

  if(gap_debug)
  {
    printf("p_mov_anim_preview: apv_mlayer_image %d\n"
           , (int) l_pvals->apv_mlayer_image);
  }
  
  /* APV_MODE (Wich frames to use in the preview?)  */
  switch(l_pvals->apv_mode)
  {
    gchar *l_filename;
    
    case GAP_APV_QUICK:
      /* use an empty dummy frame for all frames */    
      l_tmp_frame_id = gimp_image_new(l_width, l_height,l_type);
      break;
    case GAP_APV_ONE_FRAME:
      /* use only one frame in the preview */    
      l_filename = p_alloc_fname(ainfo_ptr->basename,
                                 preview_frame_nr,
                                 ainfo_ptr->extension);
      l_tmp_frame_id =  p_load_image(l_filename);
      if((l_pvals->apv_scalex != 100.0) || (l_pvals->apv_scaley != 100.0))
      {
	l_size_x = (gimp_image_width(l_tmp_frame_id) * l_pvals->apv_scalex) / 100;
	l_size_y = (gimp_image_height(l_tmp_frame_id) * l_pvals->apv_scaley) / 100;
	l_params = gimp_run_procedure ("gimp_image_scale",
  		                   &l_retvals,
			           GIMP_PDB_IMAGE,    l_tmp_frame_id,
			           GIMP_PDB_INT32,    l_size_x,
			           GIMP_PDB_INT32,    l_size_y,
			           GIMP_PDB_END);
      }
      g_free(l_filename);
      break;
    default:  /* GAP_APV_EXACT */
      /* read the original frames for the preview (slow) */
      l_tmp_frame_id = -1; 
      break;
  }
  l_pvals->apv_src_frame = l_tmp_frame_id;

  if(gap_debug)
  {
    printf("p_mov_anim_preview: apv_src_frame %d\n"
           , (int) l_pvals->apv_src_frame);
  }


  /* EXECUTE move path in preview Mode */ 
  /* --------------------------------- */ 
  l_rc = p_mov_execute(l_mov_ptr);

  if(l_pvals->cache_tmp_image_id >= 0)
  {
     if(gap_debug)
     {
	printf("p_mov_anim_preview: DELETE cache_tmp_image_id:%d\n",
	         (int)l_pvals->cache_tmp_image_id);
     }
     /* destroy the cached frame image */
     gimp_image_delete(l_pvals->cache_tmp_image_id);
     l_pvals->cache_tmp_image_id = -1;
  }
  
  if(l_rc < 0)
  {
    return(-1);
  }
  
  /* add a display for the animated preview multilayer image */
  gimp_display_new(l_mlayer_image_id);
 
  /* delete the scaled copy of the src image (if there is one) */
  if(l_tmp_image_id >= 0)
  { 
    gimp_image_delete(l_tmp_image_id);
  }
  /* delete the (scaled) dummy frames (if there is one) */
  if(l_tmp_frame_id >= 0)
  { 
    gimp_image_delete(l_tmp_frame_id);
  }

  return(l_mlayer_image_id);
}	/* end p_mov_anim_preview */

/* ============================================================================
 * p_con_keyframe
 * ============================================================================
 */

gint
p_conv_keyframe_to_rel(gint abs_keyframe, t_mov_values *pvals)
{
    if(pvals->dst_range_start <= pvals->dst_range_end)
    {
       return (abs_keyframe -  pvals->dst_range_start);
    }
    return (pvals->dst_range_start - abs_keyframe);
}

gint
p_conv_keyframe_to_abs(gint rel_keyframe, t_mov_values *pvals)
{
    if(pvals->dst_range_start <= pvals->dst_range_end)
    {
      return(rel_keyframe + pvals->dst_range_start);
    }
    return(pvals->dst_range_start - rel_keyframe);
}

/* ============================================================================
 * p_gap_save_pointfile
 * ============================================================================
 */
gint
p_gap_save_pointfile(char *filename, t_mov_values *pvals)
{
  FILE *l_fp;
  gint l_idx;
  
  if(filename == NULL) return -1;
  
  l_fp = fopen(filename, "w+");
  if(l_fp != NULL)
  {
    fprintf(l_fp, "# GAP file contains saved Move Path Point Table\n");
    fprintf(l_fp, "%d  %d  # current_point  points\n",
                  (int)pvals->point_idx,
                  (int)pvals->point_idx_max + 1);
    fprintf(l_fp, "# x  y   width height opacity rotation [rel_keyframe]\n");
    for(l_idx = 0; l_idx <= pvals->point_idx_max; l_idx++)
    {
      if((l_idx > 0) 
      && (l_idx < pvals->point_idx_max)
      && ((int)pvals->point[l_idx].keyframe > 0))
      {
	fprintf(l_fp, "%04d %04d  %03d %03d  %03d %d %d\n",
                     (int)pvals->point[l_idx].p_x,
                     (int)pvals->point[l_idx].p_y,
                     (int)pvals->point[l_idx].w_resize,
                     (int)pvals->point[l_idx].h_resize,
                     (int)pvals->point[l_idx].opacity,
                     (int)pvals->point[l_idx].rotation,
                     (int)p_conv_keyframe_to_rel(pvals->point[l_idx].keyframe_abs, pvals));
      }
      else
      {
	fprintf(l_fp, "%04d %04d  %03d %03d  %03d %d\n",
                     (int)pvals->point[l_idx].p_x,
                     (int)pvals->point[l_idx].p_y,
                     (int)pvals->point[l_idx].w_resize,
                     (int)pvals->point[l_idx].h_resize,
                     (int)pvals->point[l_idx].opacity,
                     (int)pvals->point[l_idx].rotation);
      }
    }
     
    fclose(l_fp);
    return 0;
  }
  return -1;
}

/* ============================================================================
 * p_gap_load_pointfile
 * ============================================================================
 */
gint
p_gap_load_pointfile(char *filename, t_mov_values *pvals)
{
#define POINT_REC_MAX 128

  FILE *l_fp;
  gint   l_idx;
  char  l_buff[POINT_REC_MAX +1 ];
  char *l_ptr;
  gint   l_cnt;
  gint   l_rc;
  gint   l_v1, l_v2, l_v3, l_v4, l_v5, l_v6, l_v7;

  l_rc = -1;
  if(filename == NULL) return(l_rc);
  
  l_fp = fopen(filename, "r");
  if(l_fp != NULL)
  {
    l_idx = -1;
    while (NULL != fgets (l_buff, POINT_REC_MAX, l_fp))
    {
       /* skip leading blanks */
       l_ptr = l_buff;
       while(*l_ptr == ' ') { l_ptr++; }
       
       /* check if line empty or comment only (starts with '#') */
       if((*l_ptr != '#') && (*l_ptr != '\n') && (*l_ptr != '\0'))
       {
         l_cnt = sscanf(l_ptr, "%d%d%d%d%d%d%d", &l_v1, &l_v2, &l_v3, &l_v4, &l_v5, &l_v6, &l_v7);
         if(l_idx == -1)
         {
           if((l_cnt < 2) || (l_v2 > GAP_MOV_MAX_POINT) || (l_v1 > l_v2))
           {
             break;
            }
           pvals->point_idx     = l_v1;
           pvals->point_idx_max = l_v2 -1;
           l_idx = 0;
         }
         else
         {
           if((l_cnt != 6) && (l_cnt != 7))
           {
             l_rc = -2;  /* have to call p_reset_points() when called from dialog window */
             break;
           }
           pvals->point[l_idx].p_x      = l_v1;
           pvals->point[l_idx].p_y      = l_v2;
           pvals->point[l_idx].w_resize = l_v3;
           pvals->point[l_idx].h_resize = l_v4;
           pvals->point[l_idx].opacity  = l_v5;
           pvals->point[l_idx].rotation = l_v6;
           if((l_cnt == 7) && (l_idx > 0))
	   {
             pvals->point[l_idx].keyframe = l_v7;
             pvals->point[l_idx].keyframe_abs = p_conv_keyframe_to_abs(l_v7, pvals);
	   }
	   else
	   {
             pvals->point[l_idx].keyframe_abs = 0;
             pvals->point[l_idx].keyframe = 0;
	   }
           l_idx ++;
         }

         if(l_idx > pvals->point_idx_max) break;
       }
    }
     
    fclose(l_fp);
    if(l_idx >= 0)
    {
       l_rc = 0;  /* OK if we found at least one valid Controlpoint in the file */
    }
  }
  return (l_rc);
}


/* ============================================================================
 * procedured for calculating angels
 *   (used in rotate_follow option)
 * ============================================================================
 */

static gdouble
p_calc_angle(gint p1x, gint p1y, gint p2x, gint p2y)
{
  /* calculate angle in degree
   * how to rotate an object that follows the line between p1 and p2
   */
  gdouble l_a;
  gdouble l_b;
  gdouble l_angle_rad;
  gdouble l_angle;
  
  l_a = p2x - p1x;
  l_b = (p2y - p1y) * (-1.0);
  
  if(l_a == 0)
  {
    if(l_b < 0)  { l_angle = 90.0; }
    else         { l_angle = 270.0; }
  }
  else
  {
    l_angle_rad = atan(l_b/l_a);
    l_angle = (l_angle_rad * 180.0) / 3.14159;

    if(l_a < 0)
    {
      l_angle = 180 - l_angle;
    }
    else
    {
      l_angle = l_angle * (-1.0);
    }
  }  

  if(gap_debug)
  {
     printf("p_calc_angle: p1(%d/%d) p2(%d/%d)  a=%f, b=%f, angle=%f\n"
         , (int)p1x, (int)p1y, (int)p2x, (int)p2y
         , (float)l_a, (float)l_b, (float)l_angle);
  }
  return(l_angle);
}

static gdouble
p_rotatate_less_than_180(gdouble angle, gdouble angle_new, gint *turns)
{
  /* if an object  follows a circular path and does more than one turn
   * there comes a point where it flips from say 265 degree to -85 degree.
   *
   * if there are more (say 3) frames between the controlpoints,
   * the object performs an unexpected rotation effect because the iteration
   * from 265 to -85  is done  in a sequence like this: 265.0, 148.6, 32.3, -85.0
   *
   * we can avoid this by preventing angle changes of more than 180 degree.
   * in such a case this procedure adjusts the new_angle from -85 to 275
   * that results in oterations like this: 265.0, 268.3, 271.6, 275.0
   */
  gint l_diff;
  gint l_turns;

  l_diff = angle - (angle_new + (*turns * 360));
  if((l_diff >= -180) && (l_diff < 180))
  {
      return(angle_new + (*turns * 360));
  }

  l_diff = (angle - angle_new);
  if(l_diff < 0)
  {
     l_turns = (l_diff / 360) -1;
  }
  else
  {
     l_turns = (l_diff / 360) +1;
  }

  *turns = l_turns;

  if(gap_debug)
  {
     printf("p_rotatate_less_than_180: turns %d angle_new:%f\n"
             , (int)l_turns, (float)angle_new);
  }

  return( angle_new + (l_turns * 360));
}


/* ============================================================================
 * p_calculate_rotate_follow
 * ============================================================================
 */
void
p_calculate_rotate_follow(t_mov_values *pvals, gint32 startangle)
{
  gint l_idx;
  gdouble l_startangle;
  gdouble l_angle_1;
  gdouble l_angle_2;
  gdouble l_angle_new;
  gdouble l_angle;
  gint    l_turns;

  l_startangle = startangle;
  
  if(pvals->point_idx_max > 1)
  {
    l_angle = 0.0;
    l_turns = 0;
 
    for(l_idx = 0; l_idx <= pvals->point_idx_max; l_idx++)
    {
      if(l_idx == 0)
      {
        l_angle = p_calc_angle(pvals->point[l_idx].p_x,
	                       pvals->point[l_idx].p_y,
	                       pvals->point[l_idx +1].p_x,
	                       pvals->point[l_idx +1].p_y);
      }
      else
      {
        if(l_idx == pvals->point_idx_max)
	{
          l_angle_new = p_calc_angle(pvals->point[l_idx -1].p_x,
	                	 pvals->point[l_idx -1].p_y,
	                	 pvals->point[l_idx].p_x,
	                	 pvals->point[l_idx].p_y);
	}
	else
	{
           l_angle_1 = p_calc_angle(pvals->point[l_idx -1].p_x,
	                	  pvals->point[l_idx -1].p_y,
	                	  pvals->point[l_idx].p_x,
	                	  pvals->point[l_idx].p_y);
				  
           l_angle_2 = p_calc_angle(pvals->point[l_idx].p_x,
	                          pvals->point[l_idx].p_y,
	                          pvals->point[l_idx +1].p_x,
	                          pvals->point[l_idx +1].p_y);

           if((l_angle_1 == 0) && (l_angle_2 == 180))
	   {
               l_angle_new = 270;
	   }
	   else
	   {
             if((l_angle_1 == 90) && (l_angle_2 == 270))
	     {
               l_angle_new = 0;
	     }
	     else
	     {
               l_angle_new = (l_angle_1 + l_angle_2) / 2;
	     }
	   }
	   if(((l_angle_1 < 0) && (l_angle_2 >= 180))
	   || ((l_angle_2 < 0) && (l_angle_1 >= 180)))
	   {
	      l_angle_new += 180;
	   }
	}
	l_angle = p_rotatate_less_than_180(l_angle, l_angle_new, &l_turns);
      }

      if(gap_debug)
      {
        printf("ROT Follow [%03d] angle = %f\n", (int)l_idx, (float)l_angle);
      }

      pvals->point[l_idx].rotation = l_startangle + l_angle;
    }
  }
}	/* end p_calculate_rotate_follow */


/* ============================================================================
 * p_gap_chk_keyframes
 *   check if controlpoints and keyframe settings are OK
 *   return pointer to errormessage on Errors
 *      contains "\0" if no errors are found
 * ============================================================================
 */
gchar *p_gap_chk_keyframes(t_mov_values *pvals)
{
  gint   l_affected_frames;
  gint   l_idx;
  gint   l_errcount;
  gint   l_prev_keyframe;
  gint   l_prev_frame;
  gchar *l_err;
  gchar *l_err_lbltext;

  l_affected_frames = 1 + MAX(pvals->dst_range_start, pvals->dst_range_end)
                    - MIN(pvals->dst_range_start, pvals->dst_range_end);
  
  l_errcount = 0;
  l_prev_keyframe = 0;
  l_prev_frame = 0;
  l_err_lbltext = g_strdup("\0");
  
  for(l_idx = 0; l_idx < pvals->point_idx_max; l_idx++ )
  {
     if(pvals->point[l_idx].keyframe_abs != 0)
     {
         pvals->point[l_idx].keyframe = p_conv_keyframe_to_rel(pvals->point[l_idx].keyframe_abs, pvals);

         if(pvals->point[l_idx].keyframe > l_affected_frames - 2)
	 {
	    l_err = g_strdup_printf(_("\nError: Keyframe %d at point [%d] higher or equal than last handled frame")
	                              , pvals->point[l_idx].keyframe_abs,  l_idx+1);
	    l_err_lbltext = g_strdup_printf("%s%s", l_err_lbltext, l_err);
	    g_free(l_err);
	    l_errcount++;
	 }
         if(pvals->point[l_idx].keyframe < l_prev_frame)
	 {
	    l_err = g_strdup_printf(_("\nError: Keyframe %d at point [%d] leaves not enough space (frames)"
	                              "\nfor the previous controlpoints")
				      , pvals->point[l_idx].keyframe_abs, l_idx+1);
	    l_err_lbltext = g_strdup_printf("%s%s", l_err_lbltext, l_err);
	    g_free(l_err);
	    l_errcount++;
	 }
	 
         if(pvals->point[l_idx].keyframe <= l_prev_keyframe)
	 {
	    l_err = g_strdup_printf(_("\nError: Keyframe %d is not in sequence at point [%d]")
	                             , pvals->point[l_idx].keyframe_abs, l_idx+1);
	    l_err_lbltext = g_strdup_printf("%s%s", l_err_lbltext, l_err);
	    g_free(l_err);
	    l_errcount++;
	 }

         l_prev_keyframe = pvals->point[l_idx].keyframe;
	 if(l_prev_keyframe > l_prev_frame)
	 {
	   l_prev_frame = l_prev_keyframe +1;
	 }
     }
     else
     {
	l_prev_frame++;
	if(l_prev_frame +1 > l_affected_frames)
	{
	    l_err = g_strdup_printf(_("\nError: controlpoint [%d] is out of handled framerange"), l_idx+1);
	    l_err_lbltext = g_strdup_printf("%s%s", l_err_lbltext, l_err);
	    g_free(l_err);
	    l_errcount++;
	}
     }
     if(l_errcount > 10)
     {
       break;
     }
  }

  if(pvals->point_idx_max + 1 > l_affected_frames)
  {
	l_err = g_strdup_printf(_("\nError: more controlpoints (%d) than handled frames (%d)"
	                          "\nplease reduce controlpoints or select more frames"),
	                          (int)pvals->point_idx_max+1, (int)l_affected_frames);
	l_err_lbltext = g_strdup_printf("%s%s", l_err_lbltext, l_err);
	g_free(l_err);
  }

  return(l_err_lbltext);
}	/* end p_gap_chk_keyframes */


/* ============================================================================
 * p_check_move_path_params
 *   check the parameters for noninteractive call of MovePath
 *   return 0 (OK)  or -1 (Error)
 * ============================================================================
 */
static gint
p_check_move_path_params(t_mov_data *mov_data)
{
  gchar *l_err_lbltext;
  gint   l_rc;

  l_rc = 0;  /* assume OK */

  /* range params valid ? */
  if(MIN(mov_data->val_ptr->dst_range_start, mov_data->val_ptr->dst_range_end)
      < mov_data->dst_ainfo_ptr->first_frame_nr)
  {
     printf("Error: Range starts before first frame number %d\n",
             (int)mov_data->dst_ainfo_ptr->first_frame_nr);
     l_rc = -1;
  }

  if(MAX(mov_data->val_ptr->dst_range_start, mov_data->val_ptr->dst_range_end)
      > mov_data->dst_ainfo_ptr->last_frame_nr)
  {
     printf("Error: Range ends after last frame number %d\n",
             (int)mov_data->dst_ainfo_ptr->last_frame_nr);
     l_rc = -1;
  }
  
  /* is there a valid source object ? */
  if(mov_data->val_ptr->src_layer_id < 0)
  {
     printf("Error: the passed src_layer_id %d  is invalid\n",
             (int)mov_data->val_ptr->src_layer_id);
     l_rc = -1;
  }
  else if(gimp_drawable_is_layer(mov_data->val_ptr->src_layer_id))
  {
     mov_data->val_ptr->src_image_id = gimp_layer_get_image_id(mov_data->val_ptr->src_layer_id);
  }
  else
  {
     printf("Error: the passed src_layer_id %d  is no Layer\n",
             (int)mov_data->val_ptr->src_layer_id);
     l_rc = -1;
  }
  
  /* keyframes OK ? */
  l_err_lbltext = p_gap_chk_keyframes(mov_data->val_ptr);
  if (*l_err_lbltext != '\0')
  {
     printf("Error in Keyframe settings: %s\n", l_err_lbltext);
     l_rc = -1;
  }
  g_free(l_err_lbltext);
  
  return (l_rc);
}	/* end p_check_move_path_params */


/* ============================================================================
 * gap_move_path
 * ============================================================================
 */
int
gap_move_path(GimpRunMode run_mode, gint32 image_id, t_mov_values *pvals, gchar *pointfile
             , gint rotation_follow , gint32 startangle)
{
  int l_rc;
  t_anim_info *ainfo_ptr;
  t_mov_data  l_mov_data;

  l_rc = -1;
  ainfo_ptr = p_alloc_ainfo(image_id, run_mode);
  if(ainfo_ptr != NULL)
  {
    l_mov_data.val_ptr = pvals;
    if(NULL != l_mov_data.val_ptr)
    {
      if (0 == p_dir_ainfo(ainfo_ptr))
      {
        if(0 != p_chk_framerange(ainfo_ptr))   return -1;

        l_mov_data.val_ptr->cache_src_image_id = -1;
        l_mov_data.dst_ainfo_ptr = ainfo_ptr;
        if(run_mode == GIMP_RUN_NONINTERACTIVE)
        {
           l_rc = 0;

	   /* get controlpoints from pointfile */
	   if (pointfile != NULL)
	   {
	     l_rc = p_gap_load_pointfile(pointfile, pvals);
	     if (l_rc < 0)
	     {
	       printf("Execution Error: could not load MovePath controlpoints from file: %s\n",
	               pointfile);
	     }
	   }
	   
	   if(l_rc >= 0)
	   {
	      l_rc = p_check_move_path_params(&l_mov_data);
	   }
	   
	   /* Automatic calculation of rotation values */
	   if((rotation_follow > 0) && (l_rc == 0))
	   {
              p_calculate_rotate_follow(pvals, startangle);
	   }
        }
        else
        {
	   /* Dialog for GIMP_RUN_INTERACTIVE 
	    * (and for GIMP_RUN_WITH_LAST_VALS that is not really supported here)
	    */
           l_rc = p_move_dialog (&l_mov_data);
           if(0 != p_chk_framechange(ainfo_ptr))
           {
              l_rc = -1;
           }
        }

        if(l_rc >= 0)
        {
           l_rc = p_save_named_frame(ainfo_ptr->image_id, ainfo_ptr->old_filename);
           if(l_rc >= 0)
           {
              l_rc = p_mov_execute(&l_mov_data);
           
              /* go back to the frame_nr where move operation was started from */
              p_load_named_frame(ainfo_ptr->image_id, ainfo_ptr->old_filename);
           }
        }

        if(l_mov_data.val_ptr->cache_tmp_image_id >= 0)
        {
	   if(gap_debug)
	   {
	      printf("gap_move: DELETE cache_tmp_image_id:%d\n",
	               (int)l_mov_data.val_ptr->cache_tmp_image_id);
           }
           /* destroy the cached frame image */
           gimp_image_delete(l_mov_data.val_ptr->cache_tmp_image_id);
        }
      }

    }

    p_free_ainfo(&ainfo_ptr);
  }
  
  return(l_rc);    
}	/* end gap_move_path */
