[gimp-gap] Merged Fixes and new Features for MovePath, ModifyFrames, Onionskin, BlendFill, FrameRename, DetailT



commit 69d7f8392f1a99336eeeb74b7b33553fd003096b
Author: Wolfgang Hofer <wolfgangh svn gnome org>
Date:   Sat Apr 15 15:48:00 2017 +0200

    Merged Fixes and new Features for MovePath, ModifyFrames, Onionskin, BlendFill, FrameRename, 
DetailTracking from 2-8 to master

 ChangeLog                       |   88 ++
 gap/Makefile.am                 |   34 +
 gap/gap_base_ops.c              |  323 +++++
 gap/gap_base_ops.h              |    3 +
 gap/gap_blend_fill_main.c       |   90 +-
 gap/gap_detail_align_exec.c     | 2978 +++++++++++++++++++++++++++++++++++++--
 gap/gap_detail_align_exec.h     |   12 +
 gap/gap_detail_tracking_exec.c  | 1671 +++++++++++++++++++---
 gap/gap_detail_tracking_exec.h  |   12 +-
 gap/gap_detail_tracking_main.c  |  167 ++-
 gap/gap_edge_detection.c        |  561 ++++----
 gap/gap_edge_detection.h        |   32 +-
 gap/gap_edge_detection_dialog.c | 1280 +++++++++++++++++
 gap/gap_edge_detection_dialog.h |   64 +
 gap/gap_edge_detection_main.c   |  293 ++++
 gap/gap_geo.c                   | 1283 +++++++++++++++++
 gap/gap_geo.h                   |  193 +++
 gap/gap_layer_copy.c            |   42 +
 gap/gap_layer_copy.h            |    2 +
 gap/gap_locate2.c               | 1011 +++++++++++++-
 gap/gap_locate2.h               |  106 ++-
 gap/gap_main.c                  |   63 +
 gap/gap_mod_layer.c             |  191 +++-
 gap/gap_mod_layer.h             |    9 +
 gap/gap_mod_layer_dialog.c      |   40 +
 gap/gap_mov_dialog.c            |  602 ++++++++-
 gap/gap_mov_dialog.h            |    4 +
 gap/gap_mov_exec.c              |   13 +-
 gap/gap_mov_render.c            |   93 +-
 gap/gap_mov_xml_par.c           |   12 +
 gap/gap_onion_base.c            |   72 +-
 gap/gap_onion_base.h            |   11 +
 gap/gap_onion_dialog.c          |  167 +++-
 gap/gap_onion_main.c            |    6 +
 gap/gap_onion_main.h            |    2 +
 gap/gap_opacity_exposure_main.c | 1554 ++++++++++++++++++++
 gap/gap_vex_exec.c              |    7 +-
 gap/gap_vin.c                   |   11 +-
 gap/gap_vin.h                   |    3 +
 gap/gap_wr_desaturate.c         |  539 +++++++
 gap/gap_wr_resynth.c            |  678 ++++++++-
 41 files changed, 13455 insertions(+), 867 deletions(-)
---
diff --git a/ChangeLog b/ChangeLog
index 02ade28..538cd5b 100755
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,91 @@
+2017-04-15 Wolfgang Hofer <hof gimp org>
+
+- MovePath support Handle Offsets, "Reset all controlpoints" Button protects keyframe information by default
+  This change of behavior is a bugfix to prevent unwanted loss of keyframe information in the path 
controlpoints.
+  In case where reset of keyframe information is wanted
+  Clicking "Reset all controlpoints" Button while holding down the ALT key removes keyframe information
+  from all controlpoints the same way as the old behavior always did.
+  
+  Added X and Y Controlpoint Coordinate Buttons to copy coordinate from previous Controlpoint
+  Holding down the shift Key copy coordinate from next Controlpoint
+  Holding down the ctrl Key Calculate coordinate as average between previous and next Controlpoint.
+
+- MovePath Save Button overwrites pointfile (after succsessful inital load or save) without popup the 
fileselection.
+  Hold Ctrl or Shift forces the fileselection (Save As), 
+  Show the pointfilename (as part of the framelabel Edit Controlpoints)
+
+- Detail tracking history information is now stored in temporary image parasite
+  in the (mtrace) image that is updated while playback in detail tracking mode.
+  (older versions stored history global in the gimp session)
+
+- bugfix in the BlendFill filter (that applied always to the layer on top of stack instead of the active 
drawable)
+  furthermore keep the layermask (in case the active layer has one)
+
+- The wrapper for the 3rd party resysnthesizer plugin now supports the same options as the
+  python script "plugin-heal-selecton.py" that ships with the resynthesizer
+  with additional option to load the selection from a vectors file in SVG format.
+
+- Added a wrapper for the gimp desaturation procedure
+  (so it can be used in GIMP-GAP triggered filtercalls such as filtermacro
+  in filter all layers or modify frames feature)
+
+- Added Dialog and extended the edge detection method for use in  GIMP-GAP triggered filtercalls
+  (GIMP already has a similar Edge detection Filter based on Difference between 2 Gaussian Blur operations
+  the GAP variante also provides shifting options and separated x y settings -- that makes it more 
complicated for the user --
+  but allows more tuning when creating edge protectgion masks for remastering of videoframes)
+
+- Onionskin creation of onionskin layers has now options to create the onionskin layer(s) with
+  a layermask (none, Black, White, from Selection, Clipped from Selection) and has options
+  to set the onionskin layer or its layermask active after crestion.
+
+- FramesModify supports 2 additional functions for resizing layers in frame images
+  to selction bounds (using the selection of invoking active frame, or individual selection in all frames)
+
+- Added new base operation to remame the framename part in all framefiles.
+
+  * gap/Makefile.am
+  * gap/gap_base_ops.c
+  * gap/gap_base_ops.h
+  * gap/gap_blend_fill_main.c
+  * gap/gap_detail_align_exec.c
+  * gap/gap_detail_align_exec.h
+  * gap/gap_detail_tracking_exec.c
+  * gap/gap_detail_tracking_exec.h
+  * gap/gap_detail_tracking_main.c
+  * gap/gap_edge_detection.c
+  * gap/gap_edge_detection.h
+  * gap/gap_layer_copy.c
+  * gap/gap_layer_copy.h
+  * gap/gap_locate2.c
+  * gap/gap_locate2.h
+  * gap/gap_main.c
+  * gap/gap_mod_layer.c
+  * gap/gap_mod_layer.h
+  * gap/gap_mod_layer_dialog.c
+  * gap/gap_mov_dialog.c
+  * gap/gap_mov_dialog.h
+  * gap/gap_mov_exec.c
+  * gap/gap_mov_render.c
+  * gap/gap_mov_xml_par.c
+  * gap/gap_onion_base.c
+  * gap/gap_onion_base.h
+  * gap/gap_onion_dialog.c
+  * gap/gap_onion_main.c
+  * gap/gap_onion_main.h
+  * gap/gap_vex_exec.c
+  * gap/gap_vin.c
+  * gap/gap_vin.h
+  * gap/gap_wr_resynth.c
+  
+  * gap/gap_edge_detection_dialog.c # new
+  * gap/gap_edge_detection_dialog.h # new
+  * gap/gap_edge_detection_main.c # new
+  * gap/gap_geo.c # new
+  * gap/gap_geo.h # new
+  * gap/gap_opacity_exposure_main.c # new
+  * gap/gap_wr_desaturate.c # new
+  
+
 2016-05-28 Wolfgang Hofer <hof gimp org>
 
 - replaced deprecated procedure calls: 
diff --git a/gap/Makefile.am b/gap/Makefile.am
index c17362c..eb234a0 100644
--- a/gap/Makefile.am
+++ b/gap/Makefile.am
@@ -58,6 +58,8 @@ BASE_SOURCES = \
        gap_colormask_file.h    \
         gap_edge_detection.c    \
         gap_edge_detection.h    \
+       gap_geo.c               \
+       gap_geo.h               \
        gap_image.c             \
        gap_image.h             \
        gap_layer_copy.c        \
@@ -67,6 +69,8 @@ BASE_SOURCES = \
        gap_lib_common_defs.h   \
        gap_detail_tracking_exec.c      \
        gap_detail_tracking_exec.h      \
+       gap_detail_align_exec.c \
+       gap_detail_align_exec.h \
        gap_locate.c            \
        gap_locate.h            \
        gap_locate2.c           \
@@ -134,6 +138,7 @@ libgapstory_a_SOURCES = $(BASE_SOURCES)     $(MOVEPATH_SOURCES) \
 libexec_PROGRAMS = \
        gap_blend_fill          \
        gap_bluebox             \
+       gap_edge                \
        gap_colormask           \
        gap_detail_tracking     \
        gap_plugins             \
@@ -148,6 +153,7 @@ libexec_PROGRAMS = \
        gap_navigator_dialog    \
        gap_player              \
        gap_onion               \
+       gap_opacity_exposure    \
        gap_storyboard          \
        $(GAP_VIDEO_EXTRACT)    \
        $(GAP_VIDEO_INDEX)      \
@@ -161,6 +167,7 @@ libexec_PROGRAMS = \
        gap_wr_trans            \
        gap_wr_resynth          \
        gap_wr_layermode        \
+       gap_wr_desaturate       \
        gap_wr_opacity
 
 
@@ -178,6 +185,16 @@ gap_bluebox_SOURCES = \
        gap_bluebox.h           \
        gap_libgimpgap.h        
 
+gap_edge_SOURCES = \
+       gap_lastvaldesc.c       \
+       gap_lastvaldesc.h       \
+       gap_edge_detection_main.c       \
+       gap_edge_detection.c            \
+       gap_edge_detection.h            \
+       gap_edge_detection_dialog.c     \
+       gap_edge_detection_dialog.h     \
+       gap_libgimpgap.h        
+
 gap_colormask_SOURCES = \
        gap_lastvaldesc.c       \
        gap_lastvaldesc.h       \
@@ -364,6 +381,13 @@ gap_onion_SOURCES = \
        gap_onion_worker.h      \
        gap_libgimpgap.h        
 
+gap_opacity_exposure_SOURCES = \
+       gap_lastvaldesc.c       \
+       gap_lastvaldesc.h       \
+       gap_opacity_exposure_main.c   \
+       gap_libgimpgap.h        
+
+
 gap_storyboard_SOURCES = \
        gap_story_main.c        \
        gap_story_main.h        \
@@ -491,6 +515,13 @@ gap_wr_resynth_SOURCES = \
        gap_libgimpgap.h
 
 
+gap_wr_desaturate_SOURCES = \
+       gap_wr_desaturate.c     \
+       gap_lastvaldesc.c       \
+       gap_lastvaldesc.h       \
+       gap_libgimpgap.h
+
+
 if OS_WIN32
 mwindows = -mwindows
 endif
@@ -520,6 +551,7 @@ gap_bluebox_LDADD =          $(LIBGIMPGAP)  $(LIBGAPBASE) $(GIMP_LIBS)
 gap_blend_fill_LDADD =       $(LIBGIMPGAP)  $(LIBGAPBASE) $(GIMP_LIBS)
 gap_colormask_LDADD =        $(LIBGIMPGAP)  $(LIBGAPBASE) $(GIMP_LIBS)
 gap_detail_tracking_LDADD =  $(LIBGIMPGAP)  $(LIBGAPBASE) $(GIMP_LIBS)
+gap_edge_LDADD =             $(LIBGIMPGAP)  $(LIBGAPBASE) $(GIMP_LIBS)
 gap_filter_LDADD =           $(GAPVIDEOAPI) $(LIBGIMPGAP)  $(LIBGAPBASE) $(GIMP_LIBS)
 gap_fmac_LDADD =             $(GAPVIDEOAPI) $(LIBGIMPGAP)  $(LIBGAPBASE) $(GIMP_LIBS)
 gap_fmac_varying_LDADD =     $(GAPVIDEOAPI) $(LIBGIMPGAP)  $(LIBGAPBASE) $(GIMP_LIBS)
@@ -530,6 +562,7 @@ gap_name2layer_LDADD =       $(LIBGIMPGAP)  $(LIBGAPBASE) $(GIMP_LIBS)
 gap_navigator_dialog_LDADD = $(LIBGIMPGAP)  $(LIBGAPBASE) $(GIMP_LIBS)
 gap_player_LDADD =           $(GAPVIDEOAPI) $(GAP_AUDIO_LIBS) ${LIBGAPSTORY} $(LIBGAPBASE) $(GIMP_LIBS)
 gap_onion_LDADD =            $(LIBGIMPGAP)  $(LIBGAPBASE) $(GIMP_LIBS)
+gap_opacity_exposure_LDADD = $(LIBGIMPGAP)  $(LIBGAPBASE) $(GIMP_LIBS)
 gap_storyboard_LDADD =       $(GAPVIDEOAPI) $(GAP_AUDIO_LIBS) ${LIBGAPSTORY} $(LIBGAPBASE) $(GIMP_LIBS)
 gap_video_extract_LDADD =    $(GAPVIDEOAPI) $(GAP_AUDIO_LIBS) ${LIBGAPSTORY} $(LIBGAPBASE) $(GIMP_LIBS)
 gap_video_index_LDADD =      $(GAPVIDEOAPI) $(LIBGAPSTORY) $(LIBGAPBASE)  $(GIMP_LIBS)
@@ -544,6 +577,7 @@ gap_wr_color_levels_LDADD =  $(LIBGIMPGAP)  $(LIBGAPBASE) $(GIMP_LIBS)
 gap_wr_color_huesat_LDADD =  $(LIBGIMPGAP)  $(LIBGAPBASE) $(GIMP_LIBS)
 gap_wr_color_balance_LDADD = $(LIBGIMPGAP)  $(LIBGAPBASE) $(GIMP_LIBS)
 gap_wr_resynth_LDADD =       $(LIBGIMPGAP)  $(LIBGAPBASE) $(GIMP_LIBS)
+gap_wr_desaturate_LDADD =       $(LIBGIMPGAP)  $(LIBGAPBASE) $(GIMP_LIBS)
 
 
 EXTRA_DIST = \
diff --git a/gap/gap_base_ops.c b/gap/gap_base_ops.c
index dfebc5d..bd3b101 100644
--- a/gap/gap_base_ops.c
+++ b/gap/gap_base_ops.c
@@ -28,6 +28,7 @@
  */
 
 /* revision history:
+ * 2.8.xx;  2017/04/04    hof: added gap_base_rename
  * 1.3.17b; 2003/07/31   hof: message text fixes for translators (# 118392)
  * 1.3.16b; 2003/07/04   hof: added gap_density, confirm dialog for frame deleting operations
  * 1.3.15a  2003/06/21   hof: textspacing
@@ -64,6 +65,7 @@ extern      int gap_debug; /* ==0  ... dont print debug infos */
 #define GAP_HELP_ID_DENSITY           "plug-in-gap-density"
 #define GAP_HELP_ID_EXCHANGE          "plug-in-gap-exchg"
 #define GAP_HELP_ID_RENUMBER          "plug-in-gap-renumber"
+#define GAP_HELP_ID_RENAME            "plug-in-gap-rename"
 #define GAP_HELP_ID_SHIFT             "plug-in-gap-shift"
 #define GAP_HELP_ID_REVERSE           "plug-in-gap-reverse"
 
@@ -2287,3 +2289,324 @@ gap_base_renumber(GimpRunMode run_mode, gint32 image_id,
 
   return(rc);
 }       /* end gap_base_renumber */
+
+
+
+/* --------------------------------
+ * p_getbasenameWithoutDirpartPtr
+ * --------------------------------
+ * returns a pointer to the start of the filename part within basenamePtr.
+ * the caller MUST NOT free the resulting pointer !
+ */
+static char *
+p_getbasenameWithoutDirpartPtr(char *basenamePtr)
+{
+  char             *retPtr;
+  char             *ptr;
+  
+  /* retPtr is set after the last
+   * occurance of directory separators (check for both UNIX and Windows separator characters)
+   */
+  retPtr = basenamePtr;
+  ptr = basenamePtr;
+  for(ptr = basenamePtr; ptr != NULL; ptr++)
+  {
+    if (*ptr == '\0')
+    {
+      break;
+    }
+    if ((*ptr == ':') || (*ptr == '/') || (*ptr == '\\'))
+    {
+      retPtr = ptr;
+      retPtr++;
+    }
+  }
+  
+  return (retPtr);
+  
+}  /* end p_getbasenameWithoutDirpartPtr */
+
+
+/* ----------------------------------
+ * p_rename_frames
+ * ----------------------------------
+ * rename all frames within the same directory to newBasenamePtr
+ * if the doRename flag is not TRUE just check if any of the new names already exist.
+ *
+ *     Old filenames               New Filenames
+ *   -----------------------------------------------
+ *     frame_000002.xcf            newname_000002.xcf
+ *     frame_000003.xcf            newname_000003.xcf
+ *     frame_000004.xcf            newname_000004.xcf
+ *     frame_000005.xcf            newname_000005.xcf
+ *
+ */
+static gint32
+p_rename_frames(GapAnimInfo *ainfo_ptr, char *newBasenamePtr, gboolean doRename)
+{
+  long l_fnr;
+  gint l_errcount;
+
+
+  if(ainfo_ptr->run_mode == GIMP_RUN_INTERACTIVE)
+  {
+    if (doRename == TRUE)
+    {
+      gimp_progress_init(_("Rename Frames"));
+    }
+    else
+    {
+      gimp_progress_init(_("Check Framnames"));
+    }
+  }
+
+  l_errcount = 0;
+  l_fnr = ainfo_ptr->first_frame_nr;
+  while (l_fnr <= ainfo_ptr->last_frame_nr)
+  {
+    char *l_curr_name;
+    
+    if (gap_debug)
+    {
+      printf("p_rename_frames: STEP l_fnr:%d ainfo_ptr->basename:%s\n", (int)l_fnr, ainfo_ptr->basename);
+    }
+
+    l_curr_name = gap_lib_alloc_fname(ainfo_ptr->basename, l_fnr, ainfo_ptr->extension);
+
+    /* check if frame file with old name exists
+     */
+    if( gap_lib_file_exists(l_curr_name) )
+    {
+      char *l_new_name;
+      char *l_new_basename;
+      char *l_dir_path;
+      char *l_filepartPtr;   /* points into l_dir_path dont g_free this */
+      
+      l_dir_path = g_strdup(ainfo_ptr->basename);
+      l_filepartPtr = p_getbasenameWithoutDirpartPtr(l_dir_path);
+      if (l_filepartPtr != NULL)
+      {
+        *l_filepartPtr = '\0';  /* cut off filename part, if no dir present cut can be at position 0 */
+      }
+      if (*l_dir_path == '\0')
+      {
+        l_new_basename = g_strdup(newBasenamePtr);
+      }
+      else
+      {
+        /* build basename from dirpath (that already ends up with a separator) and
+         * the newBasename (that was entered in the dialog and is already without dirpath)
+         */
+        l_new_basename = g_strdup_printf("%s%s", l_dir_path, newBasenamePtr);
+      }
+      
+      if(gap_debug)
+      {
+        printf("l_dir_path:%s\n", l_dir_path);
+      }
+      
+      l_new_name = gap_lib_alloc_fname(l_new_basename, l_fnr, ainfo_ptr->extension);
+      if (gap_lib_file_exists(l_new_name))
+      {
+        l_errcount++;
+      }
+      else
+      {
+        if (doRename == TRUE)
+        {
+           gint l_rc;
+           
+           l_rc = g_rename(l_curr_name, l_new_name);
+          gap_thumb_file_rename_thumbnail(l_curr_name, l_new_name);
+          
+           if (l_fnr == ainfo_ptr->curr_frame_nr)
+           {
+             gimp_image_set_filename(ainfo_ptr->image_id, l_new_name);
+           }
+          
+          if (l_rc != 0)
+          {
+            l_errcount++;
+          }
+        }
+      }
+      g_free(l_new_name);
+      g_free(l_new_basename);
+      g_free(l_dir_path);
+    }
+    l_fnr++;
+    g_free(l_curr_name);
+
+    if(ainfo_ptr->run_mode == GIMP_RUN_INTERACTIVE)
+    {
+      gimp_progress_update( (gdouble)(l_fnr - ainfo_ptr->first_frame_nr)
+                          / (gdouble)(1+ (ainfo_ptr->last_frame_nr - ainfo_ptr->first_frame_nr)) );
+    }
+    if (l_errcount > 0)
+    {
+      break;
+    }
+  }
+  
+  if (l_errcount > 0)
+  {
+    return (-1);
+  }
+  return (0); /* OK */
+  
+}  /* end p_rename_frames */
+
+
+
+
+
+/* --------------------------------
+ * p_rename_dialog
+ * --------------------------------
+ */
+static int
+p_rename_dialog(GapAnimInfo *ainfo_ptr, char *newFrameName,  gint len_newFrameName) /// long 
*start_frame_nr, long *digits)
+{
+#define ENTRY_WIDTH 400
+  static GapArrArg  argv[3];
+  gchar            *l_title;
+  gchar            *l_oldFrameName;
+  gboolean          l_rc;
+
+
+  l_title = g_strdup_printf (_("Rename Frames (%ld)")
+                             , ainfo_ptr->frame_cnt);
+  l_oldFrameName = g_strdup_printf (_("Old FrameName: %s")
+                             , p_getbasenameWithoutDirpartPtr(ainfo_ptr->basename));
+
+  gap_arr_arg_init(&argv[0], GAP_ARR_WGT_LABEL_LEFT);
+  argv[0].label_txt = l_oldFrameName;
+
+  gap_arr_arg_init(&argv[1], GAP_ARR_WGT_TEXT);
+  argv[1].label_txt = _("New FrameName");
+  argv[1].entry_width = ENTRY_WIDTH;
+  argv[1].help_txt  = _("New FrameName for all frames (must be entered without number part, extension and 
directory path)");
+  argv[1].text_buf_len = len_newFrameName;
+  argv[1].text_buf_ret = newFrameName;
+
+
+  gap_arr_arg_init(&argv[2], GAP_ARR_WGT_HELP_BUTTON);
+  argv[2].help_id = GAP_HELP_ID_RENAME;
+
+  l_rc = gap_arr_ok_cancel_dialog(l_title, _("Rename Frames"),  3, argv);
+  g_free (l_title);
+  g_free (l_oldFrameName);
+
+  if(TRUE == l_rc)
+  {
+    return (0);
+  }
+  else
+  {
+    return -1;
+  }
+
+}       /* end p_rename_dialog */
+
+
+
+/* ============================================================================
+ * gap_base_rename
+ * ============================================================================
+ */
+gint32
+gap_base_rename(GimpRunMode run_mode, gint32 image_id,
+            char *newFrameName,  gint len_newFrameName)
+{
+  gint32 rc;
+  GapAnimInfo *ainfo_ptr;
+  char        *newBasenamePtr;
+
+  long           l_cnt;
+
+  rc = -1;
+  l_cnt = 0;
+  ainfo_ptr = gap_lib_alloc_ainfo(image_id, run_mode);
+  if(ainfo_ptr != NULL)
+  {
+    if (0 == gap_lib_dir_ainfo(ainfo_ptr))
+    {
+      if(run_mode != GIMP_RUN_NONINTERACTIVE)
+      {
+         if(0 != gap_lib_chk_framechange(ainfo_ptr)) { l_cnt = -1; }
+         else
+         {
+           strncpy(newFrameName, "newname_", len_newFrameName -1);
+           l_cnt = p_rename_dialog(ainfo_ptr, newFrameName, len_newFrameName);
+         }
+
+         if(0 != gap_lib_chk_framechange(ainfo_ptr))
+         {
+            l_cnt = -1;
+         }
+
+      }
+      
+      newBasenamePtr = p_getbasenameWithoutDirpartPtr(newFrameName);
+
+      /* check for directory path (that is not supported in this rename implementation) */
+      if ((newBasenamePtr != newFrameName) && (newBasenamePtr != NULL))
+      {
+        gap_arr_msg_win(ainfo_ptr->run_mode,
+                _("Rename Frames cancelled.\n"
+                   "new Framename MUST NOT contain directory path."));
+        l_cnt = -1;
+      }
+
+      /* check for equal names (rename is not required in this case..) */
+      if (strcmp(p_getbasenameWithoutDirpartPtr(ainfo_ptr->basename), newBasenamePtr) == 0)
+      {
+        gap_arr_msg_win(ainfo_ptr->run_mode,
+                _("Rename Frames cancelled.\n"
+                   "new Framename is equal to old Framename."));
+        l_cnt = -1;
+      }
+
+      if(gap_debug) 
+      {
+        printf("gap_base_rename: l_cnt:%d newFrameName:%s newBasenamePtr:%s\n"
+             , (int)l_cnt
+             , newFrameName
+             , newBasenamePtr
+             );
+      }
+
+      if(l_cnt >= 0)
+      {
+         /* check for all frames (on disk) if the newName already exists */
+         rc = p_rename_frames(ainfo_ptr, newBasenamePtr, FALSE);
+         if(rc < 0)
+         {
+           gap_arr_msg_win(ainfo_ptr->run_mode,
+                _("Rename Frames cancelled.\n"
+                   "one or more new Framename(s) already exits."));
+         }
+         else
+         {
+           /* rename all frames (on disk) */
+           rc = p_rename_frames(ainfo_ptr, newBasenamePtr, TRUE);
+           if(rc < 0)
+           {
+             gap_arr_msg_win(ainfo_ptr->run_mode,
+                _("Rename Frames failed.\n"
+                   "one or more new Framename(s) could not be renamed."));
+           }
+           else
+           {
+             rc = image_id;  /* if OK, return current image id */
+           }
+         }
+      }
+
+    }
+    gap_lib_free_ainfo(&ainfo_ptr);
+  }
+
+  return(rc);
+}       /* end gap_base_rename */
+
diff --git a/gap/gap_base_ops.h b/gap/gap_base_ops.h
index ad91e6e..fbae230 100644
--- a/gap/gap_base_ops.h
+++ b/gap/gap_base_ops.h
@@ -25,6 +25,7 @@
  */
 
 /* revision history:
+ * 2.8.xx;  2017/04/04    hof: added gap_base_rename
  * 1.3.16b; 2003/07/03   hof: added gap_density
  * 1.3.14a  2003/05/24   hof: created (module was splitted off from gap_lib)
  */
@@ -50,6 +51,8 @@ gint32 gap_base_shift(GimpRunMode run_mode, gint32 image_id, int nr, long range_
 gint32 gap_base_reverse(GimpRunMode run_mode, gint32 image_id, long range_from, long range_to);
 gint32 gap_base_renumber(GimpRunMode run_mode, gint32 image_id,
             long start_frame_nr, long digits);
+gint32 gap_base_rename(GimpRunMode run_mode, gint32 image_id,
+            char *newFrameName,  gint len_newFrameName);
 
 #endif
 
diff --git a/gap/gap_blend_fill_main.c b/gap/gap_blend_fill_main.c
index d7c7ee7..362e3a4 100644
--- a/gap/gap_blend_fill_main.c
+++ b/gap/gap_blend_fill_main.c
@@ -58,6 +58,7 @@ int gap_debug = 0;  /* 1 == print debug infos , 0 dont print debug infos */
 #include "gimplastvaldesc.h"
 #include "gap_image.h"
 #include "gap_arr_dialog.h"
+#include "gap_layer_copy.h"
 
 #include "gap-intl.h"
 
@@ -1006,19 +1007,13 @@ p_set_selection_from_vectors_string(FilterContext *context)
 
   if ((vectorsOk) && (vectors_ids != NULL) && (num_vectors > 0))
   {
-    /* gboolean       selOk; */
     gint32         vectorId;
     GimpChannelOps operation;
 
     vectorId = vectors_ids[0];
     operation = GIMP_CHANNEL_OP_REPLACE;
-    /* selOk = */ gimp_vectors_to_selection(vectorId
-                                     , operation
-                                     , FALSE      /*  antialias */
-                                     , FALSE      /*  feather   */
-                                     , 0.0        /*  gdouble feather_radius_x */
-                                     , 0.0        /*  gdouble feather_radius_y */
-                                     );
+    
+    gimp_image_select_item(context->imageId, operation, vectorId);
     gimp_image_remove_vectors(context->imageId, vectorId);
     context->doClearSelection = TRUE;
   }
@@ -1059,19 +1054,12 @@ p_set_selection_from_vectors_file(FilterContext *context)
 
   if ((vectorsOk) && (vectors_ids != NULL) && (num_vectors > 0))
   {
-    /* gboolean       selOk; */
     gint32         vectorId;
     GimpChannelOps operation;
 
     vectorId = vectors_ids[0];
     operation = GIMP_CHANNEL_OP_REPLACE;
-    /* selOk =  */ gimp_vectors_to_selection(vectorId
-                                     , operation
-                                     , FALSE      /*  antialias */
-                                     , FALSE      /*  feather   */
-                                     , 0.0        /*  gdouble feather_radius_x */
-                                     , 0.0        /*  gdouble feather_radius_y */
-                                     );
+    gimp_image_select_item(context->imageId, operation, vectorId);
     gimp_image_remove_vectors(context->imageId, vectorId);
     context->doClearSelection = TRUE;
   }
@@ -1151,7 +1139,7 @@ p_render_initial_workLayer(FilterContext *context)
 /* ----------------------------------------
  * p_create_workLayer
  * ----------------------------------------
- * create the workLayer as copy of the drawable rectangle area
+ * create the workLayer above the active layer as copy of the drawable rectangle area
  * that intersects with the selection expanded by borderRadius and clipped
  * to drawable boundaries.
  * The alpha channel is copied from the selection and the rgb channels
@@ -1167,6 +1155,8 @@ p_create_workLayer(FilterContext *context)
   gint     ix, iy, ix1, iy1, ix2, iy2;
   gint     iWidth, iHeight;
   gint     borderRadius;
+  gint32   l_parent_id;
+  gint32   l_position;
 
 
   altSelection_success = FALSE;
@@ -1260,7 +1250,9 @@ p_create_workLayer(FilterContext *context)
                 , 100.0   /* full opacity */
                 , 0       /* normal mode */
                 );
-  gimp_image_insert_layer(context->imageId, context->workLayerId, 0, 0);
+  l_parent_id = gimp_item_get_parent(context->drawableId);
+  l_position = gimp_image_get_item_position(context->imageId, context->drawableId);
+  gimp_image_insert_layer(context->imageId, context->workLayerId, l_parent_id, l_position);
   gimp_layer_set_offsets(context->workLayerId
                         , context->workLayerOffsX
                         , context->workLayerOffsY
@@ -1328,6 +1320,8 @@ gap_blend_fill_apply_run(gint32 image_id, gint32 activeDrawableId, gboolean doPr
   p_create_workLayer(context);
   if (context->workLayerId >= 0)
   {
+    gint32 layermaskId;
+    
     p_set_tile_cache(context);
 
     if (context->valPtr->horizontalBlendFlag)
@@ -1340,8 +1334,66 @@ gap_blend_fill_apply_run(gint32 image_id, gint32 activeDrawableId, gboolean doPr
       p_vertical_color_blend(context);
     }
 
-    rc = gimp_image_merge_down(image_id, context->workLayerId, GIMP_EXPAND_AS_NECESSARY);
+    layermaskId = gimp_layer_get_mask(activeDrawableId);
+    if (layermaskId >= 0)
+    {
+      gint32 l_parent_id;
+      gint32 l_position;
+      gint32 l_new_layer_id;
+      gint32 l_new_layermask_id;
+      gint32 l_new_layer_id_after_merge;
+
+      /* the active layer has a layermask that would be removed by merge down...
+       * therefore make a copy of the active layer (
+       * that is placed in the stack between worklayer and active layer 
+       */
+      l_parent_id = gimp_item_get_parent(activeDrawableId);
+      l_position = gimp_image_get_item_position(image_id, activeDrawableId);
+      l_new_layer_id = gimp_layer_copy(activeDrawableId);
+      gimp_image_insert_layer (image_id, l_new_layer_id, l_parent_id, l_position);
+
+      l_new_layermask_id = gimp_layer_get_mask(l_new_layer_id);
+      if (l_new_layermask_id >= 0)
+      {
+        /* remove the layermask from the copy
+         * because masked parts would be rendered as black pixels
+         * in the following merge.
+         * therefore the merge is done unmasked to preserve the original content
+         * for copying back to the original activeDrawableId
+         */
+        gimp_layer_remove_mask (l_new_layer_id, GIMP_MASK_DISCARD);
+      }
+
+      /* merge down the worklayer to the newly created copy l_new_layer_id */
+      l_new_layer_id_after_merge = gimp_image_merge_down(image_id, context->workLayerId, 
GIMP_EXPAND_AS_NECESSARY);
+      
+      if(gap_debug)
+      {
+        printf("activeDrawableId: %d\n", activeDrawableId);
+        printf("l_parent_id: %d\n", l_parent_id);
+        printf("l_position: %d\n", l_position);
+        printf("l_new_layer_id: %d\n", l_new_layer_id);
+        printf("l_new_layer_id_after_merge: %d\n", l_new_layer_id_after_merge);
+      }
+      
+      
+      
+      /* copy the content of  l_new_layer_id into the active layer */
+      gap_layer_copy_content (activeDrawableId, l_new_layer_id_after_merge);
+      
+      gimp_image_remove_layer(image_id, l_new_layer_id_after_merge);
+      
+      rc = activeDrawableId;
 
+    }
+    else
+    {
+      /* the active layer has no layermask,
+       * in this case just merge down worklayer to the active layer
+       */
+      rc = gimp_image_merge_down(image_id, context->workLayerId, GIMP_EXPAND_AS_NECESSARY);
+    }
+    
     if(context->doClearSelection)
     {
       gimp_selection_none(context->imageId);
diff --git a/gap/gap_detail_align_exec.c b/gap/gap_detail_align_exec.c
index 0c2234b..74b9b9d 100644
--- a/gap/gap_detail_align_exec.c
+++ b/gap/gap_detail_align_exec.c
@@ -1,9 +1,16 @@
 /*  gap_detail_align_exec.c
- *    This transforms and/or moves the active layer with 4 or 2 controlpoints.
+ *    This transforms and/or moves the active layer with 8, 4 or 2 controlpoints.
  *    controlpoints input from current path (or from an xml input file recorded by GAP detail tracking 
feature)
  *    4 points: rotate scale and move the layer in a way that 2 reference points match 2 target points. 
  *    2 points: simple move the layer from reference point to target point.
  *
+ *    8 points: perform a perspective transformation on the layer in a way that 4 points match 4 target 
points.
+ *              this kind of operation is supported when xml input file with 
+ *                 p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y  and
+ *                 s1x, s1y, s2x, s2y, s3x, s3y, s4x, s4y  values
+ *              is provided.
+ *              From GUI 2 paths are required for the perspective mode, each of them must have 4 points..
+ *
  *  2011/12/01
  */
 /* The GIMP -- an image manipulation program
@@ -42,7 +49,9 @@ extern int gap_debug;
 #include <libgimp/gimpui.h>
 
 #include "gap_base.h"
+#include "gap_geo.h"
 #include "gap_libgapbase.h"
+#include "gap_locate2.h"
 #include "gap_detail_align_exec.h"
 
 #include "gap-intl.h"
@@ -50,7 +59,8 @@ extern int gap_debug;
 #define GIMPRC_EXACT_ALIGN_PATH_POINT_ORDER "gap-exact-aligner-path-point-order"
 #define GAP_RESPONSE_REFRESH_PATH 1
 
-
+#define GAP_EXACT_ALIGNER_REF_IMAGE "gap-exact-aligner-ref-image"
+#define GAP_EXACT_ALIGNER_PREV_FAME_PHASE "gap-exact-aligner-prev-frame-phase"
 
 typedef struct AlignDialogVals
 {
@@ -63,6 +73,7 @@ typedef struct AlignDialogVals
 
   GtkWidget *radio_order_mode_31_42;
   GtkWidget *radio_order_mode_21_43;
+  GtkWidget *radio_order_mode_1234;
   GtkWidget *infoLabel;
   GtkWidget *okButton;
   GtkWidget *shell;
@@ -70,50 +81,66 @@ typedef struct AlignDialogVals
 } AlignDialogVals;
 
 
-typedef struct PixelCoords
-{
-  gboolean  valid;
-  gint32  px;
-  gint32  py;
-} PixelCoords;
-
-
-typedef struct AlingCoords
-{
-  PixelCoords  currCoords;   /* 1st coords in current frame */
-  PixelCoords  currCoords2;  /* 2nd detail coords in current frame */
-  PixelCoords  startCoords;  /* 1st coords of first processed (reference) frame */
-  PixelCoords  startCoords2; /* 2nd detail coords of first processed frame */
-} AlingCoords;
 
 
 typedef struct ParseContext {
   char  *parsePtr;
   gint32 frameNr;
-  AlingCoords *alingCoords;
+  GapAlignCoords *alignCoords;
 } ParseContext;
 
+typedef struct ShortLists {
+  GapLocateTuneOffsElem *shortListP1;
+  GapLocateTuneOffsElem *shortListP2;
+  GapLocateTuneOffsElem *shortListP3;
+  GapLocateTuneOffsElem *shortListP4;
+} ShortLists;
+
 
 
 #define DEFAULT_framePhase 1
 
 static gboolean     p_parse_value_gint32(ParseContext *parseCtx, gint32 *valDestPtr, gint *itemCount);
-static gboolean     p_parse_coords_p1_and_p2(ParseContext *parseCtx, PixelCoords *p1, PixelCoords *p2, 
gint32 *frameNrPtr
-                      , PixelCoords *s1, PixelCoords *s2);
+static gboolean     p_parse_coords_p1_upto_s4(ParseContext *parseCtx
+                      , GapPixelCoords *p1, GapPixelCoords *p2, GapPixelCoords *p3, GapPixelCoords *p4
+                      , gint32 *frameNrPtr
+                      , GapPixelCoords *s1, GapPixelCoords *s2, GapPixelCoords *s3, GapPixelCoords *s4
+                      , GapPixelCoords *u1, GapPixelCoords *u2, GapPixelCoords *u3, GapPixelCoords *u4);
 static gboolean     p_parse_xml_controlpoint_coords(ParseContext *parseCtx);
-static gboolean     p_parse_xml_controlpoint_coords_from_file(const char *filename, gint32 frameNr, 
AlingCoords *alingCoords);
-static gint32       p_set_drawable_offsets(gint32 activeDrawableId, AlingCoords *alingCoords);
-static gint32       p_exact_align_drawable(gint32 activeDrawableId, AlingCoords *alingCoords);
+static gboolean     p_parse_xml_controlpoint_coords_from_file(const char *filename, gint32 frameNr, 
GapAlignCoords *alignCoords);
+static gint32       p_set_drawable_offsets(gint32 activeDrawableId, GapAlignCoords *alignCoords);
+static gint32       p_exact_align_drawable(gint32 activeDrawableId, GapAlignCoords *alignCoords);
+
+static gint32       p_findRefImage();
+static void         p_layerForceAlphaAndImageSize(gint32 layerId);
+static gint32       p_recreateRefImage(gint32 referenceLayerId, gint32 transformedDrawableId);
+static gint32       p_perspective_align_drawable(gint32 activeDrawableId, GapAlignCoords *alignCoords, 
gint32 framePhase
+                       , gdouble transformPrecisionThreshold, gdouble transformPrecision);
+static void         p_create_or_replace_path_vectors(gint32 imageId
+                      , GapPixelCoords gapPixelCoordsArray[], gint coordsArrayPointsCount, gchar *vectorsName
+                      , gboolean setVisible);
+
 
 /* internal procedures for GUI purpose */
 static void         p_save_gimprc_gint32_value(const char *gimprc_option_name, gint32 value);
-static void         p_exact_align_calculate_4point_values(AlingCoords *alingCoords
+static void         p_exact_align_calculate_4point_values(GapAlignCoords *alignCoords
                       , gdouble *angleDeg, gdouble *scalePercent, gdouble *moveDx, gdouble *moveDy);
 static void         p_refresh_and_update_infoLabel(GtkWidget *widgetDummy, AlignDialogVals *advPtr);
 static void         on_exact_align_response (GtkWidget *widget,
                       gint       response_id, AlignDialogVals *advPtr);
 static void         on_order_mode_radio_callback(GtkWidget *wgt, gpointer user_data);
 static void         p_align_dialog(AlignDialogVals *advPtr);
+static gint         p_capture_4_vector_points(gint32 imageId, GapAlignCoords *alignCoords, gint32 
pointOrder);
+static gint         p_capture_4_vector_points_from_pathname(gint32 imageId, GapAlignCoords *alignCoords, 
gint32 pointOrder, char *vectorsName);
+static void         p_generateFineTuningPerCoords(GapPerspectiveTransCoords *perCoords);
+static void         p_fineTuneProbePerspectiveTransformationOld(gint32 activeDrawableId, gint32 
referenceLayerId, GapPerspectiveTransCoords *perCoords);
+static void         p_fineTuneProbePerspectiveTransformationOld2(gint32 activeDrawableId, gint32 
referenceLayerId, GapAlignCoords *alignCoords, GapPerspectiveTransCoords *perCoords);
+GapLocateTuneOffsElem * p_buildTuningShortList(GapPixelCoords *tunedCoord, GapPixelCoords *untunedCoord);
+GapLocateTuneOffsElem * p_mergeTuningShortList(GapLocateTuneOffsElem *rootShortListA, GapLocateTuneOffsElem 
*rootShortListB);
+static void         p_filterTuningShortList(GapLocateTuneOffsElem *rootShortList);
+static void         p_pickTuneCoordinateVariants(gint32 activeDrawableId, gint32 referenceLayerId, 
GapAlignCoords *alignCoords, GapPerspectiveTransCoords *perCoords, gint32 framePhase, ShortLists *sl);
+static void         p_renderPickedPathVariant(gint32 activeDrawableId, gint32 pickedArrayIdx, GapAlignCoords 
*alignCoords, gint32 framePhase, ShortLists *sl);
+static void         p_fineTuneProbePerspectiveTransformation(gint32 activeDrawableId, gint32 
referenceLayerId, GapAlignCoords *alignCoords, GapPerspectiveTransCoords *perCoords, gint32 framePhase);
 
 
 /* --------------------------------------
@@ -156,53 +183,106 @@ p_parse_value_gint32(ParseContext *parseCtx, gint32 *valDestPtr, gint *itemCount
 
 
 /* --------------------------------
- * p_parse_coords_p1_and_p2
+ * p_parse_coords_p1_upto_s4
  * --------------------------------
- * parse p1x, p1y, p2x, p2y values into p1 (mandatory) and p2 (optional) coordinates
+ * parse p1x, p1y, p2x, p2y ... values into p1 (mandatory) and p2, p3, p4 (optional) coordinates
  * and parse keyframe_abs value int *frameNrPtr (optional if present)
  * multiple occurances are not tolerated.
  * return TRUE on success.
- * Optional parse s1x, s1y, s2x, s2y values into s1 and s2
+ * Optional parse s1x, s1y, s2x, s2y ..  values into s1, s2, s3 and s4
  *   Note that Detail tracking is now capable to track more than 2 points and does select
- *   the 2 best matching results p1 and p2 out of a list of n points. 
+ *   the 2 (or 4) best matching results p1 and p2 out of a list of n points. 
  *   therefore p1 at frame[n] may not correspond to p1 at frame [n-1] as it was in older versions..
  *   as consequence the startpoints s1 (corresponds to p1) and s2 (corresponds to p1)
  *   were added to the xml file, to provide all required information for the current frame 
  *   in the current controlpoint structure.
  *   In case s1 and s2 are not provided in the xml file -- still supported older format --
  *   keep the values unchanged as parsed in the initial call
+ * Optional parse u1x, u1y, u2x, u2y ..  values of untuned coords into u1, u2, u3 and u4
  */
 static gboolean
-p_parse_coords_p1_and_p2(ParseContext *parseCtx, PixelCoords *p1, PixelCoords *p2, gint32 *frameNrPtr
-   , PixelCoords *sn1, PixelCoords *sn2)
+p_parse_coords_p1_upto_s4(ParseContext *parseCtx
+   , GapPixelCoords *p1, GapPixelCoords *p2, GapPixelCoords *p3, GapPixelCoords *p4
+   , gint32 *frameNrPtr
+   , GapPixelCoords *sn1, GapPixelCoords *sn2, GapPixelCoords *sn3, GapPixelCoords *sn4
+   , GapPixelCoords *un1, GapPixelCoords *un2, GapPixelCoords *un3, GapPixelCoords *un4
+   )
 {
   gboolean ok;
+  gboolean ret;
   gint     px1Count;
   gint     py1Count;
   gint     px2Count;
   gint     py2Count;
+
+  gint     px3Count;
+  gint     py3Count;
+  gint     px4Count;
+  gint     py4Count;
   gint     frCount;
+  
   gint     sx1Count;
   gint     sy1Count;
   gint     sx2Count;
   gint     sy2Count;
-  PixelCoords  dummyCoords;
-  PixelCoords *s1;
-  PixelCoords *s2;
-  
+
+  gint     sx3Count;
+  gint     sy3Count;
+  gint     sx4Count;
+  gint     sy4Count;
+
+  gint     ux1Count;
+  gint     uy1Count;
+  gint     ux2Count;
+  gint     uy2Count;
+
+  gint     ux3Count;
+  gint     uy3Count;
+  gint     ux4Count;
+  gint     uy4Count;
+
+  GapPixelCoords  dummyCoords;
+  GapPixelCoords *s1;
+  GapPixelCoords *s2;
+  GapPixelCoords *s3;
+  GapPixelCoords *s4;
+  GapPixelCoords *u1;
+  GapPixelCoords *u2;
+  GapPixelCoords *u3;
+  GapPixelCoords *u4;  
   ok = TRUE;
+  frCount  = 0;
   px1Count = 0;
   py1Count = 0;
   px2Count = 0;
   py2Count = 0;
-  frCount  = 0;
+  px3Count = 0;
+  py3Count = 0;
+  px4Count = 0;
+  py4Count = 0;
+
   sx1Count = 0;
   sy1Count = 0;
   sx2Count = 0;
   sy2Count = 0;
+  sx3Count = 0;
+  sy3Count = 0;
+  sx4Count = 0;
+  sy4Count = 0;
+
+  ux1Count = 0;
+  uy1Count = 0;
+  ux2Count = 0;
+  uy2Count = 0;
+  ux3Count = 0;
+  uy3Count = 0;
+  ux4Count = 0;
+  uy4Count = 0;
   
   s1 = &dummyCoords;
   s2 = &dummyCoords;
+  s3 = &dummyCoords;
+  s4 = &dummyCoords;
   if(sn1 != NULL)
   {
     s1 = sn1;
@@ -211,7 +291,35 @@ p_parse_coords_p1_and_p2(ParseContext *parseCtx, PixelCoords *p1, PixelCoords *p
   {
     s2 = sn2;
   }
-  
+  if(sn3 != NULL)
+  {
+    s3 = sn3;
+  }
+  if(sn4 != NULL)
+  {
+    s4 = sn4;
+  }
+
+  u1 = &dummyCoords;
+  u2 = &dummyCoords;
+  u3 = &dummyCoords;
+  u4 = &dummyCoords;
+  if(un1 != NULL)
+  {
+    u1 = un1;
+  }
+  if(un2 != NULL)
+  {
+    u2 = un2;
+  }
+  if(un3 != NULL)
+  {
+    u3 = un3;
+  }
+  if(un4 != NULL)
+  {
+    u4 = un4;
+  }  
   
   while(*parseCtx->parsePtr != '\0')
   {
@@ -240,6 +348,26 @@ p_parse_coords_p1_and_p2(ParseContext *parseCtx, PixelCoords *p1, PixelCoords *p
       parseCtx->parsePtr += strlen("p2y=\"");
       ok = p_parse_value_gint32(parseCtx, &p2->py, &py2Count);
     }
+    else if (strncmp(parseCtx->parsePtr, "p3x=\"", strlen("p3x=\"")) == 0)
+    {
+      parseCtx->parsePtr += strlen("p3x=\"");
+      ok = p_parse_value_gint32(parseCtx, &p3->px, &px3Count);
+    }
+    else if (strncmp(parseCtx->parsePtr, "p3y=\"", strlen("p3y=\"")) == 0)
+    {
+      parseCtx->parsePtr += strlen("p3y=\"");
+      ok = p_parse_value_gint32(parseCtx, &p3->py, &py3Count);
+    }
+    else if (strncmp(parseCtx->parsePtr, "p4x=\"", strlen("p4x=\"")) == 0)
+    {
+      parseCtx->parsePtr += strlen("p4x=\"");
+      ok = p_parse_value_gint32(parseCtx, &p4->px, &px4Count);
+    }
+    else if (strncmp(parseCtx->parsePtr, "p4y=\"", strlen("p4y=\"")) == 0)
+    {
+      parseCtx->parsePtr += strlen("p4y=\"");
+      ok = p_parse_value_gint32(parseCtx, &p4->py, &py4Count);
+    }    
     /* ------------ startcoordinates ------------------- */
     else if (strncmp(parseCtx->parsePtr, "s1x=\"", strlen("s1x=\"")) == 0)
     {
@@ -261,6 +389,67 @@ p_parse_coords_p1_and_p2(ParseContext *parseCtx, PixelCoords *p1, PixelCoords *p
       parseCtx->parsePtr += strlen("s2y=\"");
       ok = p_parse_value_gint32(parseCtx, &s2->py, &sy2Count);
     }
+    else if (strncmp(parseCtx->parsePtr, "s3x=\"", strlen("s3x=\"")) == 0)
+    {
+      parseCtx->parsePtr += strlen("s3x=\"");
+      ok = p_parse_value_gint32(parseCtx, &s3->px, &sx3Count);
+    }
+    else if (strncmp(parseCtx->parsePtr, "s3y=\"", strlen("s3y=\"")) == 0)
+    {
+      parseCtx->parsePtr += strlen("s3y=\"");
+      ok = p_parse_value_gint32(parseCtx, &s3->py, &sy3Count);
+    }
+    else if (strncmp(parseCtx->parsePtr, "s4x=\"", strlen("s4x=\"")) == 0)
+    {
+      parseCtx->parsePtr += strlen("s4x=\"");
+      ok = p_parse_value_gint32(parseCtx, &s4->px, &sx4Count);
+    }
+    else if (strncmp(parseCtx->parsePtr, "s4y=\"", strlen("s4y=\"")) == 0)
+    {
+      parseCtx->parsePtr += strlen("s4y=\"");
+      ok = p_parse_value_gint32(parseCtx, &s4->py, &sy4Count);
+    }  
+    /* ------------ untuned coords  ------------------- */
+    else if (strncmp(parseCtx->parsePtr, "u1x=\"", strlen("u1x=\"")) == 0)
+    {
+      parseCtx->parsePtr += strlen("u1x=\"");
+      ok = p_parse_value_gint32(parseCtx, &u1->px, &ux1Count);
+    }
+    else if (strncmp(parseCtx->parsePtr, "u1y=\"", strlen("u1y=\"")) == 0)
+    {
+      parseCtx->parsePtr += strlen("u1y=\"");
+      ok = p_parse_value_gint32(parseCtx, &u1->py, &uy1Count);
+    }
+    else if (strncmp(parseCtx->parsePtr, "u2x=\"", strlen("u2x=\"")) == 0)
+    {
+      parseCtx->parsePtr += strlen("u2x=\"");
+      ok = p_parse_value_gint32(parseCtx, &u2->px, &ux2Count);
+    }
+    else if (strncmp(parseCtx->parsePtr, "u2y=\"", strlen("u2y=\"")) == 0)
+    {
+      parseCtx->parsePtr += strlen("u2y=\"");
+      ok = p_parse_value_gint32(parseCtx, &u2->py, &uy2Count);
+    }
+    else if (strncmp(parseCtx->parsePtr, "u3x=\"", strlen("u3x=\"")) == 0)
+    {
+      parseCtx->parsePtr += strlen("u3x=\"");
+      ok = p_parse_value_gint32(parseCtx, &u3->px, &ux3Count);
+    }
+    else if (strncmp(parseCtx->parsePtr, "u3y=\"", strlen("u3y=\"")) == 0)
+    {
+      parseCtx->parsePtr += strlen("u3y=\"");
+      ok = p_parse_value_gint32(parseCtx, &u3->py, &uy3Count);
+    }
+    else if (strncmp(parseCtx->parsePtr, "u4x=\"", strlen("u4x=\"")) == 0)
+    {
+      parseCtx->parsePtr += strlen("u4x=\"");
+      ok = p_parse_value_gint32(parseCtx, &u4->px, &ux4Count);
+    }
+    else if (strncmp(parseCtx->parsePtr, "u4y=\"", strlen("u4y=\"")) == 0)
+    {
+      parseCtx->parsePtr += strlen("u4y=\"");
+      ok = p_parse_value_gint32(parseCtx, &u4->py, &uy4Count);
+    }    
     else if (strncmp(parseCtx->parsePtr, "/>", strlen("/>")) == 0)
     {
       /* stop evaluate when current controlpoint ends */
@@ -283,6 +472,8 @@ p_parse_coords_p1_and_p2(ParseContext *parseCtx, PixelCoords *p1, PixelCoords *p
     }
   }
 
+  ret = FALSE;
+  
   if ((ok == TRUE)
   && (px1Count == 1)
   && (py1Count == 1)
@@ -296,6 +487,18 @@ p_parse_coords_p1_and_p2(ParseContext *parseCtx, PixelCoords *p1, PixelCoords *p
     {
       p2->valid = TRUE;
     }
+    if ((px3Count == 1)
+    &&  (py3Count == 1))
+    {
+      p3->valid = TRUE;
+    }
+    if ((px4Count == 1)
+    &&  (py4Count == 1))
+    {
+      p4->valid = TRUE;
+    }
+
+
 
     if ((sx1Count == 1)
     && (sy1Count == 1)
@@ -303,6 +506,7 @@ p_parse_coords_p1_and_p2(ParseContext *parseCtx, PixelCoords *p1, PixelCoords *p
     {
       s1->valid = TRUE;
     }
+
     if ((sx2Count == 1)
     && (sy2Count == 1)
     && (s2 != NULL))
@@ -310,29 +514,94 @@ p_parse_coords_p1_and_p2(ParseContext *parseCtx, PixelCoords *p1, PixelCoords *p
       s2->valid = TRUE;
     }
 
+    if ((sx3Count == 1)
+    && (sy3Count == 1)
+    && (s3 != NULL))
+    {
+      s3->valid = TRUE;
+    }
 
+    if ((sx4Count == 1)
+    && (sy4Count == 1)
+    && (s4 != NULL))
+    {
+      s4->valid = TRUE;
+    }
+
+    if ((ux1Count == 1)
+    && (uy1Count == 1)
+    && (u1 != NULL))
+    {
+      u1->valid = TRUE;
+    }
+
+    if ((ux2Count == 1)
+    && (uy2Count == 1)
+    && (u2 != NULL))
+    {
+      u2->valid = TRUE;
+    }
+
+    if ((ux3Count == 1)
+    && (uy3Count == 1)
+    && (u3 != NULL))
+    {
+      u3->valid = TRUE;
+    }
+
+    if ((ux4Count == 1)
+    && (uy4Count == 1)
+    && (u4 != NULL))
+    {
+      u4->valid = TRUE;
+    }
+    
+    ret = TRUE;
 
-    return (TRUE);
   }
 
 
   
   if(gap_debug)
   {
-    printf("p_parse_coords_p1_and_p2 ok:%d px1Count:%d py1Count:%d px2Count:%d py2Count:%d frCount:%d\n"
-           " parsePtr:%.200s\n" 
+    printf("p_parse_coords_p1_upto_s4 ok:%d "
+           " px1Count:%d py1Count:%d px2Count:%d py2Count:%d px3Count:%d py3Count:%d px4Count:%d 
py4Count:%d\n"
+           " sx1Count:%d sy1Count:%d sx2Count:%d sy2Count:%d sx3Count:%d sy3Count:%d sx4Count:%d 
sy4Count:%d\n"
+           " ux1Count:%d uy1Count:%d ux2Count:%d uy2Count:%d ux3Count:%d uy3Count:%d ux4Count:%d 
uy4Count:%d\n"
+           "  frCount:%d parsePtr:%.400s\n\n" 
       ,(int)ok
       ,(int)px1Count
       ,(int)py1Count
       ,(int)px2Count
       ,(int)py2Count
+      ,(int)px3Count
+      ,(int)py3Count
+      ,(int)px4Count
+      ,(int)py4Count
+      ,(int)sx1Count
+      ,(int)sy1Count
+      ,(int)sx2Count
+      ,(int)sy2Count
+      ,(int)sx3Count
+      ,(int)sy3Count
+      ,(int)sx4Count
+      ,(int)sy4Count
+      ,(int)ux1Count
+      ,(int)uy1Count
+      ,(int)ux2Count
+      ,(int)uy2Count
+      ,(int)ux3Count
+      ,(int)uy3Count
+      ,(int)ux4Count
+      ,(int)uy4Count
       ,(int)frCount
       ,parseCtx->parsePtr 
       );
   }
-  return (FALSE);
+
+  return (ret);
   
-}  /* end p_parse_coords_p1_and_p2 */
+}  /* end p_parse_coords_p1_upto_s4 */
 
 
 static void
@@ -378,24 +647,41 @@ p_parse_xml_controlpoint_coords(ParseContext *parseCtx)
     {
       parseCtx->parsePtr += strlen("<controlpoint ");
       
-      if(parseCtx->alingCoords->startCoords.valid == FALSE)
+      if(parseCtx->alignCoords->startCoords[0].valid == FALSE)
       {
-        ok = p_parse_coords_p1_and_p2(parseCtx
-                               , &parseCtx->alingCoords->startCoords
-                               , &parseCtx->alingCoords->startCoords2
+        ok = p_parse_coords_p1_upto_s4(parseCtx
+                               , &parseCtx->alignCoords->startCoords[0]
+                               , &parseCtx->alignCoords->startCoords[1]
+                               , &parseCtx->alignCoords->startCoords[2]
+                               , &parseCtx->alignCoords->startCoords[3]
                                , &frameNr
                                , NULL
                                , NULL
+                               , NULL
+                               , NULL
+
+                               , NULL
+                               , NULL
+                               , NULL
+                               , NULL
                                );
       }
       else
       {
-        ok = p_parse_coords_p1_and_p2(parseCtx
-                               , &parseCtx->alingCoords->currCoords
-                               , &parseCtx->alingCoords->currCoords2
+        ok = p_parse_coords_p1_upto_s4(parseCtx
+                               , &parseCtx->alignCoords->currCoords[0]
+                               , &parseCtx->alignCoords->currCoords[1]
+                               , &parseCtx->alignCoords->currCoords[2]
+                               , &parseCtx->alignCoords->currCoords[3]
                                , &frameNr
-                               , &parseCtx->alingCoords->startCoords
-                               , &parseCtx->alingCoords->startCoords2
+                               , &parseCtx->alignCoords->startCoords[0]
+                               , &parseCtx->alignCoords->startCoords[1]
+                               , &parseCtx->alignCoords->startCoords[2]
+                               , &parseCtx->alignCoords->startCoords[3]
+                               , &parseCtx->alignCoords->untunedCoords[0]
+                               , &parseCtx->alignCoords->untunedCoords[1]
+                               , &parseCtx->alignCoords->untunedCoords[2]
+                               , &parseCtx->alignCoords->untunedCoords[3]
                                );
       }
 
@@ -414,7 +700,7 @@ p_parse_xml_controlpoint_coords(ParseContext *parseCtx)
       }
 
       if ((frameNr == parseCtx->frameNr)
-      &&  (parseCtx->alingCoords->currCoords2.valid == TRUE))
+      &&  (parseCtx->alignCoords->currCoords[1].valid == TRUE))
       {
         return(TRUE);
       }
@@ -424,7 +710,7 @@ p_parse_xml_controlpoint_coords(ParseContext *parseCtx)
 
 
   if ((ok == TRUE)
-  &&  (parseCtx->alingCoords->currCoords2.valid == TRUE))
+  &&  (parseCtx->alignCoords->currCoords[1].valid == TRUE))
   {
     /* accept the last controlpoint when no matching frameNr was found */
     return(TRUE);
@@ -443,7 +729,7 @@ p_parse_xml_controlpoint_coords(ParseContext *parseCtx)
  * return TRUE on success.
  */
 static gboolean
-p_parse_xml_controlpoint_coords_from_file(const char *filename, gint32 frameNr, AlingCoords *alingCoords)
+p_parse_xml_controlpoint_coords_from_file(const char *filename, gint32 frameNr, GapAlignCoords *alignCoords)
 {
   char *textBuffer;
   gsize lengthTextBuffer;
@@ -461,13 +747,13 @@ p_parse_xml_controlpoint_coords_from_file(const char *filename, gint32 frameNr,
   }
 
   parseCtx.parsePtr = textBuffer;
-  parseCtx.alingCoords = alingCoords;
+  parseCtx.alignCoords = alignCoords;
   parseCtx.frameNr = frameNr;
 
-  parseCtx.alingCoords->startCoords.valid  = FALSE;
-  parseCtx.alingCoords->startCoords2.valid = FALSE;
-  parseCtx.alingCoords->currCoords.valid   = FALSE;
-  parseCtx.alingCoords->currCoords2.valid  = FALSE;
+  parseCtx.alignCoords->startCoords[0].valid  = FALSE;
+  parseCtx.alignCoords->startCoords[1].valid = FALSE;
+  parseCtx.alignCoords->currCoords[0].valid   = FALSE;
+  parseCtx.alignCoords->currCoords[1].valid  = FALSE;
   
   parseOk = p_parse_xml_controlpoint_coords(&parseCtx);
     
@@ -485,7 +771,7 @@ p_parse_xml_controlpoint_coords_from_file(const char *filename, gint32 frameNr,
  * simple 2-point align via offsets (without rotate and scale)
  */
 static gint32
-p_set_drawable_offsets(gint32 activeDrawableId, AlingCoords *alingCoords)
+p_set_drawable_offsets(gint32 activeDrawableId, GapAlignCoords *alignCoords)
 {
   gdouble px1, py1, px2, py2;
   gdouble dx, dy;
@@ -493,10 +779,10 @@ p_set_drawable_offsets(gint32 activeDrawableId, AlingCoords *alingCoords)
   gint    offset_y;
   
 
-  px1 = alingCoords->startCoords.px;
-  py1 = alingCoords->startCoords.py;
-  px2 = alingCoords->currCoords.px;
-  py2 = alingCoords->currCoords.py;
+  px1 = alignCoords->startCoords[0].px;
+  py1 = alignCoords->startCoords[0].py;
+  px2 = alignCoords->currCoords[0].px;
+  py2 = alignCoords->currCoords[0].py;
 
   dx = px2 - px1;
   dy = py2 - py1;
@@ -517,7 +803,7 @@ p_set_drawable_offsets(gint32 activeDrawableId, AlingCoords *alingCoords)
  * to match 2 pairs of corresponding coordonates.
  */
 static gint32
-p_exact_align_drawable(gint32 activeDrawableId, AlingCoords *alingCoords)
+p_exact_align_drawable(gint32 activeDrawableId, GapAlignCoords *alignCoords)
 {
   gdouble px1, py1, px2, py2;
   gdouble px3, py3, px4, py4;
@@ -527,15 +813,15 @@ p_exact_align_drawable(gint32 activeDrawableId, AlingCoords *alingCoords)
   gdouble scaleXY;
   gint32  transformedDrawableId;
   
-  px1 = alingCoords->startCoords.px;
-  py1 = alingCoords->startCoords.py;
-  px2 = alingCoords->startCoords2.px;
-  py2 = alingCoords->startCoords2.py;
+  px1 = alignCoords->startCoords[0].px;
+  py1 = alignCoords->startCoords[0].py;
+  px2 = alignCoords->startCoords[1].px;
+  py2 = alignCoords->startCoords[1].py;
   
-  px3 = alingCoords->currCoords.px;
-  py3 = alingCoords->currCoords.py;
-  px4 = alingCoords->currCoords2.px;
-  py4 = alingCoords->currCoords2.py;
+  px3 = alignCoords->currCoords[0].px;
+  py3 = alignCoords->currCoords[0].py;
+  px4 = alignCoords->currCoords[1].px;
+  py4 = alignCoords->currCoords[1].py;
 
   dx1 = px2 - px1;
   dy1 = py2 - py1;
@@ -612,6 +898,406 @@ p_exact_align_drawable(gint32 activeDrawableId, AlingCoords *alingCoords)
   
 }  /* end p_exact_align_drawable */
 
+
+
+
+/* -----------------------------------
+ * p_findGint32Value
+ * -----------------------------------
+ * find the reference image (used in previous calls in the same gimp session)
+ */
+static gint32
+p_findGint32Value(const char *key)
+{
+  gint32 value;
+  int l_len;
+  
+  /* init default value */
+  value = -1;
+
+  l_len = gimp_get_data_size (key);
+  if (l_len == sizeof(gint32))
+  {
+    gimp_get_data (key, &value);
+  }
+
+  return (value);
+
+}  /* end p_findGint32Value */
+
+
+/* -----------------------------------
+ * p_findRefImage
+ * -----------------------------------
+ * find the reference image (used in previous calls in the same gimp session)
+ */
+static gint32
+p_findRefImage()
+{
+  gint32 refImageId;
+  refImageId = p_findGint32Value(GAP_EXACT_ALIGNER_REF_IMAGE);
+  return (refImageId);
+}  /* end p_findRefImage */
+
+/* -----------------------------------
+ * p_layerForceAlphaAndImageSize
+ * -----------------------------------
+ * make sure that the layer has alpha channel
+ * and is same size as its image.
+ */
+static void 
+p_layerForceAlphaAndImageSize(gint32 layerId)
+{
+  if(! gimp_drawable_has_alpha(layerId))
+  {
+     /* have to add alpha channel */
+     gimp_layer_add_alpha(layerId);
+  }
+
+  gimp_layer_resize_to_image_size(layerId);
+
+}  /* end p_layerForceAlphaAndImageSize */
+
+
+/* -----------------------------------
+ * p_recreateRefImage
+ * -----------------------------------
+ * The reference image is upadted or created from referenceLayerId and the transformedDrawableId.
+ * it typically has just one layer named "REF" (GAP_EXACT_ALIGNER_REF_LAYER_NAME)
+ * and is used as dynamic changing reference for fine tuning purpose
+ * to represent the previous rendered frame,
+ * where only the opaque pixels of the initial reference layer are set opaque.
+ * Note that opaque pixels typically are used to mark samll background areas around tracking points
+ * that will be used for fine tuning the perspective transformation when rendering the current frame.
+ * 
+ * returns the id of the (re)created reference layer in the reference image.
+ */
+static gint32    p_recreateRefImage(gint32 referenceLayerId, gint32 transformedDrawableId)
+{
+  gint32 refImageId;
+  gint32 tmpBgLayerId;
+  gint32 tmpTopLayerId;
+  gint32 newRefLayerId;
+  gint32 layermaskId;
+  gint    l_src_offset_x, l_src_offset_y;    /* layeroffsets as they were in src_image */
+  gboolean addDisplay;
+
+  addDisplay = FALSE;
+  refImageId = p_findRefImage();
+
+  if (gap_image_is_alive(refImageId))
+  {
+    tmpBgLayerId = gimp_image_merge_visible_layers (refImageId, GIMP_CLIP_TO_IMAGE);
+  }
+  else
+  {
+    /* create a new ref image */
+    addDisplay = TRUE;                    
+    refImageId = gap_image_new_of_samesize(gimp_item_get_image(referenceLayerId));
+
+   
+    /* copy referenceLayerId as tmpBgLayerId Layer to refImageId */
+    tmpBgLayerId = gap_layer_copy_to_dest_image(refImageId
+                                                , referenceLayerId   /* l_src_layer_id */
+                                                , 100.0   /* Opacity */
+                                                ,0        /* NORMAL */
+                                                ,&l_src_offset_x
+                                                ,&l_src_offset_y
+                                                );
+    gimp_image_insert_layer(refImageId
+                           , tmpBgLayerId
+                           , 0
+                           , 0              /* top of layerstack */
+                           );
+                           
+  }
+
+  p_layerForceAlphaAndImageSize(tmpBgLayerId);
+  
+  
+  tmpTopLayerId = gap_layer_copy_to_dest_image(refImageId
+                                               , transformedDrawableId   /* l_src_layer_id */
+                                               , 100.0   /* Opacity */
+                                               ,0        /* NORMAL */
+                                               ,&l_src_offset_x
+                                               ,&l_src_offset_y
+                                               );
+  gimp_image_insert_layer(refImageId
+                          , tmpTopLayerId
+                          , 0
+                          , 0              /* top of layerstack */
+                          );
+  gimp_layer_set_offsets(tmpTopLayerId, l_src_offset_x, l_src_offset_y);
+
+
+  p_layerForceAlphaAndImageSize(tmpTopLayerId);
+  
+  layermaskId = gimp_layer_create_mask(tmpTopLayerId, GIMP_ADD_WHITE_MASK);
+  gimp_layer_add_mask(tmpTopLayerId, layermaskId);
+
+  /* copy the alpha channel from BG to the layermask of the top layer */
+  gap_layer_copy_picked_channel(layermaskId, 0    /* dst_pick is the alpha channel */
+                               ,tmpBgLayerId, 3     /* src_pick is the alpha channel */
+                               ,FALSE               /* shadow */
+                               );
+  
+  newRefLayerId = gimp_image_merge_down(refImageId, tmpTopLayerId, GIMP_EXPAND_AS_NECESSARY);
+  
+  gimp_item_set_name(newRefLayerId, GAP_EXACT_ALIGNER_REF_LAYER_NAME);
+
+  if (addDisplay)
+  {
+    gimp_display_new(refImageId);
+    gimp_displays_flush();
+  }
+  
+  if(gap_debug)
+  {
+    printf("p_recreateRefImage setData:%s len:%d  refImageId:%d newRefLayerId:%d\n"
+      , GAP_EXACT_ALIGNER_REF_IMAGE
+      , (int)sizeof (gint32)
+      , (int)refImageId
+      , (int)newRefLayerId
+      );
+  }
+  gimp_set_data (GAP_EXACT_ALIGNER_REF_IMAGE
+                    , &refImageId, sizeof (gint32));
+                    
+  return (newRefLayerId);
+
+}  /* end p_recreateRefImage */
+
+
+/* -----------------------------------
+ * p_perspective_align_drawable
+ * -----------------------------------
+ * 8-point alignment including necessary perspective transformation
+ * to match 4 pairs of corresponding coordonates.
+ */
+static gint32
+p_perspective_align_drawable(gint32 activeDrawableId, GapAlignCoords *alignCoords, gint32 framePhase
+  , gdouble transformPrecisionThreshold, gdouble transformPrecision)
+{
+  GapPerspectiveTransCoords  perspectiveCoords;
+  GapPerspectiveTransCoords *perCoords;
+  gboolean perCoordsOk;
+  gint32  transformedDrawableId;
+  
+  
+  if(gap_debug)
+  {
+    printf("p_perspective_align_drawable: Start on activeDrawableId:%d PrecisionThreshold:%f Precision:%f 
framePhase:%d\n"
+       , (int)activeDrawableId
+       , (double)transformPrecisionThreshold
+       , (double)transformPrecision
+       , (int)framePhase
+       );
+  }  
+  
+  perCoords = &perspectiveCoords;
+  perCoords->transformPrecisionThreshold = transformPrecisionThreshold;
+  perCoords->transformPrecision          = transformPrecision;
+
+  perCoordsOk = gap_geo_perspective_trans_coords_from_align_coords(activeDrawableId, alignCoords, perCoords);
+  if (perCoordsOk == TRUE)
+  {
+    gint32 referenceLayerId;
+    gint32 refImageId;
+    
+    referenceLayerId = gimp_image_get_layer_by_name(gimp_item_get_image(activeDrawableId)
+                                                   , GAP_EXACT_ALIGNER_REF_LAYER_NAME
+                                                   );
+    refImageId = -1;
+    if (framePhase > 1)
+    {
+      refImageId = p_findRefImage();
+    }
+
+    if(gap_debug)
+    {
+      printf("p_perspective_align_drawable referenceLayerId:%d refImageId:%d AT framePhase:%d\n"
+        , (int)referenceLayerId
+        , (int)refImageId
+        , (int)framePhase
+        );
+    }
+
+    
+    if((referenceLayerId < 0) && (refImageId >= 0))
+    {
+       referenceLayerId = gimp_image_get_layer_by_name(refImageId
+                                                   , GAP_EXACT_ALIGNER_REF_LAYER_NAME
+                                                   );
+    }
+
+    if(gap_debug)
+    {
+      printf("p_perspective_align_drawable searchresult for layername %s is referenceLayerId:%d 
refImageId:%d AT framePhase:%d\n"
+        , GAP_EXACT_ALIGNER_REF_LAYER_NAME
+        , (int)referenceLayerId
+        , (int)refImageId
+        , (int)framePhase
+        );
+    }
+
+
+    if(referenceLayerId >= 0)
+    {
+      /* The presence of a reference layer triggers the fine tuning probe rendering
+       * on a temporary created image to select the best matching transformation.
+       */
+      /// p_fineTuneProbePerspectiveTransformationOld(activeDrawableId, referenceLayerId, perCoords);  // 
TODO remove this..
+      /// p_fineTuneProbePerspectiveTransformationOld2(activeDrawableId, referenceLayerId, alignCoords, 
perCoords);
+      p_fineTuneProbePerspectiveTransformation(activeDrawableId, referenceLayerId, alignCoords, perCoords, 
framePhase);
+
+    }
+
+  
+    gimp_context_set_defaults();
+    gimp_context_set_transform_resize(GIMP_TRANSFORM_RESIZE_ADJUST);   /* do NOT clip */                     
            
+    gimp_context_set_transform_direction(GIMP_TRANSFORM_FORWARD);                                 
+    transformedDrawableId = gimp_item_transform_perspective(activeDrawableId
+                              , perCoords->x0, perCoords->y0
+                              , perCoords->x1, perCoords->y1
+                              , perCoords->x2, perCoords->y2
+                              , perCoords->x3, perCoords->y3
+                              );
+    
+    // DISABLED the dynamic change of the reference image (that may produce jitter effects ?)
+    if(FALSE) // if(referenceLayerId >= 0)
+    {
+      gint32 prevFramePhase;
+      
+      prevFramePhase = p_findGint32Value(GAP_EXACT_ALIGNER_PREV_FAME_PHASE);
+
+      /* note that frames modify typically calls this filter
+       * with framePhase sequence 1, n, 2, 3, ... n-1
+       * Therefore skip recreation of the reference image in the 2nd call whre framePhase == n
+       */
+      if ((framePhase == 1)
+      ||  (framePhase == prevFramePhase +1))
+      {
+        p_recreateRefImage(referenceLayerId, transformedDrawableId);
+      }
+
+      prevFramePhase = framePhase;
+      gimp_set_data (GAP_EXACT_ALIGNER_PREV_FAME_PHASE
+                    , &prevFramePhase, sizeof (gint32));
+    }
+    
+    if(gap_debug)
+    {
+      printf("p_perspective_align_drawable: activeDrawableId:%d transformedDrawableId:%d\n"
+             "    p0: %f %f\n"
+             "    p1: %f %f\n"
+             "    p2: %f %f\n"
+             "    p3: %f %f\n"
+        ,(int)activeDrawableId
+        ,(int)transformedDrawableId
+        ,(float)perCoords->x0
+        ,(float)perCoords->y0
+        ,(float)perCoords->x1
+        ,(float)perCoords->y1
+        ,(float)perCoords->x2
+        ,(float)perCoords->y2
+        ,(float)perCoords->x3
+        ,(float)perCoords->y3
+        );
+    }
+    return (transformedDrawableId);  
+
+  }
+  else
+  {
+    if(gap_debug)
+    {
+      printf("p_perspective_align_drawable: activeDrawableId:%d FAILED\n"
+        ,(int)activeDrawableId
+        );
+    }
+    return (-1);
+  }
+  
+}  /* end p_perspective_align_drawable */
+
+
+/* --------------------------------
+ * p_create_or_replace_path_vectors
+ * --------------------------------
+ * if the image already contains a vectors object with the specified vectorsName
+ * then  replace it with the points in gapPixelCoordsArray.
+ * 
+ * in case there is no vectors object with the specified vectorsName create it and add it to the image.
+ *
+ */
+static void
+p_create_or_replace_path_vectors(gint32 imageId, GapPixelCoords gapPixelCoordsArray[], gint 
coordsArrayPointsCount, gchar *vectorsName
+   , gboolean setVisible)
+{
+  gint32  vectorsId;
+
+  gdouble  *points;
+  gint      num_points;
+  gint      l_idx;
+  gboolean  closed;
+  GimpVectorsStrokeType type;
+  GapPixelCoords *targetCoords;
+  
+  vectorsId = gimp_image_get_vectors_by_name(imageId, vectorsName);
+  if(vectorsId >= 0)
+  {
+    gimp_image_remove_vectors(imageId, vectorsId);
+  } 
+
+  /* create new vectors path */
+  vectorsId = gimp_vectors_new(imageId, vectorsName);
+
+  
+  if(vectorsId >= 0)  
+  {
+    num_points = 6 * GAP_ALIGN_COORDS_MAX;
+    points = g_new (gdouble, num_points);
+  
+    for(l_idx = 0; l_idx < coordsArrayPointsCount; l_idx++)
+    {
+      gdouble pdx;
+      gdouble pdy;
+      gint    offset;
+    
+      offset = l_idx * 6;
+      targetCoords  = &gapPixelCoordsArray[l_idx];
+
+      pdx = targetCoords->px;
+      pdy = targetCoords->py;
+
+      points[0 + offset] = pdx;
+      points[1 + offset] = pdy;
+      points[2 + offset] = pdx;
+      points[3 + offset] = pdy;
+      points[4 + offset] = pdx;
+      points[5 + offset] = pdy;
+    }
+
+  
+    closed = FALSE;
+    type = GIMP_VECTORS_STROKE_TYPE_BEZIER;
+    /* newStrokeId = */ gimp_vectors_stroke_new_from_points (vectorsId
+                                   , type
+                                   , num_points
+                                   , points
+                                   , closed
+                                   );
+    g_free(points);
+  
+    gimp_image_insert_vectors(imageId, vectorsId, -1, 0);
+    gimp_item_set_visible(vectorsId, setVisible);
+
+  }
+  
+
+}  /* end p_create_or_replace_path_vectors */
+
+
 /* -----------------------------------
  * gap_detail_xml_align
  * -----------------------------------
@@ -621,6 +1307,15 @@ p_exact_align_drawable(gint32 activeDrawableId, AlingCoords *alingCoords)
  * and scales, rotates and aligns the specified drawableId (shall be a layer)
  * in a way that it exactly matches with the 1st (reference) controlpoint in the XML file.
  *
+ * in case the XML file provides 4 tracked points and 4 start (referece) points
+ * the alignment is done via perspective transformation.
+ * 
+ * for usable results the tracked points shall meet the follwing conditions:
+ * p1 shall be in the upper left quadrant
+ * p2 shall be in the upper right quadrant
+ * p3 shall be in the lower right quadrant
+ * p4 shall be in the lower left quadrant
+ *
  * returns the drawable id of the resulting transformed layer (or -1 on errors)s
  */
 gint32
@@ -638,27 +1333,109 @@ gap_detail_xml_align(gint32 drawableId, XmlAlignValues *xaVals)
             );
   }
   
-  if(xaVals->framePhase > 1)
+  if(xaVals->framePhase <= 1)
+  {
+    gint32 referenceLayerId;
+
+    /* when handling the 1st frame (framePhase == 1)
+     * recreate the reference image in case the 1st frame contains a reference layer named "REF".
+     * (or clear to -1 in case no REF layer is present)
+     */
+    
+    referenceLayerId = gimp_image_get_layer_by_name(gimp_item_get_image(drawableId)
+                                                   , GAP_EXACT_ALIGNER_REF_LAYER_NAME
+                                                   );
+    if(referenceLayerId >= 0)
+    {
+      p_recreateRefImage(referenceLayerId, referenceLayerId);
+    }
+    else
+    {
+      gint32 refImageId;
+      refImageId = -1;
+      gimp_set_data (GAP_EXACT_ALIGNER_REF_IMAGE
+                    , &refImageId, sizeof (gint32));
+    }
+  }
+  else
   {
     gboolean  parseOk;
-    AlingCoords alingCoords;
+    GapAlignCoords alignCoords;
     
     parseOk =
       p_parse_xml_controlpoint_coords_from_file(xaVals->moveLogFile
-         , xaVals->framePhase, &alingCoords);
+         , xaVals->framePhase, &alignCoords);
 
     if(parseOk)
     {
-      if ((alingCoords.startCoords2.valid == TRUE)
-      &&  (alingCoords.currCoords2.valid == TRUE))
+      gint idx;
+      gint countValidPairs;
+      
+      countValidPairs = 0;
+      for(idx = 0; idx < 4; idx++)
+      {
+        if ((alignCoords.startCoords[idx].valid == TRUE)
+        &&  (alignCoords.currCoords[idx].valid == TRUE))
+        {
+          countValidPairs++;
+        }
+      }
+      
+      if(gap_debug)
+      {
+        printf("gap_detail_xml_align: framePhase:%d  countValidPairs:%d\n"
+                  , (int)xaVals->framePhase
+                  , (int)countValidPairs
+               );
+      }
+
+      if (countValidPairs > 1)
+      {
+        gint32 imageId;
+        
+        imageId = gimp_item_get_image(drawableId);
+        
+        /* import the xml align coordinates as SRC and TARGET path vectors
+         * (this is not relevant for automatical processing, but is useful for analyse purpose
+         * and allows manually fixes afterwards)
+         */
+        if (alignCoords.untunedCoords[0].valid && alignCoords.untunedCoords[3].valid)
+        {
+          p_create_or_replace_path_vectors(imageId
+                                       , &alignCoords.untunedCoords[0], countValidPairs
+                                       , GAP_EXACT_ALIGNER_USRC_PATH_NAME /* vectorsName */
+                                       , TRUE /* setVisible */ 
+                                       );
+        }
+        p_create_or_replace_path_vectors(imageId
+                                       , &alignCoords.startCoords[0], countValidPairs
+                                       , GAP_EXACT_ALIGNER_TARGET_PATH_NAME /* vectorsName */
+                                       , TRUE /* setVisible */ 
+                                       );
+        p_create_or_replace_path_vectors(imageId
+                                       , &alignCoords.currCoords[0], countValidPairs
+                                       , GAP_EXACT_ALIGNER_SRC_PATH_NAME /* vectorsName */
+                                       , TRUE /* setVisible */ 
+                                       );
+      }
+      
+      
+      if (countValidPairs == 4)
+      {
+        /* perspective align transformation with 4 point pairs */
+        newDrawableId = p_perspective_align_drawable(drawableId, &alignCoords, xaVals->framePhase
+                           , xaVals->transformPrecisionThreshold, xaVals->transformPrecision);
+        
+      } else if ((alignCoords.startCoords[1].valid == TRUE)
+             &&  (alignCoords.currCoords[1].valid == TRUE))
       {
         /* exact align transformation with 2 point pairs including rotation and scaling */
-        newDrawableId = p_exact_align_drawable(drawableId, &alingCoords);
+        newDrawableId = p_exact_align_drawable(drawableId, &alignCoords);
       }
       else
       {
         /* simple move (to match current recorded point to recorded start point) */
-        newDrawableId = p_set_drawable_offsets(drawableId, &alingCoords);
+        newDrawableId = p_set_drawable_offsets(drawableId, &alignCoords);
       }
     }
     else
@@ -693,8 +1470,10 @@ gap_detail_xml_align_get_values(XmlAlignValues *xaVals)
   int l_len;
 
   /* init default values */
-  xaVals->framePhase                 = DEFAULT_framePhase;
-  xaVals->moveLogFile[0]             = '\0';
+  xaVals->framePhase                  = DEFAULT_framePhase;
+  xaVals->transformPrecisionThreshold = GAP_GEO_TRANSFORM_PRECISION_THRSHOLD;
+  xaVals->transformPrecision          = GAP_GEO_TRANSFORM_PRECISION;
+  xaVals->moveLogFile[0]              = '\0';
 
   l_len = gimp_get_data_size (GAP_DETAIL_TRACKING_XML_ALIGNER_PLUG_IN_NAME);
   if (l_len == sizeof(XmlAlignValues))
@@ -713,8 +1492,10 @@ gap_detail_xml_align_get_values(XmlAlignValues *xaVals)
   if(gap_debug)
   {
     printf("gap_detail_xml_align_get_values:\n"
-           "  framePhase:%d  moveLogFile:%s\n"
+           "  framePhase:%d  transformPrecisionThreshold:%f  transformPrecision:%f moveLogFile:%s\n"
             , (int)xaVals->framePhase
+            , (double)xaVals->transformPrecisionThreshold
+            , (double)xaVals->transformPrecision
             , xaVals->moveLogFile
             );
   }
@@ -733,11 +1514,13 @@ gboolean
 gap_detail_xml_align_dialog(XmlAlignValues *xaVals)
 {
 #define SPINBUTTON_ENTRY_WIDTH 70
-#define DETAIL_ALIGN_XML_DIALOG_ARGC 3
+#define DETAIL_ALIGN_XML_DIALOG_ARGC 5
 
   static GapArrArg  argv[DETAIL_ALIGN_XML_DIALOG_ARGC];
   gint ii;
   gint ii_framePhase;
+  gint ii_precisionThres;
+  gint ii_precision;
 
   ii=0; gap_arr_arg_init(&argv[ii], GAP_ARR_WGT_INT_PAIR); ii_framePhase = ii;
   argv[ii].label_txt = _("Frame Phase:");
@@ -762,6 +1545,51 @@ gap_detail_xml_align_dialog(XmlAlignValues *xaVals)
   argv[ii].entry_width = 400;
 
 
+  ii++; ii_precision = ii;
+  gap_arr_arg_init(&argv[ii], GAP_ARR_WGT_FLT_PAIR);
+  argv[ii].constraint = TRUE;
+  argv[ii].label_txt = _("Precision:");
+  argv[ii].help_txt  = _("Precision (in pixels) for calculation of perspective transformation matrix. "
+                         "Smaller values give more precision (and need more iterations at calculation)");
+  argv[ii].flt_min   = 0.0;
+  argv[ii].flt_max   = 1.0;
+  argv[ii].flt_step  = 0.01;
+  argv[ii].pagestep  = 0.1;
+  argv[ii].flt_digits = 4;
+  argv[ii].flt_ret   = GAP_GEO_TRANSFORM_PRECISION;
+  if(xaVals->transformPrecision > 0)
+  {
+    argv[ii].flt_ret   = xaVals->transformPrecision;
+  }
+  argv[ii].has_default = TRUE;
+  argv[ii].flt_default = GAP_GEO_TRANSFORM_PRECISION;
+  
+  ii++; ii_precisionThres = ii;
+  gap_arr_arg_init(&argv[ii], GAP_ARR_WGT_FLT_PAIR);
+  argv[ii].constraint = TRUE;
+  argv[ii].label_txt = _("PrecisionThreshold:");
+  argv[ii].help_txt  = _("Threshold for fine tuning purpose. "
+                         "Iterative calulated coordinates with precision lower than this threshold "
+                         "are used for fine tuning probe render attempts. "
+                         "icreasing the threshold results in more probe attempts "
+                         "and makes processng very slow but typically reduces jitter effects."
+                         "Setting the threshold smaller than precision diasbles finetuning probe rendering."
+                         "Note that finetuning also depends on the presence of a reference layer "
+                         "with layername REF in the 1st handled frame");
+  argv[ii].flt_min   = 0.0;
+  argv[ii].flt_max   = 2.0;
+  argv[ii].flt_step  = 0.01;
+  argv[ii].pagestep  = 0.1;
+  argv[ii].flt_digits = 4;
+  argv[ii].flt_ret   = GAP_GEO_TRANSFORM_PRECISION_THRSHOLD;
+  if(xaVals->transformPrecisionThreshold > 0)
+  {
+    argv[ii].flt_ret   = xaVals->transformPrecisionThreshold;
+  }
+  argv[ii].has_default = TRUE;
+  argv[ii].flt_default = GAP_GEO_TRANSFORM_PRECISION_THRSHOLD;
+    
+
 
   ii++; gap_arr_arg_init(&argv[ii], GAP_ARR_WGT_DEFAULT_BUTTON);
   argv[ii].label_txt = _("Default");
@@ -771,7 +1599,9 @@ gap_detail_xml_align_dialog(XmlAlignValues *xaVals)
                             _("Settings :"),
                             DETAIL_ALIGN_XML_DIALOG_ARGC, argv))
   {
-      xaVals->framePhase               = (gint32)(argv[ii_framePhase].int_ret);
+      xaVals->framePhase                  = (gint32)(argv[ii_framePhase].int_ret);
+      xaVals->transformPrecisionThreshold = (gdouble)(argv[ii_precisionThres].flt_ret);
+      xaVals->transformPrecision          = (gdouble)(argv[ii_precision].flt_ret);
       
       gimp_set_data (GAP_DETAIL_TRACKING_XML_ALIGNER_PLUG_IN_NAME
                     , xaVals, sizeof (XmlAlignValues));
@@ -784,7 +1614,6 @@ gap_detail_xml_align_dialog(XmlAlignValues *xaVals)
 }               /* end gap_detail_xml_align_dialog */
 
 
-
 /* ---------------------------------- */
 /* ---------------------------------- */
 /* ---------------------------------- */
@@ -800,41 +1629,97 @@ gap_detail_xml_align_dialog(XmlAlignValues *xaVals)
  * capture the first 4 points of the 1st stroke in the active path vectors
  * pointOrder POINT_ORDER_MODE_31_42 : order 0,1,2,3  compatible with the exactAligner script
  *            POINT_ORDER_MODE_21_43 : order 0,2,1,3  alternative point order.
+ *            POINT_ORDER_MODE_1234_1234: alternative with 2 paths, 
+ *                                      using active path as Source mark 4 points in the active layer
+ *                                      and sperate path vectors with name "TARGET"
+ *                                        
  */
 static gint
-p_capture_4_vector_points(gint32 imageId, AlingCoords *alingCoords, gint32 pointOrder)
+p_capture_4_vector_points(gint32 imageId, GapAlignCoords *alignCoords, gint32 pointOrder)
 {
-  gint32  activeVectorsId;
-  PixelCoords *coordPtr[4];
+  gint pointCount;
   gint         ii;
+
+  for(ii=0; ii < 4; ii++)
+  {
+      alignCoords->startCoords[ii].px = -1;
+      alignCoords->startCoords[ii].py = -1;
+      alignCoords->startCoords[ii].valid = FALSE;
+
+      alignCoords->currCoords[ii].px = -1;
+      alignCoords->currCoords[ii].py = -1;
+      alignCoords->currCoords[ii].valid = FALSE;
+  }
+  
+  pointCount = p_capture_4_vector_points_from_pathname(imageId, alignCoords, pointOrder, NULL);
+  
+  if (pointOrder == POINT_ORDER_MODE_1234_1234)
+  {
+    pointCount += p_capture_4_vector_points_from_pathname(imageId, alignCoords, pointOrder, 
GAP_EXACT_ALIGNER_TARGET_PATH_NAME);
+  }
+  
+  return (pointCount);
+  
+}  /* end p_capture_4_vector_points */
+
+static gint
+p_capture_4_vector_points_from_pathname(gint32 imageId, GapAlignCoords *alignCoords, gint32 pointOrder, char 
*vectorsName)
+{
+  gint32  activeVectorsId;
+  GapPixelCoords *coordPtr[4];
   gint         countVaildPoints;
+
+  
   
-  if (pointOrder == POINT_ORDER_MODE_31_42)
+  if (pointOrder == POINT_ORDER_MODE_1234_1234)
+  {
+    if (vectorsName == NULL)
+    {
+      /* capture currCoords from the active path */
+      coordPtr[0] = &alignCoords->currCoords[0];
+      coordPtr[1] = &alignCoords->currCoords[1];
+      coordPtr[2] = &alignCoords->currCoords[2];
+      coordPtr[3] = &alignCoords->currCoords[3];
+    }
+    else
+    {
+      /* capture start coords from path with name: GAP_EXACT_ALIGNER_TARGET_PATH_NAME */
+      coordPtr[0] = &alignCoords->startCoords[0];
+      coordPtr[1] = &alignCoords->startCoords[1];
+      coordPtr[2] = &alignCoords->startCoords[2];
+      coordPtr[3] = &alignCoords->startCoords[3];
+    }
+  }
+  else if (pointOrder == POINT_ORDER_MODE_31_42)
   {
-    coordPtr[0] = &alingCoords->startCoords;
-    coordPtr[1] = &alingCoords->startCoords2;
-    coordPtr[2] = &alingCoords->currCoords;
-    coordPtr[3] = &alingCoords->currCoords2;
+    coordPtr[0] = &alignCoords->startCoords[0];
+    coordPtr[1] = &alignCoords->startCoords[1];
+    coordPtr[2] = &alignCoords->currCoords[0];
+    coordPtr[3] = &alignCoords->currCoords[1];
   }
   else
   {
-    coordPtr[0] = &alingCoords->startCoords;
-    coordPtr[2] = &alingCoords->startCoords2;
-    coordPtr[1] = &alingCoords->currCoords;
-    coordPtr[3] = &alingCoords->currCoords2;
+    coordPtr[0] = &alignCoords->startCoords[0];
+    coordPtr[2] = &alignCoords->startCoords[1];
+    coordPtr[1] = &alignCoords->currCoords[0];
+    coordPtr[3] = &alignCoords->currCoords[1];
   }
 
   
   countVaildPoints = 0;
-  for(ii=0; ii < 4; ii++)
+  
+  activeVectorsId = -1;
+  if(vectorsName == NULL)
   {
-    coordPtr[ii]->px = -1;
-    coordPtr[ii]->py = -1;
-    coordPtr[ii]->valid = FALSE;
-
+    activeVectorsId = gimp_image_get_active_vectors(imageId);
+  }
+  else
+  {
+    if (*vectorsName != '\0')
+    {
+      activeVectorsId = gimp_image_get_vectors_by_name(imageId, vectorsName);
+    }
   }
-
-  activeVectorsId = gimp_image_get_active_vectors(imageId);
   if(activeVectorsId >= 0)
   {
     gint num_strokes;
@@ -907,7 +1792,7 @@ p_capture_4_vector_points(gint32 imageId, AlingCoords *alingCoords, gint32 point
  
   return(countVaildPoints);
   
-}  /* end p_capture_4_vector_points */
+}  /* end p_capture_4_vector_points_from_pathname */
 
 
 /* ---------------------------------
@@ -926,10 +1811,1709 @@ p_save_gimprc_gint32_value(const char *gimprc_option_name, gint32 value)
 }  /* p_save_gimprc_gint32_value */
 
 
+/* ------------------------------------
+ * p_generateFineTuningPerCoords        DEPRECATED
+ * ------------------------------------
+ * generate modified variants of perspective coords in the internal array values of perCoords.
+ * (movement with 1/2 pixel shifts in all directions for fine tuning purpose)
+ */
+static void
+p_generateFineTuningPerCoords(GapPerspectiveTransCoords *perCoords)
+{
+#define MAX_FINE_SHIFT_ATTEMPTS 9
 
+  gint ida;
+  gint idd;
+  gdouble dx[MAX_FINE_SHIFT_ATTEMPTS];
+  gdouble dy[MAX_FINE_SHIFT_ATTEMPTS];
+  
 
+  dx[0] = 0;
+  dy[0] = 0;
 
+  dx[1] = -0.25;
+  dy[1] =  0;
+  dx[2] =  0.25;
+  dy[2] =  0;
 
+  dx[3] =  0;
+  dy[3] = -0.25;
+  dx[4] =  0;
+  dy[4] =  0.25;
+
+  dx[5] = -0.25;
+  dy[5] = -0.25;
+  dx[6] =  0.25;
+  dy[6] =  0.25;
+  
+  dx[7] = -0.25;
+  dy[7] =  0.25;
+  dx[8] =  0.25;
+  dy[8] = -0.25;
+  
+  
+  ida = perCoords->numberOfArrayValues;
+  for(idd = 0; idd < MAX_FINE_SHIFT_ATTEMPTS; idd++)
+  {
+    if(ida < MAX_ATTEMPTS_PERSPECTIVE)
+    {
+      perCoords->numberOfArrayValues++;
+      perCoords->ax0[ida] = perCoords->x0 + dx[idd];    
+      perCoords->ay0[ida] = perCoords->y0 + dy[idd];
+      perCoords->ax1[ida] = perCoords->x1 + dx[idd]; 
+      perCoords->ay1[ida] = perCoords->y1 + dy[idd];
+      perCoords->ax2[ida] = perCoords->x2 + dx[idd];
+      perCoords->ay2[ida] = perCoords->y2 + dy[idd];
+      perCoords->ax3[ida] = perCoords->x3 + dx[idd];
+      perCoords->ay3[ida] = perCoords->y3 + dy[idd];
+      
+      ida++;
+    }
+    else
+    {
+      break;
+    }
+  }
+
+
+}    /* end p_generateFineTuningPerCoords */
+
+/* ------------------------------
+ * p_buildTuningShortList
+ * ------------------------------
+ * delivers a list that has at least one element (with offsets 0) and up to 4 elements
+ * depending on distance between tuned ans untuned coordinate.
+ * (the distance is typical <= 4 pixels offset)
+ * the returned short list includes coords on the line between tuned and untuned coordiante.
+ * it has just 1 element in case tuned and untuned coordinate are equal.
+ *
+ * example:
+ *  tunedCoord:   p1x="299"  p1y="113"
+ *  untunedCoord: u1x="297"  u1y="115"
+ * delivers a short list with 3 elements:
+ *  1.)  tuneOffsetX = 0,  tuneOffsetY = 0    ...  299 + (0)  = 299 (p1x)  ; 113 + (0)  = 113 (p1y)
+ *  2.)  tuneOffsetX = -1, tuneOffsetY = 1    ...  299 + (-1) = 298 (p1x)  ; 113 + (1)  = 114 (p1y)
+ *  3.)  tuneOffsetX = -2, tuneOffsetY = 2    ...  299 + (-2) = 297 (p1x)  ; 113 + (2)  = 115 (p1y)
+ */
+GapLocateTuneOffsElem * 
+p_buildTuningShortList(GapPixelCoords *tunedCoord, GapPixelCoords *untunedCoord)
+{
+  GapLocateTuneOffsElem *rootShortList;
+  GapLocateTuneOffsElem *tailShortList;
+  GapLocateTuneOffsElem *elem;
+  
+  gint tuneOffsetX;
+  gint tuneOffsetY;
+  gint maxAbsOffs;
+  gdouble relDiff;
+
+  /* 1.st entry  tuneOffsetX = 0, tuneOffsetY = 0 */
+  tuneOffsetX = 0;
+  tuneOffsetY = 0;
+  relDiff = 0.0;
+  rootShortList = gap_locate_newGapLocateTuneOffsElem(tuneOffsetX, tuneOffsetX, relDiff);
+  tailShortList = rootShortList;
+
+  tuneOffsetX = untunedCoord->px - tunedCoord->px;
+  tuneOffsetY = untunedCoord->py - tunedCoord->py;
+  maxAbsOffs = MAX(abs(tuneOffsetX), abs(tuneOffsetY));
+
+  if (maxAbsOffs > 3)
+  {
+    elem = gap_locate_newGapLocateTuneOffsElem((tuneOffsetX / 4), (tuneOffsetY / 4), relDiff);
+    tailShortList->next = elem;
+    tailShortList = elem;
+  }
+
+  if (maxAbsOffs > 1)
+  {
+    elem = gap_locate_newGapLocateTuneOffsElem((tuneOffsetX / 2), (tuneOffsetY / 2), relDiff);
+    tailShortList->next = elem;
+    tailShortList = elem;
+  }
+  if (maxAbsOffs == 3)
+  {
+    elem = gap_locate_newGapLocateTuneOffsElem((tuneOffsetX / 3), (tuneOffsetY / 3), relDiff);
+    tailShortList->next = elem;
+    tailShortList = elem;
+  }
+
+
+  if (maxAbsOffs > 3)
+  {
+    elem = gap_locate_newGapLocateTuneOffsElem(((tuneOffsetX / 2) + (tuneOffsetX / 4)), ((tuneOffsetY / 2) + 
(tuneOffsetY / 4)), relDiff);
+    tailShortList->next = elem;
+    tailShortList = elem;
+  }
+  
+  if (maxAbsOffs > 0)
+  {
+    elem = gap_locate_newGapLocateTuneOffsElem(tuneOffsetX, tuneOffsetY, relDiff);
+    tailShortList->next = elem;
+    tailShortList = elem;
+  }
+  
+  
+  return (rootShortList);
+  
+}  /* end p_buildTuningShortList */
+
+
+/* ------------------------------
+ * p_mergeTuningShortList
+ * ------------------------------
+ * returns a list that contains all elements of both lists A and B
+ * where list B is appendend at enf of List A and duplicate entries
+ * are set invalid in the appended list part (that was list B)
+ */
+GapLocateTuneOffsElem * 
+p_mergeTuningShortList(GapLocateTuneOffsElem *rootShortListA, GapLocateTuneOffsElem *rootShortListB)
+{
+  GapLocateTuneOffsElem *tailShortListA;
+  GapLocateTuneOffsElem *elemA;
+  GapLocateTuneOffsElem *elemB;
+  
+  if (rootShortListA == NULL)
+  {
+    return (rootShortListB);
+  }
+  if (rootShortListB == NULL)
+  {
+    return (rootShortListA);
+  }
+  
+  tailShortListA = rootShortListA;
+  for(elemA = rootShortListA; elemA != NULL; elemA = elemA->next)
+  {
+    tailShortListA = elemA;
+    if (elemA->valid != TRUE) 
+    { 
+      continue;  /* skip invalid list entries */
+    }
+    for(elemB = rootShortListB; elemB != NULL; elemB = elemB->next)
+    {
+      if (elemB->valid != TRUE) 
+      { 
+        continue;  /* skip invalid list entries */
+      }
+      if((elemA->tuneOffsetX == elemB->tuneOffsetX)
+      && (elemA->tuneOffsetY == elemB->tuneOffsetY))
+      {
+         /* set duplicate entries invalid in list B */
+         elemB->valid = FALSE;
+      }
+    }
+  }
+  
+  tailShortListA->next = rootShortListB;
+  
+  return (rootShortListA);
+  
+}  /* end p_mergeTuningShortList */
+
+/* ------------------------------
+ * p_filterTuningShortList
+ * ------------------------------
+ * returns the same list, where unwanted elements are filtered (by setting them invalid)
+ * lists starting with strong points are reduced to this first element.
+ * weak list with more candiates of similar matching quality are truncated after N elements.
+ * (note that the short List has to be sorted already by macthing quality)
+ */
+static void
+p_filterTuningShortList(GapLocateTuneOffsElem *rootShortListA)
+{
+  GapLocateTuneOffsElem *elemA;
+  gint     validElemCount;
+  gint     remainingValidElemCount;
+  gboolean isStrong;
+  
+  if (rootShortListA == NULL)
+  {
+    return;
+  }
+  
+  isStrong = gap_locate_check_strong_shortlist(rootShortListA
+                                              , 1.02  /* nearlySameFactor */  // TODO find usable value
+                                              , 0.1   /* strongRelDiff    */  // TODO find usable value
+                                              );
+  validElemCount = 0;
+  remainingValidElemCount = 0;
+  for(elemA = rootShortListA; elemA != NULL; elemA = elemA->next)
+  {
+    if (elemA->valid != TRUE) 
+    { 
+      continue;  /* skip invalid list entries */
+    }
+    validElemCount++;
+    if (isStrong)
+    {
+      if (validElemCount > 1)
+      {
+         /* all elements (except the 1st one) are set invalid when the 1st one is a strong matching point */
+         elemA->valid = FALSE;
+      }
+      else
+       {
+         remainingValidElemCount++;
+       }
+    }
+    else
+    {
+       if (validElemCount > 30)   /// TODO can we limit to less variants (for performance reason) or shal we 
do even more fro quality reason ? 
+       {
+         elemA->valid = FALSE;
+       }
+       else
+       {
+         remainingValidElemCount++;
+       }
+    }
+  }
+  
+  if(gap_debug)
+  {
+    printf("  p_filterTuningShortList ");
+    
+    if (isStrong) { printf("STRONG"); }
+    else          { printf("Weak"); }
+    
+    printf("  validElemCount:%d remainingValidElemCount:%d\n"
+        , (int)validElemCount
+        , (int)remainingValidElemCount
+        );
+  }
+  
+  
+}  /* end p_filterTuningShortList */
+
+
+/* ------------------------------
+ * p_pickTuneCoordinateVariants
+ * ------------------------------
+ */
+static void
+p_pickTuneCoordinateVariants(gint32 activeDrawableId, gint32 referenceLayerId, GapAlignCoords *alignCoords, 
GapPerspectiveTransCoords *perCoords
+  , gint32 framePhase, ShortLists *sl)
+{
+  GapLocateTuneOffsElem *shortListP1;
+  GapLocateTuneOffsElem *shortListP2;
+  GapLocateTuneOffsElem *shortListP3;
+  GapLocateTuneOffsElem *shortListP4;
+
+  GapLocateTuneOffsElem *elemP1;
+  GapLocateTuneOffsElem *elemP2;
+  GapLocateTuneOffsElem *elemP3;
+  GapLocateTuneOffsElem *elemP4;
+  gint ida;
+  gint countTruncatedAttempts;
+  
+  gdouble qFactor;
+  
+    /*  TODO find a practical qFactor in the tests..
+     * the qFactor shall eliminate weak matchers (by setting them invalid)
+     * in case there is a clear favorite matching offset available,
+     * but keep more elements (== tune attempts) in case there are more very similar matching candidates.
+     */
+  qFactor = 1.4; // TODO...
+
+  shortListP1 = NULL;
+  shortListP2 = NULL;
+  shortListP3 = NULL;
+  shortListP4 = NULL;
+
+//  if ((alignCoords->untunedCoords[0].valid)
+//  &&  (alignCoords->untunedCoords[1].valid)
+//  &&  (alignCoords->untunedCoords[2].valid)
+//  &&  (alignCoords->untunedCoords[3].valid))
+  if (FALSE)  // Disabled because results not good enough...
+  {
+    if(gap_debug)
+    {
+      printf("p_pickTuneCoordinateVariants based on already TUNED and UNTUNED align coords\n");
+    }
+    /* build short lists for tuning attempts 
+     * based on set of already tuned and untuned coords already provided in the alignCoords.
+     * the built short list includes coords on the line between tuned and untuned coordiante.
+     * (tyically when tracking was recorded with additional color realtion tuning)
+     */
+    shortListP1 = p_buildTuningShortList(&alignCoords->currCoords[0], &alignCoords->untunedCoords[0]);
+    shortListP2 = p_buildTuningShortList(&alignCoords->currCoords[1], &alignCoords->untunedCoords[1]);
+    shortListP3 = p_buildTuningShortList(&alignCoords->currCoords[2], &alignCoords->untunedCoords[2]);
+    shortListP4 = p_buildTuningShortList(&alignCoords->currCoords[3], &alignCoords->untunedCoords[3]);
+  }
+  else
+  {
+    if(gap_debug)
+    {
+      printf("p_pickTuneCoordinateVariants by cheking color relations\n");
+    }
+    /* find potential tuning offsets in near environment +- 4 pixels by cheking color relations
+     * and merge the results into (optional already existing) short lists
+     */
+    //shortListP1 = p_mergeTuningShortList(shortListP1, gap_locate_FindTuneOffsShortList(activeDrawableId, 
referenceLayerId, &alignCoords->startCoords[0], &alignCoords->currCoords[0], qFactor));
+    //shortListP2 = p_mergeTuningShortList(shortListP2, gap_locate_FindTuneOffsShortList(activeDrawableId, 
referenceLayerId, &alignCoords->startCoords[1], &alignCoords->currCoords[1], qFactor));
+    //shortListP3 = p_mergeTuningShortList(shortListP3, gap_locate_FindTuneOffsShortList(activeDrawableId, 
referenceLayerId, &alignCoords->startCoords[2], &alignCoords->currCoords[2], qFactor));
+    //shortListP4 = p_mergeTuningShortList(shortListP4, gap_locate_FindTuneOffsShortList(activeDrawableId, 
referenceLayerId, &alignCoords->startCoords[3], &alignCoords->currCoords[3], qFactor));
+
+    shortListP1 = gap_locate_FindTuneOffsShortList(activeDrawableId, referenceLayerId, 
&alignCoords->startCoords[0], &alignCoords->currCoords[0], qFactor);
+    p_filterTuningShortList(shortListP1);
+
+    shortListP2 = gap_locate_FindTuneOffsShortList(activeDrawableId, referenceLayerId, 
&alignCoords->startCoords[1], &alignCoords->currCoords[1], qFactor);
+    p_filterTuningShortList(shortListP2);
+
+    shortListP3 = gap_locate_FindTuneOffsShortList(activeDrawableId, referenceLayerId, 
&alignCoords->startCoords[2], &alignCoords->currCoords[2], qFactor);
+    p_filterTuningShortList(shortListP3);
+
+    shortListP4 = gap_locate_FindTuneOffsShortList(activeDrawableId, referenceLayerId, 
&alignCoords->startCoords[3], &alignCoords->currCoords[3], qFactor);
+    p_filterTuningShortList(shortListP4);
+  }
+
+  if(gap_debug)
+  {
+      printf("p_pickTuneCoordinateVariants\n");
+  }
+
+
+  countTruncatedAttempts = 0;
+  ida = 0;
+  perCoords->numberOfArrayValues = 0;
+  for(elemP1 = shortListP1; elemP1 != NULL; elemP1 = elemP1->next)
+  {
+    if (elemP1->valid != TRUE) 
+    { 
+      continue;  /* skip low quality list entries */
+    }
+    for(elemP2 = shortListP2; elemP2 != NULL; elemP2 = elemP2->next)
+    {
+      if (elemP2->valid != TRUE) 
+      {
+        continue;  /* skip low quality list entries */
+      }
+      for(elemP3 = shortListP3; elemP3 != NULL; elemP3 = elemP3->next)
+      {
+        if (elemP3->valid != TRUE)
+        {
+          continue;  /* skip low quality list entries */
+        }
+        for(elemP4 = shortListP4; elemP4 != NULL; elemP4 = elemP4->next)
+        {
+          GapAlignCoords alignCoordsDup;
+          GapPerspectiveTransCoords  perCoordsTunedStruct;
+          GapPerspectiveTransCoords *perCoordsTuned;
+          gboolean perCoordsOk;
+
+
+          if (elemP4->valid != TRUE)
+          { 
+            continue;  /* skip low quality list entries */
+          }
+
+          if (ida >= MAX_ATTEMPTS_PERSPECTIVE)
+          {
+             countTruncatedAttempts++;
+             continue;
+         }
+          gap_geo_DuplicateAlignCoords(alignCoords, &alignCoordsDup);
+          perCoordsTuned->transformPrecisionThreshold =  perCoords->transformPrecisionThreshold;
+         perCoordsTuned->transformPrecision =  perCoords->transformPrecision;
+
+          
+          /* apply tune offsets to the align coordinate duplicates */
+          alignCoordsDup.currCoords[0].px = alignCoords->currCoords[0].px + elemP1->tuneOffsetX;
+          alignCoordsDup.currCoords[0].py = alignCoords->currCoords[0].py + elemP1->tuneOffsetY;
+          
+          alignCoordsDup.currCoords[1].px = alignCoords->currCoords[1].px + elemP2->tuneOffsetX;
+          alignCoordsDup.currCoords[1].py = alignCoords->currCoords[1].py + elemP2->tuneOffsetY;
+          
+          alignCoordsDup.currCoords[2].px = alignCoords->currCoords[2].px + elemP3->tuneOffsetX;
+          alignCoordsDup.currCoords[2].py = alignCoords->currCoords[2].py + elemP3->tuneOffsetY;
+          
+          alignCoordsDup.currCoords[3].px = alignCoords->currCoords[3].px + elemP4->tuneOffsetX;
+          alignCoordsDup.currCoords[3].py = alignCoords->currCoords[3].py + elemP4->tuneOffsetY;
+
+          perCoordsTuned = &perCoordsTunedStruct;
+          perCoordsOk = gap_geo_perspective_trans_coords_from_align_coords(activeDrawableId, 
&alignCoordsDup, perCoordsTuned);
+          if (perCoordsOk == TRUE)
+          {
+             // record coordinates for potential iteration attempts
+             perCoords->ax0[ida] = perCoordsTuned->x0; 
+            perCoords->ay0[ida] = perCoordsTuned->y0;
+            perCoords->ax1[ida] = perCoordsTuned->x1;
+            perCoords->ay1[ida] = perCoordsTuned->y1;
+            perCoords->ax2[ida] = perCoordsTuned->x2;
+            perCoords->ay2[ida] = perCoordsTuned->y2;
+            perCoords->ax3[ida] = perCoordsTuned->x3;
+            perCoords->ay3[ida] = perCoordsTuned->y3;
+            
+            if(gap_debug)
+            {
+              gint ii;
+              for(ii=0; ii < 4; ii++)
+              {
+                printf("TuneVariant ida:%d frame:%d alignCoords->currCoords[%d].px:%d .py:%d  tuned.px:%d 
.py:%d  tuneOffsetX:%d tuneOffsetY:%d\n"
+                   ,(int)ida
+                   ,(int)framePhase
+                  ,(int)ii
+                  ,(int)alignCoords->currCoords[ii].px
+                  ,(int)alignCoords->currCoords[ii].py
+                  ,(int)alignCoordsDup.currCoords[ii].px
+                  ,(int)alignCoordsDup.currCoords[ii].py
+                  ,(int)(alignCoordsDup.currCoords[ii].px - alignCoords->currCoords[ii].px)
+                  ,(int)(alignCoordsDup.currCoords[ii].py - alignCoords->currCoords[ii].py)
+                  );
+              }
+              printf("\n");
+              
+            }
+            
+            
+            ida++;
+            perCoords->numberOfArrayValues = ida;
+          }
+          
+        }
+      }
+    }
+  }
+
+  sl->shortListP1 = shortListP1;
+  sl->shortListP2 = shortListP2;
+  sl->shortListP3 = shortListP3;
+  sl->shortListP4 = shortListP4;
+
+  
+  if(gap_debug)
+  {
+    printf("p_pickTuneCoordinateVariants Done, storedAttempts:%d countTruncatedAttempts:%d\n\n"
+      ,(int)perCoords->numberOfArrayValues
+      ,(int)countTruncatedAttempts
+      );
+  }
+
+}  /* end p_pickTuneCoordinateVariants */
+
+/* ------------------------------
+ * p_renderPickedPathVariant
+ * ------------------------------
+ */
+static void
+p_renderPickedPathVariant(gint32 activeDrawableId, gint32 pickedArrayIdx, GapAlignCoords *alignCoords, 
gint32 framePhase, ShortLists *sl)
+{
+  GapLocateTuneOffsElem *shortListP1;
+  GapLocateTuneOffsElem *shortListP2;
+  GapLocateTuneOffsElem *shortListP3;
+  GapLocateTuneOffsElem *shortListP4;
+
+  GapLocateTuneOffsElem *elemP1;
+  GapLocateTuneOffsElem *elemP2;
+  GapLocateTuneOffsElem *elemP3;
+  GapLocateTuneOffsElem *elemP4;
+  gint ida;
+
+
+  shortListP1 = sl->shortListP1;
+  shortListP2 = sl->shortListP2;
+  shortListP3 = sl->shortListP3;
+  shortListP4 = sl->shortListP4;
+
+
+  ida = 0;
+  for(elemP1 = shortListP1; elemP1 != NULL; elemP1 = elemP1->next)
+  {
+    if (elemP1->valid != TRUE) 
+    { 
+      continue;  /* skip low quality list entries */
+    }
+    for(elemP2 = shortListP2; elemP2 != NULL; elemP2 = elemP2->next)
+    {
+      if (elemP2->valid != TRUE) 
+      {
+        continue;  /* skip low quality list entries */
+      }
+      for(elemP3 = shortListP3; elemP3 != NULL; elemP3 = elemP3->next)
+      {
+        if (elemP3->valid != TRUE)
+        {
+          continue;  /* skip low quality list entries */
+        }
+        for(elemP4 = shortListP4; elemP4 != NULL; elemP4 = elemP4->next)
+        {
+          GapAlignCoords alignCoordsDup;
+
+
+          if (elemP4->valid != TRUE)
+          { 
+            continue;  /* skip low quality list entries */
+          }
+
+          if (ida >= MAX_ATTEMPTS_PERSPECTIVE)
+          {
+             /* Truncated Attempts */
+             return;
+         }
+         if (pickedArrayIdx == ida)
+         {
+            gint32 imageId;
+
+            gap_geo_DuplicateAlignCoords(alignCoords, &alignCoordsDup);
+            
+            /* apply tune offsets to the align coordinate duplicates */
+            alignCoordsDup.currCoords[0].px = alignCoords->currCoords[0].px + elemP1->tuneOffsetX;
+            alignCoordsDup.currCoords[0].py = alignCoords->currCoords[0].py + elemP1->tuneOffsetY;
+          
+            alignCoordsDup.currCoords[1].px = alignCoords->currCoords[1].px + elemP2->tuneOffsetX;
+            alignCoordsDup.currCoords[1].py = alignCoords->currCoords[1].py + elemP2->tuneOffsetY;
+          
+            alignCoordsDup.currCoords[2].px = alignCoords->currCoords[2].px + elemP3->tuneOffsetX;
+            alignCoordsDup.currCoords[2].py = alignCoords->currCoords[2].py + elemP3->tuneOffsetY;
+          
+            alignCoordsDup.currCoords[3].px = alignCoords->currCoords[3].px + elemP4->tuneOffsetX;
+            alignCoordsDup.currCoords[3].py = alignCoords->currCoords[3].py + elemP4->tuneOffsetY;
+            
+                
+           imageId = gimp_item_get_image(activeDrawableId);
+           p_create_or_replace_path_vectors(imageId
+                                           , &alignCoordsDup.currCoords[0], 4
+                                           , "PICKED" /* vectorsName */
+                                           , TRUE /* setVisible */ 
+                                            );
+
+           if(gap_debug)
+           {
+             gint ii;
+             for(ii=0; ii < 4; ii++)
+             {
+               printf("Render PATH TuneVariant ida:%d frame:%d alignCoords->currCoords[%d].px:%d .py:%d  
tuned.px:%d .py:%d  tuneOffsetX:%d tuneOffsetY:%d\n"
+                  ,(int)ida
+                  ,(int)framePhase
+                 ,(int)ii
+                 ,(int)alignCoords->currCoords[ii].px
+                 ,(int)alignCoords->currCoords[ii].py
+                 ,(int)alignCoordsDup.currCoords[ii].px
+                 ,(int)alignCoordsDup.currCoords[ii].py
+                 ,(int)(alignCoordsDup.currCoords[ii].px - alignCoords->currCoords[ii].px)
+                 ,(int)(alignCoordsDup.currCoords[ii].py - alignCoords->currCoords[ii].py)
+                 );
+             }
+             printf("\n");
+            }
+            
+            return;
+          }
+    
+          ida++;
+          
+        }
+      }
+    }
+  }
+
+}  /* end p_renderPickedPathVariant */
+
+
+
+/* ---------------------------
+ * p_freeShortLists
+ * ---------------------------
+ */
+static void
+p_freeShortLists(ShortLists *sl)
+{
+  if(sl->shortListP1 != NULL)
+  {
+   gap_locate_freeTuneOffsList(sl->shortListP1);
+   sl->shortListP1 = NULL;
+  }
+  if(sl->shortListP2 != NULL)
+  {
+   gap_locate_freeTuneOffsList(sl->shortListP2);
+   sl->shortListP2 = NULL;
+  }
+  if(sl->shortListP3 != NULL)
+  {
+   gap_locate_freeTuneOffsList(sl->shortListP3);
+   sl->shortListP3 = NULL;
+  }
+  if(sl->shortListP4 != NULL)
+  {
+   gap_locate_freeTuneOffsList(sl->shortListP4);
+   sl->shortListP4 = NULL;
+  }
+
+}  /* end p_freeShortLists */
+
+
+/* --------------------------------------------
+ * p_fineTuneProbePerspectiveTransformation
+ * --------------------------------------------
+ * This procedure creates a temporary image with a copy of a referenceLayer and performs probe rendering
+ * on copies of the activeDrawable to select the best matching transformation.
+ * Therefore areas of the background (marked as opaque via layermask) 
+ * are compared referenceLayer versus probe rendered transformed layer 
+ * with a set of similar variants of persepective transformation coorinates.
+ *
+ *
+ * This processing and resource intensive fine tuning shall reduce unwanted jitter effects
+ * with small amplitudes near +- 1 pixel.
+ *
+ * Restrictions: 
+ *   activeDrawableId must be a layer at full image size
+ *   activeDrawableId and referenceLayerId must have same size and bpp
+ */
+static void
+p_fineTuneProbePerspectiveTransformation(gint32 activeDrawableId, gint32 referenceLayerId
+  ,GapAlignCoords *alignCoords, GapPerspectiveTransCoords *perCoords, gint32 framePhase)
+{
+  gdouble bestAvgColordiff;
+  gint    pickedArrayIdx;
+  gint    ida;
+  gint    idaMax;
+  gint    l_src_offset_x, l_src_offset_y;    /* layeroffsets as they were in src_image */
+  gint32  tmpImageId;
+  gint32  requiredPixelCount;
+  gint32  comparedPixelCount;
+  gint32  tmpBgLayerId;
+  ShortLists shortLists;
+  ShortLists *sl;
+  
+  gdouble width;
+  gdouble height;
+
+  width  = (gdouble)gimp_image_width(gimp_item_get_image(activeDrawableId));
+  height = (gdouble)gimp_image_height(gimp_item_get_image(activeDrawableId));
+
+  if(TRUE != gap_geo_pick_corners_from_align_coords(alignCoords, width, height))
+  {
+    printf("p_fineTuneProbePerspectiveTransformation FAILED because 4 vaild coords could not be found\n");
+    return;  /* stop in case picking coords near the corners failed */
+  }
+
+  /* calculate tuning variants based on color relation checks and store the
+   * resulting perspective transformation varinats in the perCoords array 
+   */
+  sl = &shortLists;
+  sl->shortListP1 = NULL;
+  sl->shortListP2 = NULL;
+  sl->shortListP3 = NULL;
+  sl->shortListP4 = NULL;
+  
+  p_pickTuneCoordinateVariants(activeDrawableId, referenceLayerId, alignCoords, perCoords, framePhase, sl);
+
+
+  if(gap_debug)
+  {
+    guchar *fname;
+    
+    fname = gimp_image_get_filename(gimp_item_get_image(activeDrawableId));
+    printf("p_fineTuneProbePerspectiveTransformation START numberOfArrayValues: %d  activeDrawableId:%d  
referenceLayerId:%d width:%f height:%f imagename:%s\n"
+      , (int)perCoords->numberOfArrayValues
+      , (int)activeDrawableId
+      , (int)referenceLayerId
+      , width
+      , height
+      , fname
+      );
+    if(fname != NULL)
+    {
+      g_free(fname);
+    }
+  }
+
+  if(activeDrawableId == referenceLayerId)
+  {
+    p_freeShortLists(sl);
+    /* do nothing in case active and reference are the same layer
+     */
+    return;
+  }
+
+  bestAvgColordiff = 1.1; /* init with worst possible value + 0.1 */
+  pickedArrayIdx = -1;    /* -1 indicates nothing picked */
+  
+  /* create tmpImageId */
+  tmpImageId = gap_image_new_of_samesize(gimp_item_get_image(activeDrawableId));
+  
+  /* copy referenceLayerId as tmpBgLayerId Layer to tmpImageId 
+   * TODO: check if layermask is included in the copy ...
+   */
+  tmpBgLayerId = gap_layer_copy_to_dest_image(tmpImageId
+                                             , referenceLayerId   /* l_src_layer_id */
+                                             , 100.0   /* Opacity */
+                                             ,0        /* NORMAL */
+                                             ,&l_src_offset_x
+                                             ,&l_src_offset_y
+                                             );
+  gimp_image_insert_layer(tmpImageId
+                         , tmpBgLayerId
+                         , 0
+                         , 0              /* top of layerstack */
+                         );
+  gimp_layer_resize_to_image_size(tmpBgLayerId);
+
+    
+  
+  /* apply layermask on tmpBgLayerId (following opacity checks done just on the alphe channel) */
+  gimp_layer_remove_mask (tmpBgLayerId, GIMP_MASK_APPLY);
+
+
+  /* compare refDrawable against itself
+   * to count the number of opaque pixels in it
+   */
+  gap_locateColordiffOpaquePixels(tmpBgLayerId      /* refDrawable */
+                                 , tmpBgLayerId     /*  targetDrawableId */
+                                 , 1                /* gint32  requiredPixelCount */
+                                 , &comparedPixelCount    /* gint32 *comparedPixelCount */
+                                 );
+  /* 50 percent of the opaque pixels should be involved in further compare steps 
+   * (but do not require more than 500 pixels)
+   */
+  requiredPixelCount = MIN(500, (comparedPixelCount * 50) / 100);
+  if(gap_debug)
+  {
+     printf("FINE TUNE tmpBgLayerId:%d  number of opaque pixels found:%d requiredPixelCount:%d (50 percent 
or 500)\n"
+           ,(int)tmpBgLayerId
+           ,(int)comparedPixelCount
+           ,(int)requiredPixelCount
+           );
+  }
+
+  if (comparedPixelCount < 10)
+  {
+    /* delete the temporary image and skip fine tune attempts (does not make sense
+     * when having not enough opaque refernce pixels
+     */
+    gap_image_delete_immediate(tmpImageId);
+    p_freeShortLists(sl);
+    return;
+  }
+
+  if(gap_debug)
+  {
+    /* import the xml align coordinates as SRC and TARGET path vectors
+     * (this is not relevant for processing, but is useful for analyse purpose)
+     */
+    p_create_or_replace_path_vectors(tmpImageId
+                                       , &alignCoords->startCoords[0], 4
+                                       , "Ref" /* vectorsName */
+                                       , TRUE /* setVisible */ 
+                                       );
+    p_create_or_replace_path_vectors(tmpImageId
+                                       , &alignCoords->currCoords[0], 4
+                                       , "Trk_orig" /* vectorsName */
+                                       , FALSE /* setVisible */ 
+                                       );
+    
+    /* when debugging add_display and keep the tmpImageId for analyse purpose */
+    //gimp_display_new(tmpImageId);
+  }
+
+
+  /* probe perspective transformation variants with potential settings
+   * and pick the one that delivers best match
+   * (with the lowest colordiff compared to the reference layer at its opaque areas)
+   */
+  idaMax = perCoords->numberOfArrayValues; // MIN(40, MAX_ITER_ATTEMPTS_PERSPECTIVE);
+  for(ida = 0; ida < idaMax; ida++)
+  {
+     gint32 attemptLayerId;
+     gint32 transformedDrawableId;
+     gint   countDisplacementZero;
+     gint   countReloacateErrors;
+     gint   idl;
+     gdouble avgColordiff;
+     GapPixelCoords reTracedCoordsArray[4];
+          
+     /* copy activeDrawableId to tmpImageId */
+     attemptLayerId = gap_layer_copy_to_dest_image(tmpImageId
+                                             , activeDrawableId   /* l_src_layer_id */
+                                             , 100.0   /* Opacity */
+                                             ,0        /* NORMAL */
+                                             ,&l_src_offset_x
+                                             ,&l_src_offset_y
+                                             );
+     gimp_image_insert_layer(tmpImageId
+                             , attemptLayerId
+                             , 0
+                             , 0              /* top of layerstack */
+                             );
+
+     gimp_layer_resize_to_image_size(attemptLayerId);
+     
+     gimp_context_set_defaults();
+     gimp_context_set_transform_resize(GIMP_TRANSFORM_RESIZE_ADJUST);   /* do NOT clip */                    
             
+     gimp_context_set_transform_direction(GIMP_TRANSFORM_FORWARD);                                 
+     transformedDrawableId = gimp_item_transform_perspective(attemptLayerId
+                                      , perCoords->ax0[ida], perCoords->ay0[ida]
+                                      , perCoords->ax1[ida], perCoords->ay1[ida]
+                                      , perCoords->ax2[ida], perCoords->ay2[ida]
+                                      , perCoords->ax3[ida], perCoords->ay3[ida]
+                                      );
+
+    if(! gimp_drawable_has_alpha(transformedDrawableId))
+    {
+       /* have to add alpha channel */
+       gimp_layer_add_alpha(transformedDrawableId);
+    }
+     if(gap_debug)
+     {
+       printf("IDA:%d transformedDrawableId:%d attemptLayerId:%d\n"
+         , (int)ida
+         , (int)transformedDrawableId
+         , (int)attemptLayerId
+         );
+     }
+     
+     gimp_layer_resize_to_image_size(transformedDrawableId);
+     
+     /* compare all opaque pixels and calculate 
+      *  average colordifference tmpBgLayerId versus transformedDrawableId
+      */
+     avgColordiff = gap_locateColordiffOpaquePixels(tmpBgLayerId            /* refDrawable */
+                                                   , transformedDrawableId  /*  targetDrawableId */
+                                                   , requiredPixelCount     /* gint32  requiredPixelCount */
+                                                   , &comparedPixelCount    /* gint32 *comparedPixelCount */
+                                                   );
+     if(avgColordiff < bestAvgColordiff)
+     {
+       /* pick the best matching transformation attempt
+        * for the same transformation to the activeDrawableId.
+        * best matching has smallest color diff compared with referenceLayerId 
+        */
+       pickedArrayIdx = ida;
+       bestAvgColordiff = avgColordiff;
+     }
+     
+
+     if(gap_debug)
+     {
+         printf("PROBE ida:%d frame:%d drawableId:%d  comparedPixelCount:%d avgColordiff:%0.12f 
bestAvgColordiff[%d]:%0.12f\n"
+           ,(int)ida
+           ,(int)framePhase
+           ,(int)transformedDrawableId
+           ,(int)comparedPixelCount
+           ,(double)avgColordiff
+           ,(int)pickedArrayIdx
+           ,(double)bestAvgColordiff
+           );
+     }
+     
+     if (TRUE)  // DISABLED the re-loaction checks ...
+     {
+       continue;
+     }
+     
+     
+     /* (re)locate 4 target points on base of the alredy transformed layer
+      * ideally all 4 target coordinates should be equal to the 4 corresponding reference coordinates.
+      *
+      */
+     countDisplacementZero = 0;
+     countReloacateErrors = 0;
+     for(idl=0; idl < 4; idl++)
+     {
+       gint32  refX;
+       gint32  refY;
+       gint32  targetX;
+       gint32  targetY;
+       gdouble colordiffLocate;
+       gint32  displacementX;
+       gint32  displacementY;
+       GapPixelCoords *targetCoords;
+  
+       refX = alignCoords->refPtr[idl]->px;
+       refY = alignCoords->refPtr[idl]->py;
+       colordiffLocate = gap_locateAreaWithinRadiusWithOffset (tmpBgLayerId  /* reference Layer */
+                                    , refX
+                                    , refY
+                                    , 15   // valPtr->refShapeRadius
+                                    , transformedDrawableId                  /* target Layer */
+                                    , 8    //  valPtr->targetMoveRadius
+                                    , &targetX
+                                    , &targetY
+                                    , 0  // locateOffsetX  // use locateOffset 0 to start locating exact at 
ref coordinate
+                                    , 0  // locateOffsetY
+                                    );
+         
+       displacementX = refX - targetX;
+       displacementY = refY - targetY;
+       
+       targetCoords = &reTracedCoordsArray[idl];
+       targetCoords->px  = (gdouble)targetX;
+       targetCoords->py  = (gdouble)targetY;
+         
+
+       if(gap_debug)
+       {
+         printf("  idl:%d displacementX:%d  displacementY:%d colordiffLocate:%0.12f refX:%d refY:%d  
targetX:%d targetY:%d\n"
+           ,(int)idl
+           ,(int)displacementX
+           ,(int)displacementY
+           ,(double)colordiffLocate
+           ,(int)refX
+           ,(int)refY
+           ,(int)targetX
+           ,(int)targetY
+           );
+       }
+
+
+     }
+     
+     /* PAUSE dialog is just for testing (when gap_debug is set TRUE) for test purpose */
+     if(gap_debug)
+     {
+        gchar *msg_txt;
+        gboolean l_rc;
+
+        /* import the xml re-traced coordinates as path vectors
+         * (this is not relevant for processing, but is useful for analyse purpose)
+         */
+        p_create_or_replace_path_vectors(tmpImageId
+                                       , &reTracedCoordsArray[0], 4
+                                       , GAP_EXACT_ALIGNER_RE_TRACED_PATH_NAME /* vectorsName */
+                                       , TRUE /* setVisible */ 
+                                       );
+                                       
+        /* display dialog with "OK" Button to pause processing
+         * (the tester can analyse the tempory work image while paused.)
+         */
+        msg_txt = g_strdup_printf(_("Fine Tuning step %d done.\n"
+                            ""
+                            "press OK for next iteration step\n")
+                            ,(int)ida
+                         );
+        l_rc = gap_arr_confirm_dialog(msg_txt
+                                 , _("Detail Align FineTuning PAUSED")   /* title_txt */
+                                 , _("Confirm to continue")   /* frame_txt */
+                                 );
+        g_free(msg_txt);
+        if (l_rc != TRUE)
+        {
+           printf("ESCAPE fineTune iteration at attempt IDA:%d pickedArrayIdx:%d because CANCELLED by 
user.\n"
+                ,(int)ida
+                ,(int)pickedArrayIdx
+                );
+           break;
+        }
+     
+     }
+     
+     if (countDisplacementZero >= 4)
+     {
+       if(gap_debug)
+       {
+         printf("ESCAPE fineTune iteration at attempt IDA:%d pickedArrayIdx:%d because transformed layer 
exactly matches at 4 ref points\n"
+                ,(int)ida
+                ,(int)pickedArrayIdx
+                );
+       }
+       break;
+     }
+
+     if (countReloacateErrors >= 4)
+     {
+       if(gap_debug)
+       {
+         printf("ESCAPE fineTune iteration at attempt IDA:%d pickedArrayIdx:%d because locating of tracking 
coordinates FAILED\n"
+                ,(int)ida
+                ,(int)pickedArrayIdx
+                );
+       }
+       break;
+     }     
+     
+     
+  }
+  
+  /////////////////////////////////////////////////
+  
+
+ 
+  if (pickedArrayIdx >= 0)
+  {
+     p_renderPickedPathVariant(activeDrawableId, pickedArrayIdx, alignCoords, framePhase, sl);
+
+     perCoords->x0 = perCoords->ax0[pickedArrayIdx];    
+     perCoords->y0 = perCoords->ay0[pickedArrayIdx];
+     perCoords->x1 = perCoords->ax1[pickedArrayIdx]; 
+     perCoords->y1 = perCoords->ay1[pickedArrayIdx];
+     perCoords->x2 = perCoords->ax2[pickedArrayIdx];
+     perCoords->y2 = perCoords->ay2[pickedArrayIdx];
+     perCoords->x3 = perCoords->ax3[pickedArrayIdx];
+     perCoords->y3 = perCoords->ay3[pickedArrayIdx];
+     
+     if(gap_debug)
+     {
+       printf("\nfineTune RESULT pickedArrayIdx:%d\n\n"
+             ,(int)pickedArrayIdx
+             );
+     }
+  }
+
+  
+  if(gap_debug != TRUE)
+  {
+    /* remove the temporary image (that was created just for probe purpose) */
+    gap_image_delete_immediate(tmpImageId);
+  }
+  
+  p_freeShortLists(sl);
+
+
+}  /* end p_fineTuneProbePerspectiveTransformation */
+
+
+/* --------------------------------------------
+ * p_fineTuneProbePerspectiveTransformationOld2   DEPRECATED 
+ * --------------------------------------------
+ * Old implementation. TODO remove this after successful test of the new variante !
+ * 222222222222222222222222222222222222222222222222222222222222222222222222222222222222222
+ * This procedure creates a temporary image with a copy of a referenceLayer and performs probe rendering
+ * on copies of the activeDrawable to select the best matching transformation.
+ * Therefore areas of the background (marked as opaque via layermask) 
+ * are compared referenceLayer versus probe rendered transformed layer 
+ * with a set of similar variants of persepective transformation coorinates.
+ *
+ *
+ * This processing and resource intensive fine tuning shall reduce unwanted jitter effects
+ * with small amplitudes near +- 1 pixel.
+ *
+ * Restrictions: 
+ *   activeDrawableId must be a layer at full image size
+ *   activeDrawableId and referenceLayerId must have same size and bpp
+ */
+static void
+p_fineTuneProbePerspectiveTransformationOld2(gint32 activeDrawableId, gint32 referenceLayerId
+  ,GapAlignCoords *alignCoords, GapPerspectiveTransCoords *perCoords)
+{
+  gdouble bestAvgColordiff;
+  gint    pickedArrayIdx;
+  gint    ida;
+  gint    idaMax;
+  gint    l_src_offset_x, l_src_offset_y;    /* layeroffsets as they were in src_image */
+  gint32  tmpImageId;
+  gint32  requiredPixelCount;
+  gint32  comparedPixelCount;
+  gint32  tmpBgLayerId;
+  
+  gdouble width;
+  gdouble height;
+  gdouble dx[4];
+  gdouble dy[4];
+  gdouble ddx;    /* horizontal itaration stepsize  1920 / 32000 = 0,06 pixels */
+  gdouble ddy;    /* vertical itaration stepsize  1080 / 32000 = 0,03375 pixels */
+
+  width  = (gdouble)gimp_image_width(gimp_item_get_image(activeDrawableId));
+  height = (gdouble)gimp_image_height(gimp_item_get_image(activeDrawableId));
+
+  ddx = width / 32000.0;
+  ddy = height / 32000.0;
+  
+  if(gap_debug)
+  {
+    printf("p_fineTuneProbePerspectiveTransformationOld2 START numberOfArrayValues: %d  activeDrawableId:%d  
referenceLayerId:%d width:%f height:%f ddx:%f ddy:%f\n"
+      , (int)perCoords->numberOfArrayValues
+      , (int)activeDrawableId
+      , (int)referenceLayerId
+      , width
+      , height
+      , ddx
+      , ddy
+      );
+  }
+
+  if(TRUE != gap_geo_pick_corners_from_align_coords(alignCoords, width, height))
+  {
+    printf("p_fineTuneProbePerspectiveTransformationOld2 FAILED because 4 vaild coords could not be 
found\n");
+    return;  /* stop in case picking coords near the corners failed */
+  }
+
+  /* set all initial displacements to 0.0 (the first iteration starts with unmodified perspective settings) 
*/
+  dx[0] = 0.0; 
+  dy[0] = 0.0;
+  dx[1] = 0.0;
+  dy[1] = 0.0;
+  dx[2] = 0.0;
+  dy[2] = 0.0;
+  dx[3] = 0.0;
+  dy[3] = 0.0;
+
+  if(activeDrawableId == referenceLayerId)
+  {
+    /* do nothing in case active and reference are the same layer
+     */
+    return;
+  }
+
+  bestAvgColordiff = 1.1; /* init with worst possible value + 0.1 */
+  pickedArrayIdx = -1;    /* -1 indicates nothing picked */
+  
+  /* create tmpImageId */
+  tmpImageId = gap_image_new_of_samesize(gimp_item_get_image(activeDrawableId));
+  
+  /* copy referenceLayerId as tmpBgLayerId Layer to tmpImageId 
+   * TODO: check if layermask is included in the copy ...
+   */
+  tmpBgLayerId = gap_layer_copy_to_dest_image(tmpImageId
+                                             , referenceLayerId   /* l_src_layer_id */
+                                             , 100.0   /* Opacity */
+                                             ,0        /* NORMAL */
+                                             ,&l_src_offset_x
+                                             ,&l_src_offset_y
+                                             );
+  gimp_image_insert_layer(tmpImageId
+                         , tmpBgLayerId
+                         , 0
+                         , 0              /* top of layerstack */
+                         );
+  gimp_layer_resize_to_image_size(tmpBgLayerId);
+
+    
+  
+  /* apply layermask on tmpBgLayerId (following opacity checks done just on the alphe channel) */
+  gimp_layer_remove_mask (tmpBgLayerId, GIMP_MASK_APPLY);
+
+
+  /* compare refDrawable against itself
+   * to count the number of opaque pixels in it
+   */
+  gap_locateColordiffOpaquePixels(tmpBgLayerId      /* refDrawable */
+                                 , tmpBgLayerId     /*  targetDrawableId */
+                                 , 1                /* gint32  requiredPixelCount */
+                                 , &comparedPixelCount    /* gint32 *comparedPixelCount */
+                                 );
+  /* 50 percent of the opaque pixels should be involved in further compare steps 
+   * (but do not require more than 500 pixels)
+   */
+  requiredPixelCount = MIN(500, (comparedPixelCount * 50) / 100);
+  if(gap_debug)
+  {
+     printf("FINE TUNE tmpBgLayerId:%d  number of opaque pixels found:%d requiredPixelCount:%d (50 percent 
or 500)\n"
+           ,(int)tmpBgLayerId
+           ,(int)comparedPixelCount
+           ,(int)requiredPixelCount
+           );
+  }
+
+  if (comparedPixelCount < 10)
+  {
+    /* delete the temporary image and skip fine tune attempts (does not make sense
+     * when having not enough opaque refernce pixels
+     */
+    gap_image_delete_immediate(tmpImageId);
+    return;
+  }
+
+  if(gap_debug)
+  {
+    /* import the xml align coordinates as SRC and TARGET path vectors
+     * (this is not relevant for processing, but is useful for analyse purpose)
+     */
+    p_create_or_replace_path_vectors(tmpImageId
+                                       , &alignCoords->startCoords[0], 4
+                                       , "Ref" /* vectorsName */
+                                       , TRUE /* setVisible */ 
+                                       );
+    p_create_or_replace_path_vectors(tmpImageId
+                                       , &alignCoords->currCoords[0], 4
+                                       , "Trk_orig" /* vectorsName */
+                                       , FALSE /* setVisible */ 
+                                       );
+    
+    /* when debugging add_display and keep the tmpImageId for analyse purpose */
+    //gimp_display_new(tmpImageId);
+  }
+
+
+  /* probe perspective transformation variants with potential settings
+   * and pick the one that delivers best match
+   * (with the lowest colordiff compared to the reference layer at its opaque areas)
+   */
+  idaMax = MIN(40, MAX_ITER_ATTEMPTS_PERSPECTIVE);
+  for(ida = 0; ida < idaMax; ida++)
+  {
+     gint32 attemptLayerId;
+     gint32 transformedDrawableId;
+     gint   countDisplacementZero;
+     gint   countReloacateErrors;
+     gint   idl;
+     gdouble avgColordiff;
+     GapPixelCoords reTracedCoordsArray[4];
+          
+
+     
+     perCoords->ax0[ida] = perCoords->x0 + dx[0]; 
+     perCoords->ay0[ida] = perCoords->y0 + dy[0];
+     perCoords->ax1[ida] = perCoords->x1 + dx[1];
+     perCoords->ay1[ida] = perCoords->y1 + dy[1];
+     perCoords->ax2[ida] = perCoords->x2 + dx[2];
+     perCoords->ay2[ida] = perCoords->y2 + dy[2];
+     perCoords->ax3[ida] = perCoords->x3 + dx[3];
+     perCoords->ay3[ida] = perCoords->y3 + dy[3];
+    
+     /* copy activeDrawableId to tmpImageId */
+     attemptLayerId = gap_layer_copy_to_dest_image(tmpImageId
+                                             , activeDrawableId   /* l_src_layer_id */
+                                             , 100.0   /* Opacity */
+                                             ,0        /* NORMAL */
+                                             ,&l_src_offset_x
+                                             ,&l_src_offset_y
+                                             );
+     gimp_image_insert_layer(tmpImageId
+                             , attemptLayerId
+                             , 0
+                             , 0              /* top of layerstack */
+                             );
+
+     gimp_layer_resize_to_image_size(attemptLayerId);
+     
+     gimp_context_set_defaults();
+     gimp_context_set_transform_resize(GIMP_TRANSFORM_RESIZE_ADJUST);   /* do NOT clip */                    
             
+     gimp_context_set_transform_direction(GIMP_TRANSFORM_FORWARD);                                 
+     transformedDrawableId = gimp_item_transform_perspective(attemptLayerId
+                                      , perCoords->ax0[ida], perCoords->ay0[ida]
+                                      , perCoords->ax1[ida], perCoords->ay1[ida]
+                                      , perCoords->ax2[ida], perCoords->ay2[ida]
+                                      , perCoords->ax3[ida], perCoords->ay3[ida]
+                                      );
+     if(gap_debug)
+     {
+       printf("IDA:%d transformedDrawableId:%d attemptLayerId:%d\n"
+         , (int)ida
+         , (int)transformedDrawableId
+         , (int)attemptLayerId
+         );
+     }
+     
+     gimp_layer_resize_to_image_size(transformedDrawableId);
+     
+     /* compare all opaque pixels and calculate 
+      *  average colordifference tmpBgLayerId versus transformedDrawableId
+      */
+     avgColordiff = gap_locateColordiffOpaquePixels(tmpBgLayerId            /* refDrawable */
+                                                   , transformedDrawableId  /*  targetDrawableId */
+                                                   , requiredPixelCount     /* gint32  requiredPixelCount */
+                                                   , &comparedPixelCount    /* gint32 *comparedPixelCount */
+                                                   );
+     if(avgColordiff < bestAvgColordiff)
+     {
+       /* pick the best matching transformation attempt
+        * for the same transformation to the activeDrawableId.
+        * best matching has smallest color diff compared with referenceLayerId 
+        */
+       pickedArrayIdx = ida;
+       bestAvgColordiff = avgColordiff;
+     }
+     
+
+     if(gap_debug)
+     {
+         printf("PROBE ida:%d drawableId:%d  comparedPixelCount:%d avgColordiff:%0.12f 
bestAvgColordiff[%d]:%0.12f\n"
+           ,(int)ida
+           ,(int)transformedDrawableId
+           ,(int)comparedPixelCount
+           ,(double)avgColordiff
+           ,(int)pickedArrayIdx
+           ,(double)bestAvgColordiff
+           );
+     }
+     
+     /* (re)locate 4 target points on base of the alredy transformed layer
+      * ideally all 4 target coordinates should be equal to the 4 corresponding reference coordinates.
+      * in this ideal case no further iteration is needed.
+      * otherwise calculate dx[i] dy[i] offests based on the detected remaining displacement.
+      */
+     countDisplacementZero = 0;
+     countReloacateErrors = 0;
+     for(idl=0; idl < 4; idl++)
+     {
+       gint32  refX;
+       gint32  refY;
+       gint32  targetX;
+       gint32  targetY;
+       gdouble colordiffLocate;
+       gint32  displacementX;
+       gint32  displacementY;
+       GapPixelCoords *targetCoords;
+  
+       refX = alignCoords->refPtr[idl]->px;
+       refY = alignCoords->refPtr[idl]->py;
+       colordiffLocate = gap_locateAreaWithinRadiusWithOffset (tmpBgLayerId  /* reference Layer */
+                                    , refX
+                                    , refY
+                                    , 15   // valPtr->refShapeRadius
+                                    , transformedDrawableId                  /* target Layer */
+                                    , 8    //  valPtr->targetMoveRadius
+                                    , &targetX
+                                    , &targetY
+                                    , 0  // locateOffsetX  // use locateOffset 0 to start locating exact at 
ref coordinate
+                                    , 0  // locateOffsetY
+                                    );
+         
+       displacementX = refX - targetX;
+       displacementY = refY - targetY;
+       
+       targetCoords = &reTracedCoordsArray[idl];
+       targetCoords->px  = (gdouble)targetX;
+       targetCoords->py  = (gdouble)targetY;
+         
+
+       if(gap_debug)
+       {
+         printf("  idl:%d displacementX:%d  displacementY:%d colordiffLocate:%0.12f refX:%d refY:%d  
targetX:%d targetY:%d\n"
+           ,(int)idl
+           ,(int)displacementX
+           ,(int)displacementY
+           ,(double)colordiffLocate
+           ,(int)refX
+           ,(int)refY
+           ,(int)targetX
+           ,(int)targetY
+           );
+       }
+
+       if (colordiffLocate < 0.5)
+       {
+         if ((displacementX == 0) && (displacementY == 0))
+         {
+           countDisplacementZero++;
+         }
+         
+         if (displacementX > 0)       { dx[idl] += ddx; }
+         else if (displacementX < 0)  { dx[idl] -= ddx; }
+       
+         if (displacementY > 0)       { dy[idl] += ddy; }
+         else if (displacementY < 0)  { dy[idl] -= ddy; }
+       
+       }
+       else
+       {
+         countReloacateErrors++;
+         printf("WARNING could not re-locate this point refX:%d refY:%d  colordiffLocate:%0.12f >= 0.5 \n"
+           , (int) refX
+           , (int) refY
+           , (colordiffLocate)
+           );
+       }
+     }
+     
+     /* PAUSE dialog is just for testing (when gap_debug is set TRUE) for test purpose */
+     if(gap_debug)
+     {
+        gchar *msg_txt;
+        gboolean l_rc;
+
+        /* import the xml re-traced coordinates as path vectors
+         * (this is not relevant for processing, but is useful for analyse purpose)
+         */
+        p_create_or_replace_path_vectors(tmpImageId
+                                       , &reTracedCoordsArray[0], 4
+                                       , GAP_EXACT_ALIGNER_RE_TRACED_PATH_NAME /* vectorsName */
+                                       , TRUE /* setVisible */ 
+                                       );
+                                       
+        /* display dialog with "OK" Button to pause processing
+         * (the tester can analyse the tempory work image while paused.)
+         */
+        msg_txt = g_strdup_printf(_("Fine Tuning step %d done.\n"
+                            ""
+                            "press OK for next iteration step\n")
+                            ,(int)ida
+                         );
+        l_rc = gap_arr_confirm_dialog(msg_txt
+                                 , _("Detail Align FineTuning PAUSED")   /* title_txt */
+                                 , _("Confirm to continue")   /* frame_txt */
+                                 );
+        g_free(msg_txt);
+        if (l_rc != TRUE)
+        {
+           printf("ESCAPE fineTune iteration at attempt IDA:%d pickedArrayIdx:%d because CANCELLED by 
user.\n"
+                ,(int)ida
+                ,(int)pickedArrayIdx
+                );
+           break;
+        }
+     
+     }
+     
+     if (countDisplacementZero >= 4)
+     {
+       if(gap_debug)
+       {
+         printf("ESCAPE fineTune iteration at attempt IDA:%d pickedArrayIdx:%d because transformed layer 
exactly matches at 4 ref points\n"
+                ,(int)ida
+                ,(int)pickedArrayIdx
+                );
+       }
+       break;
+     }
+
+     if (countReloacateErrors >= 4)
+     {
+       if(gap_debug)
+       {
+         printf("ESCAPE fineTune iteration at attempt IDA:%d pickedArrayIdx:%d because locating of tracking 
coordinates FAILED\n"
+                ,(int)ida
+                ,(int)pickedArrayIdx
+                );
+       }
+       break;
+     }     
+     
+     
+  }
+  
+  if (pickedArrayIdx >= 0)
+  {
+     perCoords->x0 = perCoords->ax0[pickedArrayIdx];    
+     perCoords->y0 = perCoords->ay0[pickedArrayIdx];
+     perCoords->x1 = perCoords->ax1[pickedArrayIdx]; 
+     perCoords->y1 = perCoords->ay1[pickedArrayIdx];
+     perCoords->x2 = perCoords->ax2[pickedArrayIdx];
+     perCoords->y2 = perCoords->ay2[pickedArrayIdx];
+     perCoords->x3 = perCoords->ax3[pickedArrayIdx];
+     perCoords->y3 = perCoords->ay3[pickedArrayIdx];
+     
+     if(gap_debug)
+     {
+       printf("\nfineTune RESULT pickedArrayIdx:%d\n\n"
+             ,(int)pickedArrayIdx
+             );
+     }
+  }
+
+  
+  if(gap_debug != TRUE)
+  {
+    /* remove the temporary image (that was created just for probe purpose) */
+    gap_image_delete_immediate(tmpImageId);
+  }
+
+}  /* end p_fineTuneProbePerspectiveTransformationOld2 */
+
+/* --------------------------------------------
+ * p_fineTuneProbePerspectiveTransformationOld   DEPRECATED 
+ * --------------------------------------------
+ * Old implementation. TODO remove this after successful test of the new variante !
+ * --------------------------------------------------------------------------------
+ * This procedure creates a temporary image with a copy of a referenceLayer and performs probe rendering
+ * of transformed copies of the activeDrawable to select the best matching transformation.
+ * Therefore areas of the background (marked as opaque via layermask) 
+ * are compared referenceLayer versus probe rendered transformed layer 
+ * with similar variants of persepective transformation coorinates.
+ *
+ *
+ * This processing and resource intensive fine tuning shall reduce unwanted jitter effects
+ * with small amplitudes near +- 1 pixel.
+ */
+static void
+p_fineTuneProbePerspectiveTransformationOld(gint32 activeDrawableId, gint32 referenceLayerId, 
GapPerspectiveTransCoords *perCoords)
+{
+  gdouble bestAvgColordiff;
+  gdouble bestPrecision;
+  gint    pickedArrayIdx;
+  gint    ida;
+  gint    l_src_offset_x, l_src_offset_y;    /* layeroffsets as they were in src_image */
+  gint32  tmpImageId;
+  gint32  requiredPixelCount;
+  gint32  comparedPixelCount;
+  gint32  tmpBgLayerId;
+
+  
+  if(FALSE)
+  {
+    p_generateFineTuningPerCoords(perCoords);
+  }
+ 
+  if(gap_debug)
+  {
+    printf("p_fineTuneProbePerspectiveTransformation START numberOfArrayValues: %d  activeDrawableId:%d  
referenceLayerId:%d\n"
+      , (int)perCoords->numberOfArrayValues
+      , (int)activeDrawableId
+      , (int)referenceLayerId
+      );
+  }
+
+  if((perCoords->numberOfArrayValues < 2) || (activeDrawableId == referenceLayerId))
+  {
+    /* do nothing in case there are no alternative values available 
+     * or in case active and reference are the same layer
+     */
+    return;
+  }
+
+  bestAvgColordiff = 1.0; /* init with worst possible value */
+  bestPrecision = 9999.9; /* worst */
+  pickedArrayIdx = -1;
+  
+  /* create tmpImageId */
+  tmpImageId = gap_image_new_of_samesize(gimp_item_get_image(activeDrawableId));
+  
+  /* copy referenceLayerId as tmpBgLayerId Layer to tmpImageId 
+   * TODO: check if layermask is included in the copy ...
+   */
+  tmpBgLayerId = gap_layer_copy_to_dest_image(tmpImageId
+                                             , referenceLayerId   /* l_src_layer_id */
+                                             , 100.0   /* Opacity */
+                                             ,0        /* NORMAL */
+                                             ,&l_src_offset_x
+                                             ,&l_src_offset_y
+                                             );
+  gimp_image_insert_layer(tmpImageId
+                         , tmpBgLayerId
+                         , 0
+                         , 0              /* top of layerstack */
+                         );
+  gimp_layer_resize_to_image_size(tmpBgLayerId);
+  
+  // TODO
+  //   if (referenceLayerId has no layermask)
+  //   {
+  //     create a black layermask on tmpBgLayerId with white filled circle around the Target coorinates.
+  //   }
+  
+  
+  /* apply layermask on tmpBgLayerId */
+  gimp_layer_remove_mask (tmpBgLayerId, GIMP_MASK_APPLY);
+
+
+  /* compare refDrawable against itself
+   * to count the number of opaque pixels in it
+   */
+  gap_locateColordiffOpaquePixels(tmpBgLayerId      /* refDrawable */
+                                 , tmpBgLayerId     /*  targetDrawableId */
+                                 , 1                /* gint32  requiredPixelCount */
+                                 , &comparedPixelCount    /* gint32 *comparedPixelCount */
+                                 );
+  /* require 90 percent of the opaque pixels to be involved in further compare steps */
+  requiredPixelCount = (comparedPixelCount * 90) / 100;
+  if(gap_debug)
+  {
+     printf("FINE TUNE tmpBgLayerId:%d  number of opaque pixels found:%d requiredPixelCount:%d (90 
percent)\n"
+           ,(int)tmpBgLayerId
+           ,(int)comparedPixelCount
+           ,(int)requiredPixelCount
+           );
+  }
+
+  if (comparedPixelCount < 10)
+  {
+    /* delete the temporary image and skip fine tune attempts (does not make sense
+     * when having not enough opaque refernce pixels
+     */
+    gap_image_delete_immediate(tmpImageId);
+    return;
+  }
+
+  /* probe perspective transformation variants with potential settings
+   * and pick the one that delivers best match
+   * (with the lowest colordiff compared to the reference layer at its opaque areas)
+   */
+  for(ida = 0; ida < perCoords->numberOfArrayValues; ida++)
+  {
+     gint32 attemptLayerId;
+     gint32 transformedDrawableId;
+     gdouble avgColordiff;
+     
+     
+     /* copy activeDrawableId to tmpImageId */
+     attemptLayerId = gap_layer_copy_to_dest_image(tmpImageId
+                                             , activeDrawableId   /* l_src_layer_id */
+                                             , 100.0   /* Opacity */
+                                             ,0        /* NORMAL */
+                                             ,&l_src_offset_x
+                                             ,&l_src_offset_y
+                                             );
+     gimp_image_insert_layer(tmpImageId
+                             , attemptLayerId
+                             , 0
+                             , 0              /* top of layerstack */
+                             );
+
+     
+     gimp_context_set_defaults();
+     gimp_context_set_transform_resize(GIMP_TRANSFORM_RESIZE_ADJUST);   /* do NOT clip */                    
             
+     gimp_context_set_transform_direction(GIMP_TRANSFORM_FORWARD);                                 
+     transformedDrawableId = gimp_item_transform_perspective(attemptLayerId
+                                      , perCoords->ax0[ida], perCoords->ay0[ida]
+                                      , perCoords->ax1[ida], perCoords->ay1[ida]
+                                      , perCoords->ax2[ida], perCoords->ay2[ida]
+                                      , perCoords->ax3[ida], perCoords->ay3[ida]
+                                      );
+     if(gap_debug)
+     {
+       printf("IDA:%d transformedDrawableId:%d attemptLayerId:%d\n"
+         , (int)ida
+         , (int)transformedDrawableId
+         , (int)attemptLayerId
+         );
+     }
+     
+     gimp_layer_resize_to_image_size(transformedDrawableId);
+     
+     /* compare all opaque pixels and calculate 
+      *  average colordifference tmpBgLayerId versus transformedDrawableId
+      */
+     avgColordiff = gap_locateColordiffOpaquePixels(tmpBgLayerId            /* refDrawable */
+                                                   , transformedDrawableId  /*  targetDrawableId */
+                                                   , requiredPixelCount     /* gint32  requiredPixelCount */
+                                                   , &comparedPixelCount    /* gint32 *comparedPixelCount */
+                                                   );
+     if((avgColordiff < bestAvgColordiff)
+     || ((avgColordiff == bestAvgColordiff) && ( perCoords->aprecision[ida] < bestPrecision)))
+     {
+       /* pick the best matching transformation attempt
+        * for the same transformation to the activeDrawableId.
+        * best matching has smallest color diff compared with referenceLayerId 
+        */
+       pickedArrayIdx = ida;
+       bestAvgColordiff = avgColordiff;
+       bestPrecision = perCoords->aprecision[ida];
+     }
+     
+
+     if(gap_debug)
+     {
+         printf("PROBE ida:%d drawableId:%d  comparedPixelCount:%d avgColordiff:%0.12f precision:%0.12f 
bestPrecision:%0.12f bestAvgColordiff[%d]:%0.12f\n"
+           ,(int)ida
+           ,(int)transformedDrawableId
+           ,(int)comparedPixelCount
+           ,(double)avgColordiff
+           ,(double)perCoords->aprecision[ida]
+           ,(double)bestPrecision
+           ,(int)pickedArrayIdx
+           ,(double)bestAvgColordiff
+           );
+     }
+     
+     
+  }
+  
+  if (pickedArrayIdx >= 0)
+  {
+     perCoords->x0 = perCoords->ax0[pickedArrayIdx];    
+     perCoords->y0 = perCoords->ay0[pickedArrayIdx];
+     perCoords->x1 = perCoords->ax1[pickedArrayIdx]; 
+     perCoords->y1 = perCoords->ay1[pickedArrayIdx];
+     perCoords->x2 = perCoords->ax2[pickedArrayIdx];
+     perCoords->y2 = perCoords->ay2[pickedArrayIdx];
+     perCoords->x3 = perCoords->ax3[pickedArrayIdx];
+     perCoords->y3 = perCoords->ay3[pickedArrayIdx];
+  }
+
+  
+  if(gap_debug)
+  {
+    /* when debugging add_display and keep the tmpImageId for analyse purpose */
+    gimp_display_new(tmpImageId);
+  }
+  else
+  {
+    /* remove the temporary image (that was created just for probe purpose) */
+    gap_image_delete_immediate(tmpImageId);
+  }
+
+}  /* end p_fineTuneProbePerspectiveTransformationOld */
 
 
 
@@ -944,7 +3528,7 @@ p_save_gimprc_gint32_value(const char *gimprc_option_name, gint32 value)
  * deliver angle in degree and scale in percent)
  */
 static void
-p_exact_align_calculate_4point_values(AlingCoords *alingCoords
+p_exact_align_calculate_4point_values(GapAlignCoords *alignCoords
    , gdouble *angleDeg, gdouble *scalePercent, gdouble *moveDx, gdouble *moveDy)
 {
   gdouble px1, py1, px2, py2;
@@ -954,15 +3538,15 @@ p_exact_align_calculate_4point_values(AlingCoords *alingCoords
   gdouble len1, len2;
   gdouble scaleXY;
   
-  px1 = alingCoords->startCoords.px;
-  py1 = alingCoords->startCoords.py;
-  px2 = alingCoords->startCoords2.px;
-  py2 = alingCoords->startCoords2.py;
+  px1 = alignCoords->startCoords[0].px;
+  py1 = alignCoords->startCoords[0].py;
+  px2 = alignCoords->startCoords[1].px;
+  py2 = alignCoords->startCoords[1].py;
   
-  px3 = alingCoords->currCoords.px;
-  py3 = alingCoords->currCoords.py;
-  px4 = alingCoords->currCoords2.px;
-  py4 = alingCoords->currCoords2.py;
+  px3 = alignCoords->currCoords[0].px;
+  py3 = alignCoords->currCoords[0].py;
+  px4 = alignCoords->currCoords[1].px;
+  py4 = alignCoords->currCoords[1].py;
 
   dx1 = px2 - px1;
   dy1 = py2 - py1;
@@ -1017,7 +3601,7 @@ p_exact_align_calculate_4point_values(AlingCoords *alingCoords
 static void
 p_refresh_and_update_infoLabel(GtkWidget *widgetDummy, AlignDialogVals *advPtr)
 {
-  AlingCoords  tmpAlingCoords;
+  GapAlignCoords  tmpAlingCoords;
   gboolean     okSensitiveFlag;
   gdouble      angleDeg;
   gdouble      scalePercent;
@@ -1045,8 +3629,68 @@ p_refresh_and_update_infoLabel(GtkWidget *widgetDummy, AlignDialogVals *advPtr)
   moveDy = 0.0;
   
   advPtr->countVaildPoints = p_capture_4_vector_points(advPtr->image_id, &tmpAlingCoords, 
advPtr->pointOrder);
-  
-  if (advPtr->countVaildPoints == 4)
+  if (advPtr->countVaildPoints >= 8)
+  {
+    GapPerspectiveTransCoords  perspectiveCoords;
+    GapPerspectiveTransCoords *perCoords;
+    gboolean perCoordsOk;
+      
+    perCoords = &perspectiveCoords;
+    
+    perCoords->transformPrecisionThreshold = GAP_GEO_TRANSFORM_PRECISION_THRSHOLD;
+    perCoords->transformPrecision          = GAP_GEO_TRANSFORM_PRECISION;
+    
+    perCoordsOk = gap_geo_perspective_trans_coords_from_align_coords(advPtr->activeDrawableId, 
&tmpAlingCoords, perCoords);
+    if (perCoordsOk == TRUE)
+    {
+      GimpMatrix3     matrix;
+
+      /* calculate the matrix from 4 displaced corners 
+       * (thes ame way as GIMP does for perspective transformation tools) 
+       */
+      gap_geo_assemble_perspective_transformation_matrix(&matrix, perCoords, advPtr->activeDrawableId);
+
+      
+      infoText = g_strdup_printf(_("Current path and %s path triggers Perspective transformation:\n"
+                            "    Top Left     Corner x: %.4f  y: %.4f (pixels)\n"
+                            "    Top Right    Corner x: %.4f  y: %.4f (pixels)\n"
+                            "    Bottom Left  Corner x: %.4f  y: %.4f (pixels)\n"
+                            "    Bottom Right Corner x: %.4f  y: %.4f (pixels)\n"
+                            "Transformation Matrix\n"
+                            "    %12.5f %12.5f %12.5f\n"
+                            "    %12.5f %12.5f %12.5f\n"
+                            "    %12.5f %12.5f %12.5f\n"
+                            "\nPress OK button to perspective transform the layer\n")
+                            , GAP_EXACT_ALIGNER_TARGET_PATH_NAME
+                            , (float) perCoords->x0, (float)perCoords->y0
+                            , (float) perCoords->x1, (float)perCoords->y1
+                            , (float) perCoords->x2, (float)perCoords->y2
+                            , (float) perCoords->x3, (float)perCoords->y3
+                            , (float) matrix.coeff[0][0], (float) matrix.coeff[0][1], (float) 
matrix.coeff[0][2]
+                            , (float) matrix.coeff[1][0], (float) matrix.coeff[1][1], (float) 
matrix.coeff[1][2]
+                            , (float) matrix.coeff[2][0], (float) matrix.coeff[2][1], (float) 
matrix.coeff[2][2]
+                            );
+      gtk_label_set_text(GTK_LABEL(advPtr->infoLabel), infoText);
+      g_free(infoText);
+      okSensitiveFlag = TRUE;
+    }
+    else
+    {
+      infoText = g_strdup_printf(_("Current paths are not valid for Perspective transformation:\n"
+                            "    For valid transformation 4 points are required\n"
+                            "    both in the active path and in another path with the name: %s \n"
+                            "    AND the connection of the 4 points must build up 4 differnt lines.\n"
+                            "")
+                            , GAP_EXACT_ALIGNER_TARGET_PATH_NAME
+                            );
+      gtk_label_set_text(GTK_LABEL(advPtr->infoLabel), infoText);
+      g_free(infoText);
+      okSensitiveFlag = FALSE;
+    }
+  }  
+  else if ((advPtr->countVaildPoints >= 4) 
+       && (tmpAlingCoords.startCoords[1].valid)
+       && (tmpAlingCoords.currCoords[1].valid))
   {
     p_exact_align_calculate_4point_values(&tmpAlingCoords
           , &angleDeg, &scalePercent, &moveDx, &moveDy);
@@ -1057,8 +3701,7 @@ p_refresh_and_update_infoLabel(GtkWidget *widgetDummy, AlignDialogVals *advPtr)
                             "    Scale:      %.1f (%%)\n"
                             "    Movement X: %.0f (pixels)\n"
                             "    Movement Y: %.0f (pixels)\n"
-                            "Press OK button to transform the layer\n"
-                            "in a way that point3 moves to point1 and point4 moves to point2")
+                            "\nPress OK button to transform the layer\n")
                             , (float) angleDeg
                             , (float) scalePercent
                             , (float) moveDx
@@ -1068,16 +3711,17 @@ p_refresh_and_update_infoLabel(GtkWidget *widgetDummy, AlignDialogVals *advPtr)
     g_free(infoText);
     okSensitiveFlag = TRUE;
   }
-  else if (advPtr->countVaildPoints == 2)
+  else if ((advPtr->countVaildPoints >= 2)
+       && (tmpAlingCoords.startCoords[0].valid)
+       && (tmpAlingCoords.currCoords[0].valid))
   {
-    moveDx = tmpAlingCoords.currCoords.px - tmpAlingCoords.startCoords.px;
-    moveDy = tmpAlingCoords.currCoords.py - tmpAlingCoords.startCoords.py;
+    moveDx = tmpAlingCoords.currCoords[0].px - tmpAlingCoords.startCoords[0].px;
+    moveDy = tmpAlingCoords.currCoords[0].py - tmpAlingCoords.startCoords[0].py;
     
     infoText = g_strdup_printf(_("Current path with 2 points triggers simple move:\n"
                             "    Movement X: %.0f (pixels)\n"
                             "    Movement Y: %.0f (pixels)\n"
-                            "Press OK button to move the layer\n"
-                            "in a way that point2 moves to point1")
+                            "\nPress OK button to move the layer\n")
                             , (float) moveDx
                             , (float) moveDy
                             );
@@ -1088,10 +3732,23 @@ p_refresh_and_update_infoLabel(GtkWidget *widgetDummy, AlignDialogVals *advPtr)
   }
   else
   {
-    gtk_label_set_text(GTK_LABEL(advPtr->infoLabel)
+    if (advPtr->pointOrder == POINT_ORDER_MODE_1234_1234)
+    {
+      infoText = g_strdup_printf(_("This filter requires a current path with 4 or 2 points\n"
+                                   "and a path with name: %s with same number of points.\n"
+                                   "\nPlease create both paths and press the Refresh button.\n")
+                                 , GAP_EXACT_ALIGNER_TARGET_PATH_NAME
+                                 );
+      gtk_label_set_text(GTK_LABEL(advPtr->infoLabel), infoText);
+      g_free(infoText);
+    }
+    else
+    {
+      gtk_label_set_text(GTK_LABEL(advPtr->infoLabel)
                         , _("This filter requires a current path with 4 or 2 points\n"
                             "It can transform and/or move the current layer according to such path 
coordinate points.\n"
-                            "Please create a path and press the Refresh button."));
+                            "\nPlease create a path and press the Refresh button."));
+    }
     okSensitiveFlag = FALSE;
   }
 
@@ -1166,6 +3823,8 @@ on_order_mode_radio_callback(GtkWidget *wgt, gpointer user_data)
   {
      if(wgt == advPtr->radio_order_mode_31_42)    { advPtr->pointOrder = POINT_ORDER_MODE_31_42; }
      if(wgt == advPtr->radio_order_mode_21_43)    { advPtr->pointOrder = POINT_ORDER_MODE_21_43; }
+     if(wgt == advPtr->radio_order_mode_1234)     { advPtr->pointOrder = POINT_ORDER_MODE_1234_1234; }
+     
 
      p_refresh_and_update_infoLabel(NULL, advPtr);
 
@@ -1190,6 +3849,7 @@ p_align_dialog(AlignDialogVals *advPtr)
   GSList    *vbox1_group = NULL;
   GtkWidget *radiobutton;
   gint       row;
+  gchar     *text;
 
 
   advPtr->runExactAlign = FALSE;
@@ -1267,7 +3927,12 @@ p_align_dialog(AlignDialogVals *advPtr)
 
 
   /* Order Mode the radio buttons */
-  radiobutton = gtk_radio_button_new_with_label (vbox1_group, _("( 3 --> 1 )  ( 4 --> 2 )\nTarget is marked 
by points 1&2 "));
+  text = g_strdup_printf(_("( 3 --> 1 )  ( 4 --> 2 )\n"
+                           "Source is marked by current path points 3&4\n"
+                           "Target is marked by current path points 1&3\n")
+                          );
+  radiobutton = gtk_radio_button_new_with_label (vbox1_group, text);
+  g_free(text);
   advPtr->radio_order_mode_31_42 = radiobutton;
   vbox1_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radiobutton));
   gtk_widget_show (radiobutton);
@@ -1278,7 +3943,12 @@ p_align_dialog(AlignDialogVals *advPtr)
     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (radiobutton), TRUE);
   }
 
-  radiobutton = gtk_radio_button_new_with_label (vbox1_group, "( 2 --> 1 )  ( 4 --> 3 )\nTarget is marked by 
points 1&3");
+  text = g_strdup_printf(_("( 2 --> 1 )  ( 4 --> 3 )\n"
+                           "Source is marked by current path points 2&4\n"
+                           "Target is marked by current path points 1&3\n")
+                          );
+  radiobutton = gtk_radio_button_new_with_label (vbox1_group, text);
+  g_free(text);
   advPtr->radio_order_mode_21_43 = radiobutton;
   vbox1_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radiobutton));
   gtk_widget_show (radiobutton);
@@ -1289,6 +3959,24 @@ p_align_dialog(AlignDialogVals *advPtr)
     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (radiobutton), TRUE);
   }
 
+  text = g_strdup_printf(_("( 1 --> T1 ) ( 2 --> T2 ) ( 3 --> T3 ) ( 4 --> T4 )\n"
+                           "Source is marked by current path points 1,2,3,4\n"
+                           "Target is marked by path with name: %s points 1,2,3,4")
+                          , GAP_EXACT_ALIGNER_TARGET_PATH_NAME
+                          );
+  radiobutton = gtk_radio_button_new_with_label (vbox1_group, text);
+  g_free(text);
+  advPtr->radio_order_mode_1234 = radiobutton;
+  vbox1_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radiobutton));
+  gtk_widget_show (radiobutton);
+  gtk_box_pack_start (GTK_BOX (vbox1), radiobutton, FALSE, FALSE, 0);
+  g_signal_connect (G_OBJECT (radiobutton),     "clicked",  G_CALLBACK (on_order_mode_radio_callback), 
advPtr);
+  if(advPtr->pointOrder == POINT_ORDER_MODE_1234_1234)
+  {
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (radiobutton), TRUE);
+  }
+
+
   p_refresh_and_update_infoLabel(NULL, advPtr);
 
   /* Done */
@@ -1315,13 +4003,13 @@ gint32
 gap_detail_exact_align_via_4point_path(gint32 image_id, gint32 activeDrawableId
    , gint32 pointOrder, GimpRunMode runMode)
 {
-  AlingCoords  alingCoordinates;
-  AlingCoords *alingCoords;
+  GapAlignCoords  alingCoordinates;
+  GapAlignCoords *alignCoords;
   gint         countVaildPoints;
   gint32       ret;
   AlignDialogVals         advals;
   
-  alingCoords = &alingCoordinates;
+  alignCoords = &alingCoordinates;
   ret = -1;
   
 
@@ -1341,7 +4029,7 @@ gap_detail_exact_align_via_4point_path(gint32 image_id, gint32 activeDrawableId
     advals.pointOrder = gap_base_get_gimprc_int_value(GIMPRC_EXACT_ALIGN_PATH_POINT_ORDER
                                                     , POINT_ORDER_MODE_31_42 /* DEFAULT */
                                                     , POINT_ORDER_MODE_31_42 /* min */
-                                                    , POINT_ORDER_MODE_21_43 /* max */
+                                                    , POINT_ORDER_MODE_1234_1234 /* max */
                                                     );
   }
 
@@ -1367,10 +4055,26 @@ gap_detail_exact_align_via_4point_path(gint32 image_id, gint32 activeDrawableId
  
   gimp_image_undo_group_start (image_id);
   
-  countVaildPoints = p_capture_4_vector_points(image_id, alingCoords, advals.pointOrder);
-  if(countVaildPoints == 4)
+  countVaildPoints = p_capture_4_vector_points(image_id, alignCoords, advals.pointOrder);
+
+  if(gap_debug)
+  {
+    printf("gap_detail_exact_align_via_4point_path activeDrawableId:%d pointOrder:%d countVaildPoints:%d\n"
+      , (int)activeDrawableId
+      , (int)advals.pointOrder
+      , (int)countVaildPoints
+      );
+  }
+
+  if(countVaildPoints == 8)
+  {
+    ret = p_perspective_align_drawable(activeDrawableId, alignCoords, -1
+                                      , GAP_GEO_TRANSFORM_PRECISION_THRSHOLD
+                                      , GAP_GEO_TRANSFORM_PRECISION);
+
+  } else if(countVaildPoints == 4)
   {
-    ret = p_exact_align_drawable(activeDrawableId, alingCoords);
+    ret = p_exact_align_drawable(activeDrawableId, alignCoords);
 
   }
   else if(countVaildPoints == 2)
@@ -1378,8 +4082,8 @@ gap_detail_exact_align_via_4point_path(gint32 image_id, gint32 activeDrawableId
     /* force order 0213 
      *  (captures the 2 valid points into startCoords and currCoords)
      */
-    countVaildPoints = p_capture_4_vector_points(image_id, alingCoords, POINT_ORDER_MODE_21_43);
-    ret = p_set_drawable_offsets(activeDrawableId, alingCoords);
+    countVaildPoints = p_capture_4_vector_points(image_id, alignCoords, POINT_ORDER_MODE_21_43);
+    ret = p_set_drawable_offsets(activeDrawableId, alignCoords);
   }
   else
   {
diff --git a/gap/gap_detail_align_exec.h b/gap/gap_detail_align_exec.h
index 3d83c53..4fe3776 100644
--- a/gap/gap_detail_align_exec.h
+++ b/gap/gap_detail_align_exec.h
@@ -57,13 +57,25 @@
 #define GAP_EXACT_ALIGNER_PLUG_IN_NAME                    "gap-exact-aligner"
 #define GAP_EXACT_ALIGNER_PLUG_IN_NAME_BINARY             "gap_exact_aligner"
 
+#define GAP_EXACT_ALIGNER_TARGET_PATH_NAME                "TARGET"
+#define GAP_EXACT_ALIGNER_SRC_PATH_NAME                   "SRC"
+#define GAP_EXACT_ALIGNER_RE_TRACED_PATH_NAME             "ReTraced"  /* used for debug fine tuning */
+#define GAP_EXACT_ALIGNER_USRC_PATH_NAME                  "USRC"      /* untuned src for debug */
+
+
+
+#define GAP_EXACT_ALIGNER_REF_LAYER_NAME                  "REF"
+
 
 #define POINT_ORDER_MODE_31_42   0
 #define POINT_ORDER_MODE_21_43   1
+#define POINT_ORDER_MODE_1234_1234   2
 
 
 typedef struct XmlAlignValues {
    gint32     framePhase;
+   gdouble    transformPrecision;           /* precision in pixels for calculating the perspective 
transformation matrix (0.0 to 1.0) */
+   gdouble    transformPrecisionThreshold;  /* for fine tuning purpose (use perespective coords with 
precision lower than threshold) */
    char       moveLogFile[1600];
 } XmlAlignValues;
 
diff --git a/gap/gap_detail_tracking_exec.c b/gap/gap_detail_tracking_exec.c
index 9caf4bf..d3c5c17 100644
--- a/gap/gap_detail_tracking_exec.c
+++ b/gap/gap_detail_tracking_exec.c
@@ -39,7 +39,9 @@ extern int gap_debug;
 #include <gtk/gtk.h>
 #include <libgimp/gimp.h>
 #include <libgimp/gimpui.h>
+#include <math.h>
 
+#include "gap_geo.h"
 #include "gap_base.h"
 #include "gap_libgapbase.h"
 #include "gap_lib.h"
@@ -48,13 +50,16 @@ extern int gap_debug;
 #include "gap_colordiff.h"
 #include "gap_image.h"
 #include "gap_layer_copy.h"
+#include "gap_detail_align_exec.h"
 #include "gap_detail_tracking_exec.h"
+#include "gap_xml_util.h"
 
 #include "gap-intl.h"
 
 #define DEFAULT_refShapeRadius            15
 #define DEFAULT_targetMoveRadius          70
 #define DEFAULT_loacteColodiffThreshold   0.08
+#define DEFAULT_numPointsSelect           4
 #define DEFAULT_coordsRelToFrame1         TRUE
 #define DEFAULT_offsX                     0
 #define DEFAULT_offsY                     0
@@ -62,47 +67,87 @@ extern int gap_debug;
 #define DEFAULT_enableScaling             TRUE
 #define DEFAULT_removeMidlayers           TRUE
 #define DEFAULT_bgLayerIsReference        TRUE
+#define DEFAULT_addTransformedLayer       TRUE
 
 #define NUMBER_OF_COORDS 12
 
+typedef struct BestIndexes
+{
+  gint32 bestIdx[4];   /* indexes of the 4 best matching points, -1 indicates an empty unusable index */
+} BestIndexes;
+
+
+
 static gdouble   p_calculate_angle_in_degree(gint p1x, gint p1y, gint p2x, gint p2y);
 static gdouble   p_calculate_scale_factor(gint p1x, gint p1y, gint p2x, gint p2y
                        , gint p3x, gint p3y, gint p4x, gint p4y);
-static gdouble   p_calculateSqrDist(PixelCoords *coordA, PixelCoords *coordB);
-static gdouble   p_getPixelCoordsQuality(PixelCoords *coords);
+static gdouble   p_getPixelCoordsQuality(GapPixelCoords *coords);
 static void      p_capture_n_vector_points(gint32 imageId, PixelCoordsArray *pixelCoordsArray, gint 
ncoordsToCapture, gchar *vectorsName);
-static void      p_copy_src_to_dst_coords(PixelCoords *srcCoords, PixelCoords *dstCoords);
 static void      p_copy_src_to_dst_coords_array(PixelCoordsArray *srcCoordsArray, PixelCoordsArray 
*dstCoordsArray);
-static void      p_locate_target(gint32 refLayerId, PixelCoords *refCoords
-                    , gint32 targetLayerId, PixelCoords *targetCoords
+static void      p_locate_target(gint32 refLayerId, GapPixelCoords *refCoords
+                    , gint32 targetLayerId, GapPixelCoords *targetCoords
                     , gint32 locateOffsetX, gint32 locateOffsetY
                     , FilterValues *valPtr);
-static void      p_write_xml_header(FILE *l_fp, gboolean center, gint width, gint height, gint numFrames);
+static void      p_write_xml_header(FILE *l_fp, gboolean center, gint width, gint height, gint numFrames, 
FilterValues *valPtr);
 static void      p_write_xml_footer(FILE *l_fp);
 static gboolean  p_log_to_file(const char *filename, const char *logString
-                    , gint32 frameNr, gboolean center, gint width, gint height);
-static void      p_coords_logging(gint32 frameNr, PixelCoords *currCoords,  PixelCoords *currCoords2
-                    , PixelCoords *startCoords, PixelCoords *startCoords2, FilterValues *valPtr
-                    , gint32 imageId
+                    , gint32 frameNr, gboolean center, gint width, gint height, FilterValues *valPtr);
+static void      p_coords_logging(gint32 frameNr, GapPixelCoords *currCoords,  GapPixelCoords *currCoords2
+                    , GapPixelCoords *startCoords, GapPixelCoords *startCoords2, FilterValues *valPtr
+                    , gint32 imageId, GapPixelCoords *startRefCoords, gint32 ii1, gint32 ii2
                     );
-static void     p_select_best_coords(gint32 frameNr, PixelCoordsArray *currCoordsArray
+
+static void     p_computePredictedCoordinate(GapPixelCoords *predictedCoord
+                   , GapPixelCoords *strongRef
+                   , GapPixelCoords *strongTrk
+                   , GapPixelCoords *weakRef);
+static void     p_coords_tune_and_logging_perspective(gint32 frameNr
+                   , PixelCoordsArray *currCoordsArray
+                   , PixelCoordsArray *startCoordsArray
+                   , PixelCoordsArray *prevCoordsArray
+                   , BestIndexes *bestIndexes
+                   , FilterValues *valPtr
+                   , gint32 imageId
+                   , gint32 activeDrawableId
+                   , gint32 referenceLayerId
+                   );
+
+static void     p_pickNearestToCorners(BestIndexes *bestIndexes, gint32 width, gint32 height
+                   , PixelCoordsArray *startCoordsArray
+                   );
+static void     p_select_best_coords(gint32 imageId, gint32 frameNr, PixelCoordsArray *currCoordsArray
                    , PixelCoordsArray *startCoordsArray, FilterValues *valPtr
-                   , gint32 *bestIdx1
-                   , gint32 *bestIdx2
+                   , BestIndexes *bestIndexes, FrameHistInfo *frameHistInfo
                    );
-static gint32  p_selective_coords_logging(gint32 frameNr 
-                    , PixelCoordsArray *currCoordsArray
-                    , PixelCoordsArray *startCoordsArray
-                    , FilterValues *valPtr
-                    , gint32 imageId
-                    );
+static gint32  p_selective_coords_logging(FrameHistInfo *frameHistInfo
+                  , PixelCoordsArray *currCoordsArray
+                  , PixelCoordsArray *startCoordsArray
+                  , PixelCoordsArray *prevCoordsArray
+                  , FilterValues *valPtr
+                  , gint32 imageId
+                  , gint32 activeDrawableId
+                  , gint32 referenceLayerId
+                 );
 static gint32    p_parse_frame_nr_from_layer_name(gint32 layerId);
-static void      p_get_frameHistInfo(FrameHistInfo *frameHistInfo);
-static void      p_set_frameHistInfo(FrameHistInfo *frameHistInfo);
-static void      p_set_n_vector_points(gint32 imageId, PixelCoordsArray *targetCoordsArray, gchar 
*vectorsName
+static void      p_get_frameHistInfo(FrameHistInfo *frameHistInfo, gint32 imageId);
+static void      p_set_frameHistInfo(FrameHistInfo *frameHistInfo, gint32 imageId);
+static gint32    p_set_n_vector_points(gint32 imageId, PixelCoordsArray *targetCoordsArray, gchar 
*vectorsName
                     , gboolean setVisible, gboolean setGuides, gint32 guideIdx);
 
 
+static void      p_set_debugCoords_from_IntersectionPoints(gint32 frameNr, gchar *vectorName
+                    , PixelCoordsArray *debugCoordsArray
+                    , GapLineDescriptionConsts *debugLine
+                    , GapLineDescriptionConsts *aBorderLine, GapLineDescriptionConsts *bBorderLine);
+static void      p_set_debug_intersection_vectors(gint32 frameNr, gint32 imageId, gchar *vectorBasename
+                      , PixelCoordsArray *pixelCoordsArray, BestIndexes *bestIndexes);
+static void      p_set_debug_vectors(gint32 frameNr, gint32 imageId
+                    , PixelCoordsArray *currCoordsArray
+                    , PixelCoordsArray *startCoordsArray
+                    , BestIndexes *bestIndexes);
+
+
+
 /* -----------------------------------
  * p_calculate_angle_in_degree
  * -----------------------------------
@@ -186,40 +231,14 @@ p_calculate_scale_factor(gint p1x, gint p1y, gint p2x, gint p2y
 }  /* end p_calculate_scale_factor */
 
 
-
-/* ----------------------------
- * p_calculateSqrDist
- * ----------------------------
- * returns the square distance between coordA and coordB
- *
- */
-static gdouble
-p_calculateSqrDist(PixelCoords *coordA, PixelCoords *coordB)
-{
-  gdouble lenX;
-  gdouble lenY;
-  gdouble sqrDist;
-  
-  lenX = coordA->px - coordB->px;
-  lenY = coordA->py - coordB->py;
-  
-  sqrDist = (lenX * lenX) + (lenY * lenY);
-  return sqrDist;
-
-} /* end p_calculateSqrDist */
-
 /* ----------------------------
  * p_getPixelCoordsQuality
  * ----------------------------
- * log the best 2 coordinates to stdout
- * or to move-path controlpoint XML file.
- *
- * weight depends on average colordiff (while locating the coordinate)
- * and distance (the longer the better for good precision 
- *  while calcualting rotation angle and scaling)
+ * returns the locating quality between 0.0 and 1.0 where 1.0 is best quality.
+ * invalid coords are rated as quiality 0.0
  */
 static gdouble
-p_getPixelCoordsQuality(PixelCoords *coords) 
+p_getPixelCoordsQuality(GapPixelCoords *coords) 
 {
   gdouble qaulity;
   if (coords->valid)
@@ -252,7 +271,7 @@ p_capture_n_vector_points(gint32 imageId, PixelCoordsArray *pixelCoordsArray, gi
   gint32  activeVectorsId;
   gint32  gx1;
   gint32  gy1;
-  PixelCoords *coordPtr;
+  GapPixelCoords *coordPtr;
 
   gx1 = -1;
   gy1 = -1;
@@ -376,18 +395,6 @@ p_capture_n_vector_points(gint32 imageId, PixelCoordsArray *pixelCoordsArray, gi
 }  /* end p_capture_n_vector_points */
 
 
-/* ------------------------------------
- * p_copy_src_to_dst_coords
- * ------------------------------------
- */
-static void
-p_copy_src_to_dst_coords(PixelCoords *srcCoords, PixelCoords *dstCoords)
-{
-  dstCoords->valid = srcCoords->valid;
-  dstCoords->avgColorDiff = srcCoords->avgColorDiff;
-  dstCoords->px = srcCoords->px;
-  dstCoords->py = srcCoords->py;
-}
 
 
 /* ------------------------------------
@@ -405,7 +412,7 @@ p_copy_src_to_dst_coords_array(PixelCoordsArray *srcCoordsArray, PixelCoordsArra
   
   for(idx = 0; idx < numberOfCoords; idx++)
   {
-    p_copy_src_to_dst_coords(&srcCoordsArray->pixCoord[idx]
+    gap_geo_copy_src_to_dst_coords(&srcCoordsArray->pixCoord[idx]
                             ,&dstCoordsArray->pixCoord[idx]
                             );
   }
@@ -418,8 +425,8 @@ p_copy_src_to_dst_coords_array(PixelCoordsArray *srcCoordsArray, PixelCoordsArra
  * ------------------------------------
  */
 static void
-p_locate_target(gint32 refLayerId, PixelCoords *refCoords
-   , gint32 targetLayerId, PixelCoords *targetCoords
+p_locate_target(gint32 refLayerId, GapPixelCoords *refCoords
+   , gint32 targetLayerId, GapPixelCoords *targetCoords
    , gint32 locateOffsetX, gint32 locateOffsetY
    , FilterValues *valPtr)
 {
@@ -468,7 +475,7 @@ p_locate_target(gint32 refLayerId, PixelCoords *refCoords
     targetCoords->valid = TRUE;
   }
 
-  //if(gap_debug)
+  if(gap_debug)
   {
     printf("p_locate_target: refX:%d refY:%d locateOffsetX:%d locateOffsetY:%d\n"
             "                targetX:%d targetY:%d targetCoords->px:%d py:%d  avgColodiff:%.5f valid:%d\n"
@@ -495,30 +502,51 @@ p_locate_target(gint32 refLayerId, PixelCoords *refCoords
  * write header for a MovePath XML file
  */
 static void
-p_write_xml_header(FILE *l_fp, gboolean center, gint width, gint height, gint numFrames)
+p_write_xml_header(FILE *l_fp, gboolean center, gint width, gint height, gint numFrames, FilterValues 
*valPtr)
 {
   fprintf(l_fp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+  
+  
   fprintf(l_fp, "<gimp_gap_move_path_parameters version=\"2\" >\n");
-  fprintf(l_fp, "  <frame_description width=\"%d\" height=\"%d\" range_from=\"1\" range_to=\"%d\" 
total_frames=\"%d\" />\n"
-          , (int)width
-          , (int)height
-          , (int)numFrames
-          , (int)numFrames
-          );
+  
+  fprintf(l_fp, "  <gimp_gap_tracking_parameters  ");
+  gap_xml_write_int_value(l_fp, "numPointsSelect", (gint32) valPtr->numPointsSelect);         
+  gap_xml_write_int_value(l_fp, "refShapeRadius", (gint32) valPtr->refShapeRadius);         
+  gap_xml_write_int_value(l_fp, "targetMoveRadius", (gint32) valPtr->targetMoveRadius);         
+  gap_xml_write_gdouble_value(l_fp, "loacteColodiffThreshold", valPtr->loacteColodiffThreshold, 1 /* digits 
*/, 5 /* precision_digits */);
+  gap_xml_write_gboolean_value(l_fp, "coordsRelToFrame1", valPtr->coordsRelToFrame1);
+  gap_xml_write_int_value(l_fp, "offsX", (gint32) valPtr->offsX);
+  gap_xml_write_int_value(l_fp, "offsY", (gint32) valPtr->offsY);
+  gap_xml_write_gdouble_value(l_fp, "offsRotate", valPtr->offsRotate, 3 /* digits */, 5 /* precision_digits 
*/);
+  gap_xml_write_gboolean_value(l_fp, "enableScaling", valPtr->enableScaling);
+  fprintf(l_fp, " />\n");
+
+  fprintf(l_fp, "  <frame_description ");
+  gap_xml_write_int_value(l_fp, "width", (gint32) width);         
+  gap_xml_write_int_value(l_fp, "height", (gint32) height);         
+  gap_xml_write_int_value(l_fp, "range_from", (gint32) 1);         
+  gap_xml_write_int_value(l_fp, "range_to", (gint32) numFrames);         
+  gap_xml_write_int_value(l_fp, "total_frames", (gint32) numFrames);         
+  fprintf(l_fp, " />\n");
+          
+          
   fprintf(l_fp, "  <tween tween_steps=\"0\" />\n");
   fprintf(l_fp, "  <trace tracelayer_enable=\"FALSE\" />\n");
   fprintf(l_fp, "  <moving_object src_layer_id=\"0\" src_layerstack=\"0\" width=\"%d\" height=\"%d\"\n"
           , (int)width
           , (int)height
           );
+          
   if(center)
   {
-    fprintf(l_fp, "    src_handle=\"GAP_HANDLE_CENTER\"\n");
+    fprintf(l_fp, "    src_handle=\"GAP_HANDLE_CENTER\"");
   }
   else
   {
-    fprintf(l_fp, "    src_handle=\"GAP_HANDLE_LEFT_TOP\"\n");
+    fprintf(l_fp, "    src_handle=\"GAP_HANDLE_LEFT_TOP\"");
   }
+  fprintf(l_fp, " handle_dx=\"0\" handle_dy=\"0\"\n"); 
+          
   fprintf(l_fp, "    src_stepmode=\"GAP_STEP_FRAME_ONCE\" step_speed_factor=\"1.00000\"\n");
   fprintf(l_fp, "    src_selmode=\"GAP_MOV_SEL_IGNORE\"\n");
   fprintf(l_fp, "    src_paintmode=\"GIMP_NORMAL_MODE\"\n");
@@ -526,7 +554,8 @@ p_write_xml_header(FILE *l_fp, gboolean center, gint width, gint height, gint nu
   fprintf(l_fp, "    >\n");
   fprintf(l_fp, "  </moving_object>\n");
   fprintf(l_fp, "\n");
-  fprintf(l_fp, "  <controlpoints current_point=\"1\" number_of_points=\"%d\"  >\n"
+
+  fprintf(l_fp, "  <controlpoints current_point=\"0\" number_of_points=\"%d\"  >\n"
           , (int)numFrames
           );
 
@@ -556,7 +585,7 @@ p_write_xml_footer(FILE *l_fp)
  */
 static gboolean
 p_log_to_file(const char *filename, const char *logString
-   , gint32 frameNr, gboolean center, gint width, gint height)
+   , gint32 frameNr, gboolean center, gint width, gint height, FilterValues *valPtr)
 {
   char *textBuffer;
   gsize lengthTextBuffer;
@@ -650,7 +679,7 @@ p_log_to_file(const char *filename, const char *logString
   l_fp = g_fopen(filename, "w+");
   if(l_fp != NULL)
   {
-      p_write_xml_header(l_fp, center, width, height, frameNr);
+      p_write_xml_header(l_fp, center, width, height, frameNr, valPtr);
 
       if (copySize > 0)
       {
@@ -699,9 +728,9 @@ p_log_to_file(const char *filename, const char *logString
  *
  */
 static void
-p_coords_logging(gint32 frameNr, PixelCoords *currCoords,  PixelCoords *currCoords2
-  , PixelCoords *startCoords, PixelCoords *startCoords2, FilterValues *valPtr
-  , gint32 imageId
+p_coords_logging(gint32 frameNr, GapPixelCoords *currCoords,  GapPixelCoords *currCoords2
+  , GapPixelCoords *startCoords, GapPixelCoords *startCoords2, FilterValues *valPtr
+  , gint32 imageId, GapPixelCoords *startRefCoords, gint32 ii1, gint32 ii2
   )
 {
   gint32  px;
@@ -719,12 +748,12 @@ p_coords_logging(gint32 frameNr, PixelCoords *currCoords,  PixelCoords *currCoor
   gint     precision_digits;
   gchar   *rotValueAsString;
 
-  if(currCoords->valid != TRUE)
+  if(startRefCoords->valid != TRUE)
   {
     /* do not record invalid coordinates */
     return;
   }
-
+  
   width = gimp_image_width(imageId);
   height = gimp_image_height(imageId);
   center = FALSE;
@@ -734,25 +763,48 @@ p_coords_logging(gint32 frameNr, PixelCoords *currCoords,  PixelCoords *currCoor
   {
     center = TRUE;
   }
+  
 
   scaleFactor = 1.0;
   rotation = 0.0;
-  px1 = currCoords->px;
-  py1 = currCoords->py;
-
+  if(currCoords->valid != TRUE)
+  {
+    /* fallback to initial start reference coordinates */
+    px1 = startRefCoords->px;
+    py1 = startRefCoords->px;
+  }
+  else
+  {
+    px1 = currCoords->px;
+    py1 = currCoords->py;
+  }
   px2 = currCoords2->px;
   py2 = currCoords2->py;
+ 
 
-  if ((valPtr->coordsRelToFrame1)
-  &&  (startCoords->valid == TRUE))
+  /* px1 and px2 represent the current coordinate or fallback value at this point */
+  if (valPtr->coordsRelToFrame1)
   {
-    px1 = startCoords->px -px1;
-    py1 = startCoords->py -py1;
+    /* px py is difference */
+    px = startCoords->px -px1;
+    py = startCoords->py -py1;
+    px1 = px;
+    py1 = py;
+  }
+  else
+  {
+    /* px py is absolute value transformed to the reference point
+     * (reference point ist the first in the path)
+     */
+    px = (px1 - startCoords->px) + startRefCoords->px;
+    py = (py1 - startCoords->py) + startRefCoords->py;
   }
 
+  /* add the offsets */
+  px += valPtr->offsX;
+  py += valPtr->offsY;
+
 
-  px = px1 + valPtr->offsX;
-  py = py1 + valPtr->offsY;
 
   if ((valPtr->coordsRelToFrame1)
   &&  (valPtr->numPointsSelect > 1)
@@ -825,7 +877,7 @@ p_coords_logging(gint32 frameNr, PixelCoords *currCoords,  PixelCoords *currCoor
 
     if(valPtr->enableScaling == TRUE)
     {
-      logString = g_strdup_printf("    <controlpoint px=\"%04d\" py=\"%04d\" rotation=\"%s\" 
width_resize=\"%s\" height_resize=\"%s\" keyframe_abs=\"%d\" p1x=\"%04d\"  p1y=\"%04d\"  p2x=\"%04d\" 
p2y=\"%04d\" s1x=\"%04d\" s1y=\"%04d\" s2x=\"%04d\" s2y=\"%04d\"/>"
+      logString = g_strdup_printf("    <controlpoint px=\"%04d\" py=\"%04d\" rotation=\"%s\" 
width_resize=\"%s\" height_resize=\"%s\" keyframe_abs=\"%06d\" p1x=\"%04d\"  p1y=\"%04d\"  p2x=\"%04d\" 
p2y=\"%04d\" s1x=\"%04d\" s1y=\"%04d\" s2x=\"%04d\" s2y=\"%04d\" ii1=\"%d\"  ii2=\"%d\"/>"
        , px
        , py
        , rotValueAsString
@@ -840,11 +892,13 @@ p_coords_logging(gint32 frameNr, PixelCoords *currCoords,  PixelCoords *currCoor
        , startCoords->py
        , startCoords2->px
        , startCoords2->py
+       , (int) ii1
+       , (int) ii2
        );
     }
     else
     {
-      logString = g_strdup_printf("    <controlpoint px=\"%04d\" py=\"%04d\" rotation=\"%s\" 
keyframe_abs=\"%d\" p1x=\"%04d\"  p1y=\"%04d\"  p2x=\"%04d\" p2y=\"%04d\" s1x=\"%04d\" s1y=\"%04d\" 
s2x=\"%04d\" s2y=\"%04d\"/>"
+      logString = g_strdup_printf("    <controlpoint px=\"%04d\" py=\"%04d\" rotation=\"%s\" 
keyframe_abs=\"%06d\" p1x=\"%04d\"  p1y=\"%04d\"  p2x=\"%04d\" p2y=\"%04d\" s1x=\"%04d\" s1y=\"%04d\" 
s2x=\"%04d\" s2y=\"%04d\" ii1=\"%d\"  ii2=\"%d\"/>"
        , px
        , py
        , rotValueAsString
@@ -857,6 +911,8 @@ p_coords_logging(gint32 frameNr, PixelCoords *currCoords,  PixelCoords *currCoor
        , startCoords->py
        , startCoords2->px
        , startCoords2->py
+       , (int) ii1
+       , (int) ii2
        );
     }
 
@@ -869,23 +925,23 @@ p_coords_logging(gint32 frameNr, PixelCoords *currCoords,  PixelCoords *currCoor
     /* single point detail coordinate tracking */
     if (valPtr->offsRotate == 0.0)
     {
-      logString = g_strdup_printf("    <controlpoint px=\"%04d\" py=\"%04d\"  keyframe_abs=\"%d\" 
p1x=\"%04d\"  p1y=\"%04d\" s1x=\"%04d\"  s1y=\"%04d\"/>"
+      logString = g_strdup_printf("    <controlpoint px=\"%04d\" py=\"%04d\"  keyframe_abs=\"%06d\" 
p1x=\"%04d\"  p1y=\"%04d\" s1x=\"%04d\"  s1y=\"%04d\" ii1=\"%d\"/>"
          , px
          , py
          , frameNr
          , currCoords->px
          , currCoords->py
-       
          , startCoords->px
          , startCoords->py
+         , (int) ii1
          );
     }
     else
     {
-      rotValueAsString = gap_base_gdouble_to_ascii_string(valPtr->offsRotate, precision_digits);
       precision_digits = 7;
+      rotValueAsString = gap_base_gdouble_to_ascii_string(valPtr->offsRotate, precision_digits);
 
-      logString = g_strdup_printf("    <controlpoint px=\"%04d\" py=\"%04d\"  rotation=\"%s\" 
keyframe_abs=\"%d\" p1x=\"%04d\"  p1y=\"%04d\" s1x=\"%04d\"  s1y=\"%04d\"/>"
+      logString = g_strdup_printf("    <controlpoint px=\"%04d\" py=\"%04d\"  rotation=\"%s\" 
keyframe_abs=\"%06d\" p1x=\"%04d\"  p1y=\"%04d\" s1x=\"%04d\"  s1y=\"%04d\" ii1=\"%d\"/>"
          , px
          , py
          , rotValueAsString
@@ -894,6 +950,7 @@ p_coords_logging(gint32 frameNr, PixelCoords *currCoords,  PixelCoords *currCoor
          , currCoords->py
          , startCoords->px
          , startCoords->py
+         , (int) ii1
          );
       g_free(rotValueAsString);
     }
@@ -912,6 +969,7 @@ p_coords_logging(gint32 frameNr, PixelCoords *currCoords,  PixelCoords *currCoor
                 , center
                 , width
                 , height
+                , valPtr
                 );
   }
 
@@ -924,20 +982,660 @@ p_coords_logging(gint32 frameNr, PixelCoords *currCoords,  PixelCoords *currCoor
 }  /* end p_coords_logging */
 
 
+/* ----------------------------------------
+ * p_computePredictedCoordinate
+ * ----------------------------------------
+ * This prediction calculation assumes the same movement on the weak matching tracked point
+ * as it was detected in the strong matching point. (simple movement without any further transformation) 
+ * TODO:
+ * ideally the prediction shall check for potential scale, rotation and perspective transfromations in case
+ * when more than one strong point is available...
+ */
+static void
+p_computePredictedCoordinate(GapPixelCoords *predictedCoord, GapPixelCoords *strongRef, GapPixelCoords 
*strongTrk, GapPixelCoords *weakRef)
+{
+  predictedCoord->px = weakRef->px + (strongTrk->px - strongRef->px);
+  predictedCoord->py = weakRef->py + (strongTrk->py - strongRef->py);
+
+} /* end p_computePredictedCoordinate */
+
+
+/* -------------------------------------
+ * p_coords_tune_and_logging_perspective
+ * -------------------------------------
+ * log coordinates to stdout
+ * or to move-path controlpoint XML file.
+ * This variant of logging handles the case of perspective transformation
+ * it provides controlpoints for the GIMP-GAP MovePath tool
+ * and for the alternative using the gap_detail_align filter (in combination with the framesModify feature)
+ *
+ * This procedure also fine-tunes the coordinates (by calling gap_locate_FindTuneOffsShortList)
+ * 
+ */
+static void
+p_coords_tune_and_logging_perspective(gint32 frameNr
+                   , PixelCoordsArray *currCoordsArray
+                   , PixelCoordsArray *startCoordsArray
+                   , PixelCoordsArray *prevCoordsArray
+                   , BestIndexes *bestIndexes
+                   , FilterValues *valPtr
+                   , gint32 imageId
+                   , gint32 activeDrawableId
+                   , gint32 referenceLayerId
+                   )
+{
+  gint32  px;
+  gint32  py;
+  gint     width;
+  gint     height;
+  gdouble  w2;
+  gdouble  h2;
+  gint     precision_digits;
+  gchar  *logString;
+
+  GapPerspectiveTransCoords  perspectiveCoords;
+  GapPerspectiveTransCoords *perCoords;
+  GapAlignCoords             gapAlingCoords;
+  GapAlignCoords            *alignCoords;
+  gboolean perCoordsOk;
+  gdouble ttlx;
+  gdouble ttly;
+  gdouble ttrx;
+  gdouble ttry;
+  gdouble tblx;
+  gdouble tbly;
+  gdouble tbrx;
+  gdouble tbry;
+  GapPixelCoords  *trkPtr[4];   /* upto 4 coords in current frame  currCoords[4]; */
+  GapPixelCoords  *refPtr[4];   /* upto 4 coords of first processed (reference) frame  startCoords[4]; */
+  
+  GapPixelCoords  *prevPtr[4];   /* upto 4 coords of previous processed frame */
+  GapPixelCoords  *predPtr[4];   /* upto 4 coords of previous processed frame */
+  GapPixelCoords  *utrkPtr[4];   /* upto 4 coords in current frame  untunedCurrCoords[4]; */
+  GapPixelCoords  untunedCurrCoords[4];
+  
+  gint invalidCount;
+  gint idx;
+    
+  gchar *ttlxValueAsString;
+  gchar *ttlyValueAsString;
+  gchar *ttrxValueAsString;
+  gchar *ttryValueAsString;
+  gchar *tblxValueAsString;
+  gchar *tblyValueAsString;
+  gchar *tbrxValueAsString;
+  gchar *tbryValueAsString;
+
+  gchar *attlxValueAsString;
+  gchar *attlyValueAsString;
+  gchar *attrxValueAsString;
+  gchar *attryValueAsString;
+  gchar *atblxValueAsString;
+  gchar *atblyValueAsString;
+  gchar *atbrxValueAsString;
+  gchar *atbryValueAsString;
+
+  perCoords = &perspectiveCoords;
+  alignCoords = &gapAlingCoords;
+
+  width = gimp_image_width(imageId);
+  height = gimp_image_height(imageId);
+
+  ///////////////////////////////////////////// start tuning
+  
+  if(gap_debug)
+  {
+    printf(" [frameNr:%d] activeDrawableId:%d referenceLayerId:%d enableScaling(Tuning):%s\n"
+          ,(int)frameNr
+          ,(int)activeDrawableId
+          ,(int)referenceLayerId
+          , (valPtr->enableScaling ? "TRUE" : "FALSE")
+          );
+  }
+  
+  if(valPtr->enableScaling)  // TODO have own boolean enableTuning
+  {
+    gboolean                useRefForPredictedCoord;
+    gboolean                isStrong[4];
+    GapLocateTuneOffsElem  *rootShortList[4];
+    gint                    strongCount;
+    gint                    idxStrongOne;
+    gint                    strongIndexes[4];
+    gdouble                 qFactor;
+  
+    /*  TODO find a practical qFactor in the tests..
+     * the qFactor shall eliminate weak matchers (by setting them invalid)
+     * in case there is a clear favorite matching offset available,
+     * but keep more elements (== tune attempts) in case there are more very similar matching candidates.
+     */
+    qFactor = 1.4; // TODO...
+  
+    for(idx=0; idx < 4; idx++)
+    {
+      isStrong[idx]      = FALSE;
+      rootShortList[idx] = NULL;
+      strongIndexes[idx] = 0;
+      
+      gap_geo_copy_src_to_dst_coords(&currCoordsArray->pixCoord[idx]   /* GapPixelCoords *srcCoords*/
+                                    ,&untunedCurrCoords[idx]           /* GapPixelCoords*dstCoords */
+                                    );
+    }
+    
+    invalidCount = 0;
+    strongCount = 0;
+    idxStrongOne = 0;
+    for(idx=0; idx < 4; idx++)
+    {
+      trkPtr[idx] = &currCoordsArray->pixCoord[bestIndexes->bestIdx[idx]];
+      refPtr[idx] = &startCoordsArray->pixCoord[bestIndexes->bestIdx[idx]];
+      prevPtr[idx] = &prevCoordsArray->pixCoord[bestIndexes->bestIdx[idx]];
+      utrkPtr[idx] = &untunedCurrCoords[bestIndexes->bestIdx[idx]];
+    
+      if((trkPtr[idx]->valid != TRUE)
+      || (refPtr[idx]->valid != TRUE))
+      {
+        invalidCount++;
+      }
+      else
+      {
+        rootShortList[idx] = gap_locate_FindTuneOffsShortList(activeDrawableId, 
+            referenceLayerId,
+            refPtr[idx],  //  GapPixelCoords *refCoord, 
+            trkPtr[idx],  //  GapPixelCoords *currCoord, 
+            qFactor       //  gdouble qFactor
+            );
+         isStrong[idx] = gap_locate_check_strong_shortlist(rootShortList[idx]
+                                                          , 1.02  /* nearlySameFactor */  // TODO find 
usable value
+                                                          , 0.1   /* strongRelDiff    */  // TODO find 
usable value
+                                                          );
+         if (isStrong[idx] == TRUE)
+         {
+           // apply tuning offsets of the 1.st list element for strong points.
+           trkPtr[idx]->px = trkPtr[idx]->px + rootShortList[idx]->tuneOffsetX;
+           trkPtr[idx]->py = trkPtr[idx]->py + rootShortList[idx]->tuneOffsetY;
+            
+           strongIndexes[strongCount] = idx;
+           idxStrongOne = strongIndexes[0];
+           strongCount++;
+
+           if(gap_debug)
+           {
+             printf(" [frameNr:%d idx:%d] upx:%d upy:%d (tuned: %d %d) relDiff:%f tuneOffsetXY:(%d %d) 
isSTRONG\n"
+                    ,(int)frameNr
+                    ,(int)idx
+                    ,(int)utrkPtr[idx]->px
+                    ,(int)utrkPtr[idx]->py
+                    ,(int)trkPtr[idx]->px
+                    ,(int)trkPtr[idx]->py
+                    ,(float)rootShortList[idx]->relDiff
+                    ,(int)rootShortList[idx]->tuneOffsetX
+                    ,(int)rootShortList[idx]->tuneOffsetY
+                    );
+           }
+         }
+         else
+         {
+           if(gap_debug)
+           {
+             printf(" [frameNr:%d idx:%d] upx:%d upy:%d (not yet tuned) relDiff:%f tuneOffsetXY:(%d %d) 
isWeak\n"
+                    ,(int)frameNr
+                    ,(int)idx
+                    ,(int)utrkPtr[idx]->px
+                    ,(int)utrkPtr[idx]->py
+                    ,(float)rootShortList[idx]->relDiff
+                    ,(int)rootShortList[idx]->tuneOffsetX
+                    ,(int)rootShortList[idx]->tuneOffsetY
+                    );
+           }
+         }
+    
+      }
+    }
+    
+    
+    if ((strongCount < 4) && (strongCount > 0))
+    {
+      // TODO maybe provide better algortithm in case having 2 or 3 strong points
+      useRefForPredictedCoord = FALSE;
+      if (strongCount > 1)
+      {
+        gdouble thisSqrDistance;
+        gdouble refSqrDistance;
+        gdouble prevSqrDistance;
+        
+        /* calculate distances between 2 strong points for this frame, the previous one and the intial one 
(ref) */
+        thisSqrDistance = gap_geo_calculateSqrDist(trkPtr[strongIndexes[0]],  trkPtr[strongIndexes[1]]);
+        refSqrDistance =  gap_geo_calculateSqrDist(refPtr[strongIndexes[0]],  refPtr[strongIndexes[1]]);
+        prevSqrDistance = gap_geo_calculateSqrDist(prevPtr[strongIndexes[0]], prevPtr[strongIndexes[1]]);
+        
+        /* use the closer one for calculation of the predicted coordinate.
+         * (remarkable different distances may indicate scaled frames (zoom) or perspective transformation 
(camera rotations))
+         */
+        if (fabs(thisSqrDistance - refSqrDistance) <= fabs(thisSqrDistance - prevSqrDistance))
+        {
+          useRefForPredictedCoord = TRUE;
+        }
+      }
+      for(idx=0; idx < 4; idx++)
+      {
+        if (useRefForPredictedCoord)
+        {
+          predPtr[idx] = refPtr[idx]; /* use inital reference coordinate for calcualtion of predicted coords 
*/
+        }
+        else
+        {
+          predPtr[idx] = prevPtr[idx];  /* use coordinates of previous frame for calcualtion of predicted 
coords */
+        }
+      }
+    
+      for(idx=0; idx < 4; idx++)
+      {
+        if (isStrong[idx] != TRUE)
+        {
+           GapPixelCoords predictedCoord;
+           
+           p_computePredictedCoordinate(&predictedCoord,  predPtr[idxStrongOne], trkPtr[idxStrongOne], 
predPtr[idx]);
+           gap_locatePickNearestToPredictedCoordinateFromShortlist(trkPtr[idx], &predictedCoord, 
rootShortList[idx], width, height);
+        
+           if(gap_debug)
+          {
+            printf(" [frameNr:%d idx:%d] upx:%d upy:%d (tuned px:%d py:%d) predictedCoord.px:%d .py:%d 
isWeak "
+                              ,(int)frameNr
+                              ,(int)idx
+                              ,(int)utrkPtr[idx]->px
+                              ,(int)utrkPtr[idx]->py
+                              ,(int)trkPtr[idx]->px
+                              ,(int)trkPtr[idx]->py
+                              ,(int)predictedCoord.px
+                              ,(int)predictedCoord.py
+                              );
+            if (useRefForPredictedCoord)
+            {
+              printf(" useRefForPredictedCoord\n");
+            }
+            else
+            {
+              printf(" usePreviosFrameForPredictedCoord\n");
+            }
+           }
+        
+        
+        }
+      }  
+    }
+    
+    // free short lists
+    for(idx=0; idx < 4; idx++)
+    {
+      gap_locate_freeTuneOffsList(rootShortList[idx]);
+    }
+  }
+
+  ///////////////////////////////////////////// end tuning
+
+  
+  invalidCount = 0;
+  for(idx=0; idx < 4; idx++)
+  {
+    trkPtr[idx] = &currCoordsArray->pixCoord[bestIndexes->bestIdx[idx]];
+    refPtr[idx] = &startCoordsArray->pixCoord[bestIndexes->bestIdx[idx]];
+  
+    if((trkPtr[idx]->valid != TRUE)
+    || (refPtr[idx]->valid != TRUE))
+    {
+      invalidCount++;
+    }
+
+    gap_geo_copy_src_to_dst_coords(trkPtr[idx]                     /* GapPixelCoords *srcCoords*/
+                                  ,&alignCoords->currCoords[idx]   /* GapPixelCoords*dstCoords */
+                                  );
+    gap_geo_copy_src_to_dst_coords(refPtr[idx]                     /* GapPixelCoords *srcCoords*/
+                                  ,&alignCoords->startCoords[idx]  /* GapPixelCoords*dstCoords */
+                                  );
+  }
+
+
+  if(invalidCount > 0)
+  {
+    /* do not record invalid coordinates */
+    return;
+  }
+
+
+ /* calculate absolute coordinates of 4 cornerpoints for the perspective transformation
+  * and convert them to GAP Move Path typical perspective notation relative to width/height
+  */
+  w2 = width / 2.0;
+  h2 = height / 2.0;
+
+  ttlx = 1.0;
+  ttly = 1.0;
+  ttrx = 1.0;
+  ttry = 1.0;
+  tblx = 1.0;
+  tbly = 1.0;
+  tbrx = 1.0;
+  tbry = 1.0;
+
+  perCoords->width = width;
+  perCoords->height = height;
+
+  perCoordsOk = gap_geo_perspective_trans_coords_from_align_coords(-1 /* activeDrawableId */ , alignCoords, 
perCoords);
+  if (perCoordsOk == TRUE)
+  {
+    ttlx = 0 - ((perCoords->x0 - w2) / w2);
+    ttly = 0 - ((perCoords->y0 - h2) / h2);
+
+    ttrx = (perCoords->x1 - w2) / w2;
+    ttry = 0 - ((perCoords->y1 - h2) / h2);
+
+    tblx = 0 - ((perCoords->x2 - w2) / w2);
+    tbly = (perCoords->y2 - h2) / h2;
+
+    tbrx = (perCoords->x3 - w2) / w2;
+    tbry = (perCoords->y3 - h2) / h2;
+  }
+  else
+  {
+    if(gap_debug)
+    {
+      printf("perCoords NOT Ok !");
+    }
+  }
+
+
+
+  logString = NULL;
+
+  precision_digits = 8;
+  
+  /* relative GAP-MovePath typical coordinate values of the perspective transformation */
+  ttlxValueAsString = gap_base_gdouble_to_ascii_string(ttlx, precision_digits);
+  ttlyValueAsString = gap_base_gdouble_to_ascii_string(ttly, precision_digits);
+  ttrxValueAsString = gap_base_gdouble_to_ascii_string(ttrx, precision_digits);
+  ttryValueAsString = gap_base_gdouble_to_ascii_string(ttry, precision_digits);
+  tblxValueAsString = gap_base_gdouble_to_ascii_string(tblx, precision_digits);
+  tblyValueAsString = gap_base_gdouble_to_ascii_string(tbly, precision_digits);
+  tbrxValueAsString = gap_base_gdouble_to_ascii_string(tbrx, precision_digits);
+  tbryValueAsString = gap_base_gdouble_to_ascii_string(tbry, precision_digits);
+
+  /* Absoulute pixel coordinate values of the perspective transformation */
+  attlxValueAsString = gap_base_gdouble_to_ascii_string(perCoords->x0, precision_digits);
+  attlyValueAsString = gap_base_gdouble_to_ascii_string(perCoords->y0, precision_digits);
+  attrxValueAsString = gap_base_gdouble_to_ascii_string(perCoords->x1, precision_digits);
+  attryValueAsString = gap_base_gdouble_to_ascii_string(perCoords->y1, precision_digits);
+  atblxValueAsString = gap_base_gdouble_to_ascii_string(perCoords->x2, precision_digits);
+  atblyValueAsString = gap_base_gdouble_to_ascii_string(perCoords->y2, precision_digits);
+  atbrxValueAsString = gap_base_gdouble_to_ascii_string(perCoords->x3, precision_digits);
+  atbryValueAsString = gap_base_gdouble_to_ascii_string(perCoords->y3, precision_digits);
+
+  /* offsets are top left corner of the resulting new layer size */
+  px = rint(MIN(perCoords->x0, perCoords->x2));
+  py = rint(MIN(perCoords->y0, perCoords->y1));
+
+  if(valPtr->enableScaling)  // TODO have own boolean enableTuning
+  {
+    gchar tunedYN;
+    
+    tunedYN = 'Y';
+    if ((utrkPtr[0]->px == trkPtr[0]->px)
+    &&  (utrkPtr[0]->py == trkPtr[0]->py)
+    &&  (utrkPtr[1]->px == trkPtr[1]->px)
+    &&  (utrkPtr[1]->py == trkPtr[1]->py)
+    &&  (utrkPtr[2]->px == trkPtr[2]->px)
+    &&  (utrkPtr[2]->py == trkPtr[2]->py)
+    &&  (utrkPtr[3]->px == trkPtr[3]->px)
+    &&  (utrkPtr[3]->py == trkPtr[3]->py))
+    {
+      tunedYN = 'N';
+    }
+    
+    /* XML with both tuned and untuned coordinates (u1x,u1y ... for test and analyse purpose) */
+    logString = g_strdup_printf(
+          "    <controlpoint px=\"%04d\" py=\"%04d\" "
+          "ttlx=\"%s\" ttly=\"%s\" ttrx=\"%s\" ttry=\"%s\" "
+          "tblx=\"%s\" tbly=\"%s\" tbrx=\"%s\" tbry=\"%s\" "
+          "attlx=\"%s\" attly=\"%s\" attrx=\"%s\" attry=\"%s\" "
+          "atblx=\"%s\" atbly=\"%s\" atbrx=\"%s\" atbry=\"%s\" "
+          "keyframe_abs=\"%06d\" "
+          "tuned=\"%c\" "
+          "u1x=\"%04d\"  u1y=\"%04d\"  u2x=\"%04d\" u2y=\"%04d\" "
+          "u3x=\"%04d\"  u3y=\"%04d\"  u4x=\"%04d\" u4y=\"%04d\" "
+          "p1x=\"%04d\"  p1y=\"%04d\"  p2x=\"%04d\" p2y=\"%04d\" "
+          "p3x=\"%04d\"  p3y=\"%04d\"  p4x=\"%04d\" p4y=\"%04d\" "
+          "s1x=\"%04d\" s1y=\"%04d\" s2x=\"%04d\" s2y=\"%04d\" "
+          "s3x=\"%04d\" s3y=\"%04d\" s4x=\"%04d\" s4y=\"%04d\" />"
+       , px, py
+       , ttlxValueAsString, ttlyValueAsString, ttrxValueAsString, ttryValueAsString
+       , tblxValueAsString, tblyValueAsString, tbrxValueAsString, tbryValueAsString
+       , attlxValueAsString, attlyValueAsString, attrxValueAsString, attryValueAsString
+       , atblxValueAsString, atblyValueAsString, atbrxValueAsString, atbryValueAsString
+       , frameNr
+       , tunedYN
+       , utrkPtr[0]->px
+       , utrkPtr[0]->py
+       , utrkPtr[1]->px
+       , utrkPtr[1]->py
+       , utrkPtr[2]->px
+       , utrkPtr[2]->py
+       , utrkPtr[3]->px
+       , utrkPtr[3]->py
+       , trkPtr[0]->px
+       , trkPtr[0]->py
+       , trkPtr[1]->px
+       , trkPtr[1]->py
+       , trkPtr[2]->px
+       , trkPtr[2]->py
+       , trkPtr[3]->px
+       , trkPtr[3]->py
+       , refPtr[0]->px
+       , refPtr[0]->py
+       , refPtr[1]->px
+       , refPtr[1]->py
+       , refPtr[2]->px
+       , refPtr[2]->py
+       , refPtr[3]->px
+       , refPtr[3]->py
+       );
+  }
+  else
+  {
+    /* XML without untuned coordinates */
+    logString = g_strdup_printf(
+          "    <controlpoint px=\"%04d\" py=\"%04d\" "
+          "ttlx=\"%s\" ttly=\"%s\" ttrx=\"%s\" ttry=\"%s\" "
+          "tblx=\"%s\" tbly=\"%s\" tbrx=\"%s\" tbry=\"%s\" "
+          "attlx=\"%s\" attly=\"%s\" attrx=\"%s\" attry=\"%s\" "
+          "atblx=\"%s\" atbly=\"%s\" atbrx=\"%s\" atbry=\"%s\" "
+          "keyframe_abs=\"%06d\" "
+          "p1x=\"%04d\"  p1y=\"%04d\"  p2x=\"%04d\" p2y=\"%04d\" "
+          "p3x=\"%04d\"  p3y=\"%04d\"  p4x=\"%04d\" p4y=\"%04d\" "
+          "s1x=\"%04d\" s1y=\"%04d\" s2x=\"%04d\" s2y=\"%04d\" "
+          "s3x=\"%04d\" s3y=\"%04d\" s4x=\"%04d\" s4y=\"%04d\" />"
+       , px, py
+       , ttlxValueAsString, ttlyValueAsString, ttrxValueAsString, ttryValueAsString
+       , tblxValueAsString, tblyValueAsString, tbrxValueAsString, tbryValueAsString
+       , attlxValueAsString, attlyValueAsString, attrxValueAsString, attryValueAsString
+       , atblxValueAsString, atblyValueAsString, atbrxValueAsString, atbryValueAsString
+       , frameNr
+       , trkPtr[0]->px
+       , trkPtr[0]->py
+       , trkPtr[1]->px
+       , trkPtr[1]->py
+       , trkPtr[2]->px
+       , trkPtr[2]->py
+       , trkPtr[3]->px
+       , trkPtr[3]->py
+       , refPtr[0]->px
+       , refPtr[0]->py
+       , refPtr[1]->px
+       , refPtr[1]->py
+       , refPtr[2]->px
+       , refPtr[2]->py
+       , refPtr[3]->px
+       , refPtr[3]->py
+       );
+  }
+  
+  g_free(ttlxValueAsString);
+  g_free(ttlyValueAsString);
+  g_free(ttrxValueAsString);
+  g_free(ttryValueAsString);
+  g_free(tblxValueAsString);
+  g_free(tblyValueAsString);
+  g_free(tbrxValueAsString);
+  g_free(tbryValueAsString);
+
+  g_free(attlxValueAsString);
+  g_free(attlyValueAsString);
+  g_free(attrxValueAsString);
+  g_free(attryValueAsString);
+  g_free(atblxValueAsString);
+  g_free(atblyValueAsString);
+  g_free(atbrxValueAsString);
+  g_free(atbryValueAsString);
+
+  if ((valPtr->moveLogFile[0] == '\0')
+  ||  (valPtr->moveLogFile[0] == '-'))
+  {
+    printf("%s\n", logString);
+  }
+  else
+  {
+    p_log_to_file(&valPtr->moveLogFile[0], logString
+                , frameNr
+                , FALSE  /* center */
+                , width
+                , height
+                , valPtr
+                );
+  }
+
+  if (logString)
+  {
+    g_free(logString);
+  }
+
+
+}  /* end p_coords_tune_and_logging_perspective */
+  
+
+/* --------------------------------------------------
+ * p_pickNearestToCorners
+ * --------------------------------------------------
+ * pick the 4 indexes of reference coordinate point that are the nearest 
+ * to the 4 corners.
+ * 
+ * Notes on the coordinate status:
+ *   Points with status != 0 are ignored
+ *                           (-1 indicates invalid coordinates
+ *                           (+1 indicates already picked coordinates)
+ *
+ *   The picked reference coordinates are marked with status 1 in this procedure
+ */
+static void
+p_pickNearestToCorners(BestIndexes *bestIndexes, gint32 width, gint32 height
+     , PixelCoordsArray *startCoordsArray)
+{
+  gint    idn;
+  gint    idcPick;  /* index of the selected corner where 0 = topLeft, 1 = TopRight, 2 = BottmLeft, 3 = 
BottomRight */
+  gint    pickIdx;
+  
+  gdouble   minSqDist;
+  gdouble   minSqDistCorner[4];
+  gboolean  cornerSelected[4];
+  gint      cornerSelectCount;
+
+
+
+  /* pick 4 coordinate near ideally near to the 4 corners in loop for idn = 0 to 3 */
+
+  cornerSelected[0] = FALSE;
+  cornerSelected[1] = FALSE;
+  cornerSelected[2] = FALSE;
+  cornerSelected[3] = FALSE;
+  cornerSelectCount = 0;
+
+  
+  for(idn = 0; idn < 4; idn++)
+  {
+    gint    idx;
+  
+    minSqDist = (width + height) * (width + height);
+    minSqDistCorner[0] = minSqDist;
+    minSqDistCorner[1] = minSqDist;
+    minSqDistCorner[2] = minSqDist;
+    minSqDistCorner[3] = minSqDist;
+  
+    idcPick = -1;
+    pickIdx = -1;  /* indicates: "could not pick a valid point" */
+    for(idx=0; idx < GAP_ALIGN_COORDS_MAX; idx++)
+    {
+      gdouble sqDist[4];
+      gint idc;
+
+      if(startCoordsArray->pixCoord[idx].status != 0)
+      {
+        continue;  /* Skip already selected and unusable coorinates */
+      }
+
+    
+      /* square distance to all 4 corners */
+      sqDist[0] = gap_geo_calculateSqrDistX2Y2(&startCoordsArray->pixCoord[idx], 0.0, 0.0);
+      sqDist[1] = gap_geo_calculateSqrDistX2Y2(&startCoordsArray->pixCoord[idx], (gdouble)width, 0.0);
+      sqDist[2] = gap_geo_calculateSqrDistX2Y2(&startCoordsArray->pixCoord[idx], 0.0, (gdouble)height);
+      sqDist[3] = gap_geo_calculateSqrDistX2Y2(&startCoordsArray->pixCoord[idx], (gdouble)width, 
(gdouble)height);
+    
+      for(idc = 0; idc < 4; idc++)
+      {
+        if (cornerSelected[idc] == TRUE)
+        {
+          continue; /* skip already picked corners */
+        }
+        if (sqDist[idc] < minSqDistCorner[idc])
+        {
+          minSqDistCorner[idc] = sqDist[idc];
+          if (minSqDistCorner[idc] < minSqDist)
+          {
+            minSqDist = minSqDistCorner[idc];
+            idcPick = idc;
+            pickIdx = idx;
+          }
+        }
+      }
+    }      /* end for idx loop over all available coordinates */
+    
+    if (idcPick >= 0)
+    {
+      cornerSelectCount++;
+      cornerSelected[idcPick] = TRUE;
+      bestIndexes->bestIdx[idcPick] = pickIdx;
+      startCoordsArray->pixCoord[pickIdx].status = 1; /* mark this coord as already selected */
+    }
+  }
+
+
+}  /* end p_pickNearestToCorners */
+
+
 /* ----------------------------
  * p_select_best_coords
  * ----------------------------
- * pick the two best matching coordinates.
+ * pick 1, 2 or 4 best matching coordinates,
+ * depending on valPtr->numPointsSelect that determines
+ * the type of wanted camera shake compensation.
+ *   where 4 is 4point perspective mode
+ *   where 2 is 2point Scale/rotate and Move mode
+ *   where 1 is 1point simple Move mode
  *
  * weight depends on average colordiff (while locating the coordinate)
  * and distance (the longer the better for good precision 
  *  while calcualting rotation angle and scaling)
  */
 static void
-p_select_best_coords(gint32 frameNr, PixelCoordsArray *currCoordsArray
+p_select_best_coords(gint32 imageId, gint32 frameNr, PixelCoordsArray *currCoordsArray
   , PixelCoordsArray *startCoordsArray, FilterValues *valPtr
-  , gint32 *bestIdx1
-  , gint32 *bestIdx2
+  , BestIndexes *bestIndexes, FrameHistInfo *frameHistInfo 
   )
 {
 #define MAX_AVG_LOOPS 4
@@ -962,11 +1660,13 @@ p_select_best_coords(gint32 frameNr, PixelCoordsArray *currCoordsArray
   gdouble avgOffsX1;
   gdouble avgOffsY1;
   gint32  pixelMovementTolerance;
-  PixelCoords  *currCoords;
-  PixelCoords  *startCoords;
+  GapPixelCoords  *currCoords;
+  GapPixelCoords  *startCoords;
   gint32 moveOffsetX;
   gint32 moveOffsetY;
   gint32 validPointsCount;
+  gint32 width;
+  gint32 height;
         
   maxWeight = 0.0;
   maxSoloQuality = 0.0;
@@ -975,6 +1675,121 @@ p_select_best_coords(gint32 frameNr, PixelCoordsArray *currCoordsArray
   pickIdx1 = 0;
   pickIdx2 = 1;
   numPoints = MIN(currCoordsArray->numberOfCoords , startCoordsArray->numberOfCoords);
+  
+  width = gimp_image_width(imageId);
+  height = gimp_image_height(imageId);
+
+  /* -------------------- 4 point perspective mode --------------- */
+  if(valPtr->numPointsSelect == 4)
+  {
+     gint idx;
+     
+     /* for 4point perspective mode there are other criteria "whats the best set of points"
+      * p0 shall be the point with best quality near the upper left corner
+      * p1 shall be the point with best quality near the upper right corner
+      * p2 shall be the point with best quality near the lower left corner
+      * p3 shall be the point with best quality near the lower right corner
+      *
+      * TODO
+      *   .. think about improved pick method that prefers points with higher quality
+      *     .. and still provides 4 points ideally one near each corner.
+      */
+      for(idx=0; idx < numPoints; idx++)
+      {
+        startCoordsArray->pixCoord[idx].status = -1;  /* indicates unusable pair */
+        if((startCoordsArray->pixCoord[idx].valid == TRUE)
+        && (currCoordsArray->pixCoord[idx].valid == TRUE))
+        {
+          startCoordsArray->pixCoord[idx].status = 0;  /* indicates selectable pair */
+        }
+      }
+      
+      p_pickNearestToCorners(bestIndexes, width, height, startCoordsArray);
+      
+      return;
+
+  }
+
+  /* -------------------- 1 point simple move mode --------------- */
+  if(valPtr->numPointsSelect == 1)
+  {
+    gdouble bestQuality = 0.0;
+    
+    /* standard method: 
+     * Singlepoint mode picks the point with best quality in case there are more points available.
+     */
+    for(idx1 = 0; idx1 < numPoints; idx1++)
+    {
+      quality = p_getPixelCoordsQuality(&currCoordsArray->pixCoord[idx1]);
+      if (quality > bestQuality)
+      {
+        bestQuality = quality;
+        pickIdx1 = idx1;
+      }
+    }
+    
+    /* optional extended method: 
+     * In case there are more points with top quality (>= 99% compared with best quality)
+     * prefere the point with the same index that was picked in the previous handled frame when possible.
+     */
+    if(valPtr->enableScaling)  // TODO have own boolean enableTuning
+    {
+      gdouble nearlyBestQuality;
+      
+      nearlyBestQuality = bestQuality * 0.997;
+      pickIdx1 = -1;
+      for(idx1 = 0; idx1 < numPoints; idx1++)
+      {
+        currCoords = &currCoordsArray->pixCoord[idx1];
+        quality = p_getPixelCoordsQuality(currCoords);
+      
+        if(gap_debug)
+        {
+          printf("frameNr:%d  idx1:%d [%d, %d] histBestIdx:%d quality:%f nearlyBestQuality:%f 
bestQuality:%f\n"
+            , (int)frameNr
+            , (int)idx1
+            , (int)currCoords->px
+            , (int)currCoords->py
+            , (int)frameHistInfo->bestIdx[0]
+            , (float)quality
+            , (float)nearlyBestQuality
+            , (float)bestQuality
+            );
+        }
+      
+        if ((quality >= nearlyBestQuality) && (frameNr > 2))
+        {
+          if(idx1 == frameHistInfo->bestIdx[0])
+          {
+            pickIdx1 = idx1;
+          }
+        }
+
+        /* in case there are more points with bestQuality pick only the 1st of them */
+        if ((quality == bestQuality) && (pickIdx1 < 0))
+        {
+          pickIdx1 = idx1;
+        }
+      }
+    }
+    
+    bestIndexes->bestIdx[0] = MAX(0, pickIdx1);
+    bestIndexes->bestIdx[1] = -1;   // not used in 1point compensation
+    bestIndexes->bestIdx[2] = -1;   // not used in 1point compensation
+    bestIndexes->bestIdx[3] = -1;   // not used in 1point compensation
+
+    if(gap_debug)
+    {
+        printf("frameNr:%d  picked idx:%d  histBestIdx:%d  bestQuality:%f\n"
+          , (int)frameNr
+          , (int)bestIndexes->bestIdx[0]
+          , (int)frameHistInfo->bestIdx[0]
+          , (float)bestQuality
+          );
+    }
+  
+    return;
+  }
 
   /* calculate average offsets (movemnet of x and y axis) 
    * in the 2nd and further outer loops eliminate extreme values
@@ -982,6 +1797,9 @@ p_select_best_coords(gint32 frameNr, PixelCoordsArray *currCoordsArray
   pixelMovementTolerance = MAX(5, valPtr->targetMoveRadius / 8);
   avgOffsX = 0.0;
   avgOffsY = 0.0;
+  avgOffsX1 = 0.0;
+  avgOffsY1 = 0.0;
+  
   for(idx2 = 0; idx2 < MAX_AVG_LOOPS; idx2++)
   {
     sumOffsX = 0;
@@ -1146,7 +1964,7 @@ p_select_best_coords(gint32 frameNr, PixelCoordsArray *currCoordsArray
 
          quality = soloQuality * p_getPixelCoordsQuality(&currCoordsArray->pixCoord[idx2])
                * p_getPixelCoordsQuality(&startCoordsArray->pixCoord[idx2]);
-         sqrDistance = p_calculateSqrDist(&currCoordsArray->pixCoord[idx1], 
&currCoordsArray->pixCoord[idx2]);
+         sqrDistance = gap_geo_calculateSqrDist(&currCoordsArray->pixCoord[idx1], 
&currCoordsArray->pixCoord[idx2]);
        
          /* operate with the square distance for performance reason
           * therfore also use square quality to compensate..
@@ -1168,10 +1986,12 @@ p_select_best_coords(gint32 frameNr, PixelCoordsArray *currCoordsArray
   }
   
 
-  *bestIdx1 = pickIdx1;
-  *bestIdx2 = pickIdx2;
+  bestIndexes->bestIdx[0] = pickIdx1;
+  bestIndexes->bestIdx[1] = pickIdx2;
+  bestIndexes->bestIdx[2] = -1;   // not used in 2point compensation
+  bestIndexes->bestIdx[3] = -1;   // not used in 2point compensation
 
-  // if(gap_debug)
+  if(gap_debug)
   {
     for(idx1 = 0; idx1 < numPoints; idx1++)
     {
@@ -1235,42 +2055,72 @@ p_select_best_coords(gint32 frameNr, PixelCoordsArray *currCoordsArray
 /* ----------------------------
  * p_selective_coords_logging
  * ----------------------------
- * log the best 2 coordinates to stdout
+ * log the best selected coordinates to stdout
  * or to move-path controlpoint XML file.
+ * Further store the best selected information in the frame history.
  *
- * weight depends on average colordiff (while locating the coordinate)
- * and distance (the longer the better for good precision 
- *  while calcualting rotation angle and scaling)
+ * Note that the controlpoint XML file has additional information
+ * that is not relevant for the move-path tool but is used
+ * in the detail-align tool (that is an alternative option
+ * stabilze video frames based on the tracked XML data)
  */
-static gint32
-p_selective_coords_logging(gint32 frameNr 
-  , PixelCoordsArray *currCoordsArray
-  , PixelCoordsArray *startCoordsArray
-  , FilterValues *valPtr
-  , gint32 imageId
-  )
+static gint32  
+p_selective_coords_logging(FrameHistInfo *frameHistInfo 
+                  , PixelCoordsArray *currCoordsArray
+                  , PixelCoordsArray *startCoordsArray
+                  , PixelCoordsArray *prevCoordsArray
+                  , FilterValues *valPtr
+                  , gint32 imageId
+                  , gint32 activeDrawableId
+                  , gint32 referenceLayerId
+                 )
 {
-  gint32 bestIdx1;
-  gint32 bestIdx2;
+  BestIndexes bestIndexes;
+  gint32 frameNr;
 
-  p_select_best_coords(frameNr
+  frameNr = frameHistInfo->frameNr;
+  p_select_best_coords(imageId, frameNr
   , currCoordsArray
   , startCoordsArray
   , valPtr
-  , &bestIdx1
-  , &bestIdx2
+  , &bestIndexes
+  , frameHistInfo
   );
 
-  p_coords_logging(frameNr
-                  , &currCoordsArray->pixCoord[bestIdx1]
-                  , &currCoordsArray->pixCoord[bestIdx2]
-                  , &startCoordsArray->pixCoord[bestIdx1]
-                  , &startCoordsArray->pixCoord[bestIdx2]
+  if(valPtr->numPointsSelect == 4)
+  {
+    /* perspective logging handles only the 4point variant */
+    p_coords_tune_and_logging_perspective(frameNr
+                                ,currCoordsArray
+                                ,startCoordsArray
+                                , prevCoordsArray
+                                ,&bestIndexes
+                                , valPtr
+                                , imageId
+                                , activeDrawableId
+                                , referenceLayerId
+                                );
+  }
+  else
+  {
+    p_coords_logging(frameNr
+                  , &currCoordsArray->pixCoord[bestIndexes.bestIdx[0]]
+                  , &currCoordsArray->pixCoord[bestIndexes.bestIdx[1]]
+                  , &startCoordsArray->pixCoord[bestIndexes.bestIdx[0]]
+                  , &startCoordsArray->pixCoord[bestIndexes.bestIdx[1]]
                   , valPtr
                   , imageId
+                  , &startCoordsArray->pixCoord[0]
+                  , bestIndexes.bestIdx[0]
+                  , bestIndexes.bestIdx[1]
                   );
-  
-  return (bestIdx1);
+  }
+  frameHistInfo->bestIdx[0] = bestIndexes.bestIdx[0];
+  frameHistInfo->bestIdx[1] = bestIndexes.bestIdx[1];
+  frameHistInfo->bestIdx[2] = bestIndexes.bestIdx[2];
+  frameHistInfo->bestIdx[3] = bestIndexes.bestIdx[3];
+
+  return (bestIndexes.bestIdx[0]);
   
 }  /* end p_selective_coords_logging */
 
@@ -1318,10 +2168,11 @@ p_parse_frame_nr_from_layer_name(gint32 layerId)
  * -------------------------------
  */
 static void
-p_get_frameHistInfo(FrameHistInfo *frameHistInfo)
+p_get_frameHistInfo(FrameHistInfo *frameHistInfo, gint32 imageId)
 {
+  GimpParasite  *l_parasite;
   int l_len;
-  PixelCoords *startCoords;
+  GapPixelCoords *startCoords;
   
 
   frameHistInfo->workImageId = -1;
@@ -1329,51 +2180,66 @@ p_get_frameHistInfo(FrameHistInfo *frameHistInfo)
   frameHistInfo->trackedFramesCount = 0;
   frameHistInfo->lostTraceCount = 0;
   frameHistInfo->startCoordsArray.numberOfCoords = 0;
+  frameHistInfo->bestIdx[0] = -1;
+  frameHistInfo->bestIdx[1] = -1;
+  frameHistInfo->bestIdx[2] = -1;
+  frameHistInfo->bestIdx[3] = -1;
 
   startCoords = &frameHistInfo->startCoordsArray.pixCoord[0];
   startCoords->valid = FALSE;
   startCoords->px = 0;
   startCoords->py = 0;
 
-  l_len = gimp_get_data_size (GAP_DETAIL_FRAME_HISTORY_INFO);
-
-  if(gap_debug)
-  {
-    printf("p_get_frameHistInfo: %s len:%d sizeof(FrameHistInfo):%d\n"
-       , GAP_DETAIL_FRAME_HISTORY_INFO
-       , (int)l_len
-       , (int)sizeof(FrameHistInfo)
-       );
-  }
-
 
-  if (l_len == sizeof(FrameHistInfo))
+  l_parasite = gimp_image_parasite_find(imageId, GAP_DETAIL_FRAME_HISTORY_INFO);
+  if(l_parasite)
   {
-
-    gimp_get_data(GAP_DETAIL_FRAME_HISTORY_INFO, frameHistInfo);
-
-    //if(gap_debug)
+    l_len = l_parasite->size;
+      
+    
+    if(gap_debug)
     {
-      PixelCoords *prevCoords;
+        printf("p_get_frameHistInfo: %s len:%d sizeof(FrameHistInfo):%d\n"
+           , GAP_DETAIL_FRAME_HISTORY_INFO
+           , (int)l_len
+           , (int)sizeof(FrameHistInfo)
+           );
+    }
+    
+    
+    if (l_len == sizeof(FrameHistInfo))
+    {
+      //// gimp_get_data(GAP_DETAIL_FRAME_HISTORY_INFO, frameHistInfo);
       
-      startCoords = &frameHistInfo->startCoordsArray.pixCoord[0];
-      prevCoords  = &frameHistInfo->prevCoordsArray.pixCoord[0];
-
-      printf("p_get_frameHistInfo: %s  frameNr:%d px:%d py:%d valid:%d\n"
-             "                     prevPx:%d prevPy:%d prevValid:%d lostTraceCount:%d  
trackedFramesCount:%d\n"
-        , GAP_DETAIL_FRAME_HISTORY_INFO
-        , (int)frameHistInfo->frameNr
-        , (int)startCoords->px
-        , (int)startCoords->py
-        , (int)startCoords->valid
-        , (int)prevCoords->px
-        , (int)prevCoords->py
-        , (int)prevCoords->valid
-        , (int)frameHistInfo->lostTraceCount
-        , (int)frameHistInfo->trackedFramesCount
-        );
+      /* copy (uchar) data from parasite to frameHistInfo (structure) */
+      memcpy(frameHistInfo, l_parasite->data, l_parasite->size);
+    
+      if(gap_debug)
+      {
+        GapPixelCoords *prevCoords;
+        
+        startCoords = &frameHistInfo->startCoordsArray.pixCoord[0];
+        prevCoords  = &frameHistInfo->prevCoordsArray.pixCoord[0];
+    
+        printf("p_get_frameHistInfo: %s  frameNr:%d px:%d py:%d valid:%d\n"
+               "                     prevPx:%d prevPy:%d prevValid:%d lostTraceCount:%d  
trackedFramesCount:%d\n"
+          , GAP_DETAIL_FRAME_HISTORY_INFO
+          , (int)frameHistInfo->frameNr
+          , (int)startCoords->px
+          , (int)startCoords->py
+          , (int)startCoords->valid
+          , (int)prevCoords->px
+          , (int)prevCoords->py
+          , (int)prevCoords->valid
+          , (int)frameHistInfo->lostTraceCount
+          , (int)frameHistInfo->trackedFramesCount
+          );
+      }
+    
     }
 
+    gimp_parasite_free(l_parasite);
+    
   }
 
 }  /* end p_get_frameHistInfo */
@@ -1382,16 +2248,18 @@ p_get_frameHistInfo(FrameHistInfo *frameHistInfo)
 /* -------------------------------
  * p_set_frameHistInfo
  * -------------------------------
- * store frame history information
- * (for the next run in the same gimp session)
+ * store frame history information as temporary image parasite data
+ * (for the next run with the same image in the same gimp session)
  */
 static void
-p_set_frameHistInfo(FrameHistInfo *frameHistInfo)
+p_set_frameHistInfo(FrameHistInfo *frameHistInfo, gint32 imageId)
 {
+  GimpParasite  *l_parasite;
+
   if(gap_debug)
   {
-      PixelCoords *startCoords;
-      PixelCoords *prevCoords;
+      GapPixelCoords *startCoords;
+      GapPixelCoords *prevCoords;
       
       startCoords = &frameHistInfo->startCoordsArray.pixCoord[0];
       prevCoords  = &frameHistInfo->prevCoordsArray.pixCoord[0];
@@ -1409,10 +2277,20 @@ p_set_frameHistInfo(FrameHistInfo *frameHistInfo)
         );
   }
 
-  gimp_set_data(GAP_DETAIL_FRAME_HISTORY_INFO, frameHistInfo, sizeof(FrameHistInfo));
+  /* attach a parasite to store frame histroy information for detail tracking */
+  l_parasite = gimp_parasite_new(GAP_DETAIL_FRAME_HISTORY_INFO
+                                 ,0  /* GIMP_PARASITE_PERSISTENT  0 for non persistent */
+                                 ,sizeof(FrameHistInfo)  /* parasite->size */
+                                 ,frameHistInfo          /* parasite->data */
+                                 );
 
-}  /* end p_set_frameHistInfo */
+  if(l_parasite)
+  {
+    gimp_image_parasite_attach(imageId, l_parasite);
+    gimp_parasite_free(l_parasite);
+  }
 
+}  /* end p_set_frameHistInfo */
 
 
 /* -------------------------------
@@ -1427,8 +2305,10 @@ p_set_frameHistInfo(FrameHistInfo *frameHistInfo)
  * if setGuides is TRUE
  * then set guide lines crossing at the target coords[guideIdx] for better visualisation.
  *  (note that path vectors will be not visible in case the path contains just one single point)
+ *
+  returns the vectorsId
  */
-static void
+static gint32
 p_set_n_vector_points(gint32 imageId, PixelCoordsArray *targetCoordsArray, gchar *vectorsName
    , gboolean setVisible, gboolean setGuides, gint32 guideIdx)
 {
@@ -1441,7 +2321,7 @@ p_set_n_vector_points(gint32 imageId, PixelCoordsArray *targetCoordsArray, gchar
   gint      l_idx;
   gboolean  closed;
   GimpVectorsStrokeType type;
-  PixelCoords *targetCoords;
+  GapPixelCoords *targetCoords;
   
  
   showGuideIdx = CLAMP(guideIdx, 0, targetCoordsArray->numberOfCoords -1);
@@ -1454,38 +2334,40 @@ p_set_n_vector_points(gint32 imageId, PixelCoordsArray *targetCoordsArray, gchar
   }
 
 
-  //if(gap_debug)
-  if(setGuides)
+  if(gap_debug)
   {
-    printf("\np_set_n_vector_points vectorsName:%s\n  numberOfCoords:%d  guideIdx:%d showGuideIdx:%d\n"
+    if(setGuides)
+    {
+      printf("\np_set_n_vector_points vectorsName:%s\n  numberOfCoords:%d  guideIdx:%d showGuideIdx:%d\n"
         , vectorsName
         , (int)targetCoordsArray->numberOfCoords
         , (int)guideIdx
         , (int)showGuideIdx
         );
-    for(l_idx = 0; l_idx < targetCoordsArray->numberOfCoords; l_idx++)
-    {
-      gdouble pdx;
-      gdouble pdy;
-      targetCoords  = &targetCoordsArray->pixCoord[l_idx];
-      if (targetCoords->valid)
+      for(l_idx = 0; l_idx < targetCoordsArray->numberOfCoords; l_idx++)
       {
-         pdx = targetCoords->px;
-         pdy = targetCoords->py;
-      }
-      else
-      {
-         pdx = 0;
-         pdy = 0;
-      }
+        gdouble pdx;
+        gdouble pdy;
+        targetCoords  = &targetCoordsArray->pixCoord[l_idx];
+        if (targetCoords->valid)
+        {
+           pdx = targetCoords->px;
+           pdy = targetCoords->py;
+        }
+        else
+        {
+           pdx = 0;
+           pdy = 0;
+        }
       
-      printf("pdx[%d] : %.2f  pdy[%d] : %.2f\n"
-        , l_idx
-        , (float)pdx
-        , l_idx
-        , (float)pdy
-        );
+        printf("pdx[%d] : %.2f  pdy[%d] : %.2f\n"
+          , l_idx
+          , (float)pdx
+          , l_idx
+          , (float)pdy
+          );
     
+      }
     }
   }
 
@@ -1559,14 +2441,190 @@ p_set_n_vector_points(gint32 imageId, PixelCoordsArray *targetCoordsArray, gchar
     g_free(points);
     
     gimp_image_insert_vectors(imageId, vectorsId, -1, 0);
-    gimp_vectors_set_visible(vectorsId, setVisible);
+    gimp_item_set_visible(vectorsId, setVisible);
 
   }
 
+  return (vectorsId);
+  
 }  /* end p_set_n_vector_points */
 
 
 
+
+/* --------------------------------------------------
+ * p_set_debugCoords_from_IntersectionPoints
+ * --------------------------------------------------
+ * caclulate intersection points of the specified debugLine with the 2 specified borderLines
+ * and set the 2 resulting intersection coordinates rounded to pixel coordinates in the 
+ *  Output debugCoordsArray
+ *   The resulting output builds the extended debug line connecting the 2 borderLines
+ */
+static void
+p_set_debugCoords_from_IntersectionPoints(gint32 frameNr, gchar *vectorName
+  , PixelCoordsArray *debugCoordsArray
+  , GapLineDescriptionConsts *debugLine
+  , GapLineDescriptionConsts *aBorderLine, GapLineDescriptionConsts *bBorderLine)
+{
+  GapDoubleCoords aInterPt;
+  GapDoubleCoords bInterPt;
+
+  gap_geo_line_intersection(aBorderLine, debugLine,  &aInterPt);
+  gap_geo_line_intersection(bBorderLine, debugLine,  &bInterPt);
+
+
+  debugCoordsArray->numberOfCoords = 2;
+  
+  debugCoordsArray->pixCoord[0].valid = aInterPt.valid;
+  debugCoordsArray->pixCoord[0].px = rint(aInterPt.x);
+  debugCoordsArray->pixCoord[0].py = rint(aInterPt.y);
+  
+  debugCoordsArray->pixCoord[1].valid = bInterPt.valid;
+  debugCoordsArray->pixCoord[1].px = rint(bInterPt.x);
+  debugCoordsArray->pixCoord[1].py = rint(bInterPt.y);
+  
+  if(gap_debug)
+  {
+    printf("frameNr: %d  vectorName:%s p[0] x:%.3f y:%.3f  p[1] x:%.3f y:%.3f\n"
+      , (int)frameNr
+      , vectorName
+      , (float)aInterPt.x
+      , (float)aInterPt.y
+      , (float)bInterPt.x
+      , (float)bInterPt.y
+      );
+  }
+
+}  /* end p_set_debugCoords_from_IntersectionPoints */
+
+
+
+/* --------------------------------------------------
+ * p_set_debug_intersection_vectors
+ * --------------------------------------------------
+ * 
+ */
+static void
+p_set_debug_intersection_vectors(gint32 frameNr, gint32 imageId, gchar *vectorBasename
+    , PixelCoordsArray *pixelCoordsArray, BestIndexes *bestIndexes)
+{
+  PixelCoordsArray debugCoordsArray;
+  GapLineDescriptionConsts upperBorderLine;
+  GapLineDescriptionConsts lowerBorderLine;
+  GapLineDescriptionConsts leftBorderLine;
+  GapLineDescriptionConsts rightBorderLine;
+  GapLineDescriptionConsts debugLine;
+  GapPixelCoords  *cordPtr[4];   /* upto 4 coords of first processed (reference) frame  startCoords[4]; */
+  gint32 width;
+  gint32 height;
+  gint   idx;
+  gchar *vectorName;
+
+
+  width = gimp_image_width(imageId);
+  height = gimp_image_height(imageId);
+
+
+  for(idx=0; idx < 4; idx++)
+  {
+    cordPtr[idx] = NULL;
+    if(bestIndexes->bestIdx[0] >= 0)
+    {
+      cordPtr[idx] = &pixelCoordsArray->pixCoord[bestIndexes->bestIdx[idx]];
+    }
+    else
+    {
+      return;  /* stop in case invalid points are detected */
+    }
+  }
+
+  /* calculate line description for the drawable border lines */
+  gap_geo_line_description_from_2Points(0,     0        /* x1,y1 */
+                                       ,width, 0        /* x2,y2 */ 
+                                       ,&upperBorderLine);
+  gap_geo_line_description_from_2Points(0,     height   /* x1,y1 */
+                                       ,width, height   /* x2,y2 */
+                                       ,&lowerBorderLine);
+  gap_geo_line_description_from_2Points(0,     0        /* x1,y1 */
+                                       ,0,     height   /* x2,y2 */ 
+                                       ,&leftBorderLine);
+  gap_geo_line_description_from_2Points(width, 0        /* x1,y1 */
+                                       ,width, height   /* x2,y2 */
+                                       ,&rightBorderLine);
+
+  
+  /* debug line V1 from p0 to p2 */
+  vectorName = g_strdup_printf("%s%s", vectorBasename, "V1");
+  gap_geo_line_description_from_2GapPixelCoords(cordPtr[0], cordPtr[2], &debugLine);
+  p_set_debugCoords_from_IntersectionPoints(frameNr, vectorName, &debugCoordsArray, &debugLine
+      , &upperBorderLine
+      , &lowerBorderLine
+      );
+  p_set_n_vector_points(imageId, &debugCoordsArray, vectorName, TRUE, FALSE, 0);
+  g_free(vectorName);
+  
+  /* debug line V2 from p1 to p3 */
+  vectorName = g_strdup_printf("%s%s", vectorBasename, "V2");
+  gap_geo_line_description_from_2GapPixelCoords(cordPtr[1], cordPtr[3], &debugLine);
+  p_set_debugCoords_from_IntersectionPoints(frameNr, vectorName, &debugCoordsArray, &debugLine
+      , &upperBorderLine
+      , &lowerBorderLine
+      );
+  p_set_n_vector_points(imageId, &debugCoordsArray, vectorName, TRUE, FALSE, 0);
+  g_free(vectorName);
+
+
+
+  /* debug line H1 from p0 to p1 */
+  vectorName = g_strdup_printf("%s%s", vectorBasename, "H1");
+  gap_geo_line_description_from_2GapPixelCoords(cordPtr[0], cordPtr[1], &debugLine);
+  p_set_debugCoords_from_IntersectionPoints(frameNr, vectorName, &debugCoordsArray, &debugLine
+      , &leftBorderLine
+      , &rightBorderLine
+      );
+  p_set_n_vector_points(imageId, &debugCoordsArray, vectorName, TRUE, FALSE, 0);
+  g_free(vectorName);
+
+  /* debug line H1 from p2 to p3 */
+  vectorName = g_strdup_printf("%s%s", vectorBasename, "H2");
+  gap_geo_line_description_from_2GapPixelCoords(cordPtr[2], cordPtr[3], &debugLine);
+  p_set_debugCoords_from_IntersectionPoints(frameNr, vectorName, &debugCoordsArray, &debugLine
+      , &leftBorderLine
+      , &rightBorderLine
+      );
+  p_set_n_vector_points(imageId, &debugCoordsArray, vectorName, TRUE, FALSE, 0);
+  g_free(vectorName);
+
+
+
+}  /* end p_set_debug_intersection_vectors */
+
+
+/* --------------------------------------------------
+ * p_set_debug_vectors
+ * --------------------------------------------------
+ *
+ * set vectors for the lines (both reference and tracking points)
+ * extended to the border intersection points.
+ * This feature is for analyse purpose only
+ * 
+ */
+static void
+p_set_debug_vectors(gint32 frameNr, gint32 imageId
+  , PixelCoordsArray *targetCoordsArray
+  , PixelCoordsArray *startCoordsArray
+  , BestIndexes *bestIndexes)
+{
+  p_set_debug_intersection_vectors(frameNr, imageId, "Ref", startCoordsArray, bestIndexes);
+  p_set_debug_intersection_vectors(frameNr, imageId, "Trk", targetCoordsArray, bestIndexes);
+
+
+}  /* end p_set_debug_vectors */
+
+
+
+
+
 /* -----------------------------------
  * gap_track_detail_on_top_layers
  * -----------------------------------
@@ -1611,11 +2669,12 @@ gap_track_detail_on_top_layers(gint32 imageId, gboolean doProgress, FilterValues
   gchar       *l_extension;
   gboolean     isTrackingToFrameImage;
   gint32       bestIdx1;
+  BestIndexes  bestIndexes;
   
   
   
 
-  //if(gap_debug)
+  if(gap_debug)
   {
       printf("\ngap_track_detail_on_top_layers: START\n"
              "  numPointsSelect:%d refShapeRadius:%d targetMoveRadius:%d locateColordiff:%.4f\n"
@@ -1674,7 +2733,7 @@ gap_track_detail_on_top_layers(gint32 imageId, gboolean doProgress, FilterValues
   p_capture_n_vector_points(imageId, &currCoordsArray, NUMBER_OF_COORDS, 
VECTORS_NAME_START_REFERENCE_POINTS);
   if (currCoordsArray.numberOfCoords == 0)
   {
-    //if(gap_debug)
+    if(gap_debug)
     {
        printf("gap_track_detail_on_top_layers  NO tracking possible because No vectors path was found\n");
     }  
@@ -1687,11 +2746,40 @@ gap_track_detail_on_top_layers(gint32 imageId, gboolean doProgress, FilterValues
   && (l_nlayers > 0))
   {
     gint32 topLayerId;
+    gint32 belowTopLayerId;
     gint32 refLayerId;
 
+    if(gap_debug)
+    {
+      int iil;
+      gchar *lname;
+      for(iil=0; iil < l_nlayers; iil++)
+      {
+        gint offset_x;
+        gint offset_y;
+        gimp_drawable_offsets (l_layers_list[iil], &offset_x, &offset_y);
+        lname = gimp_item_get_name(l_layers_list[iil]);
+        printf(" layerstack[%d] layer_id:%d name:%s size: %d x %d offset_x:%d offset_y:%d\n"
+               ,(int)iil
+               ,(int)l_layers_list[iil]
+               ,lname
+               ,(int)gimp_drawable_width(l_layers_list[iil])
+               ,(int)gimp_drawable_height(l_layers_list[iil])
+               ,(int)offset_x
+               ,(int)offset_y
+               );
+        if(lname != NULL)
+        {
+          g_free(lname);
+        }
+      }
+    }  
+
+
     topLayerId = l_layers_list[0];
 
-    refLayerId = l_layers_list[1];
+    belowTopLayerId = l_layers_list[1];
+    refLayerId = belowTopLayerId;
     if (valPtr->bgLayerIsReference == TRUE)
     {
       refLayerId = l_layers_list[l_nlayers -1];
@@ -1699,17 +2787,20 @@ gap_track_detail_on_top_layers(gint32 imageId, gboolean doProgress, FilterValues
 
     /// frameHistInfo->frameNr += 1;
 
-    p_get_frameHistInfo(frameHistInfo);
+    p_get_frameHistInfo(frameHistInfo, imageId);
 
 
     if ((frameHistInfo->trackedFramesCount == 0)
     || (frameHistInfo->workImageId != imageId)
     || (l_nlayers == 1))
     {
-      //if(gap_debug)
+      if(gap_debug)
       {
-        printf("(A) gap_track_detail_on_top_layers  BEGIN tracking l_nlayers:%d\n"
+        printf("(A) gap_track_detail_on_top_layers  BEGIN tracking l_nlayers:%d trackedFramesCount:%d 
workImageId:%d imageId:%d\n"
                ,(int)l_nlayers
+               ,(int)frameHistInfo->trackedFramesCount
+               ,(int)frameHistInfo->workImageId
+               ,(int)imageId
                );
       }  
       /* start of detail tracking when no frame history available and whenever a new workImage was created */
@@ -1727,18 +2818,28 @@ gap_track_detail_on_top_layers(gint32 imageId, gboolean doProgress, FilterValues
       frameHistInfo->workImageId = imageId;
       if (isTrackingToFrameImage != TRUE)
       {
-        bestIdx1 = p_selective_coords_logging(frameHistInfo->frameNr
+        /* the initial call for frame 1 shall just record the inital tracking points
+         * without calling loacte procedure and without tuning
+         * (therefore it uses 2x refLayerId 
+         *  note that the topLayerId of frame2 is typically already present at this time
+         *  but is handled in the 2nd call to p_selective_coords_logging 
+         *  later in this procedure)
+         */ 
+        bestIdx1 = p_selective_coords_logging(frameHistInfo
                       , &currCoordsArray
                       , &frameHistInfo->startCoordsArray
+                      , &frameHistInfo->prevCoordsArray
                       , valPtr
                       , imageId
+                      , refLayerId
+                      , refLayerId
                       );
       }
     }
     else
     {
 
-      //if(gap_debug)
+      if(gap_debug)
       {
         printf("(B) gap_track_detail_on_top_layers  CONTINUE tracking l_nlayers:%d\n"
                ,(int)l_nlayers
@@ -1749,7 +2850,7 @@ gap_track_detail_on_top_layers(gint32 imageId, gboolean doProgress, FilterValues
 
       /* (re)inital capture vector points if the start coords of first processed frame are not valid 
        *  TODO detecting by valid startCoordsArray [0] is no longer sufficient
-       *       for now re-init is hercoded disabled (not sure if that is needed ...)
+       *       for now re-init is harcoded disabled (not sure if that is needed ...)
        */
       if(FALSE) //// if (frameHistInfo->startCoordsArray.pixCoord[0].valid != TRUE)
       {
@@ -1762,11 +2863,14 @@ gap_track_detail_on_top_layers(gint32 imageId, gboolean doProgress, FilterValues
         frameHistInfo->frameNr = p_parse_frame_nr_from_layer_name(refLayerId);
         if (isTrackingToFrameImage != TRUE)
         {
-          bestIdx1 = p_selective_coords_logging(frameHistInfo->frameNr
+          bestIdx1 = p_selective_coords_logging(frameHistInfo
                       , &currCoordsArray
                       , &frameHistInfo->startCoordsArray
+                      , &frameHistInfo->prevCoordsArray
                       , valPtr
                       , imageId
+                      , refLayerId
+                      , refLayerId
                       );
         }
       }
@@ -1776,7 +2880,7 @@ gap_track_detail_on_top_layers(gint32 imageId, gboolean doProgress, FilterValues
         gint32 sumOffsY;
         gint32 numValidOffsets;
 
-        //if(gap_debug)
+        if(gap_debug)
         {
           printf("(Bb) gap_track_detail_on_top_layers  CONTINUE BG is referenence  l_nlayers:%d\n"
                 ,(int)l_nlayers
@@ -1801,8 +2905,8 @@ gap_track_detail_on_top_layers(gint32 imageId, gboolean doProgress, FilterValues
         numValidOffsets = 0;
         for (l_idx = 0; l_idx < frameHistInfo->prevCoordsArray.numberOfCoords; l_idx++)
         {
-          PixelCoords  *prevCoords;
-          PixelCoords  *startCoords;
+          GapPixelCoords  *prevCoords;
+          GapPixelCoords  *startCoords;
           
           prevCoords = &frameHistInfo->prevCoordsArray.pixCoord[l_idx];
           startCoords = &frameHistInfo->startCoordsArray.pixCoord[l_idx];
@@ -1830,8 +2934,8 @@ gap_track_detail_on_top_layers(gint32 imageId, gboolean doProgress, FilterValues
            */
           for (l_idx = 0; l_idx < frameHistInfo->prevCoordsArray.numberOfCoords; l_idx++)
           {
-            PixelCoords  *prevCoords;
-            PixelCoords  *startCoords;
+            GapPixelCoords  *prevCoords;
+            GapPixelCoords  *startCoords;
           
             prevCoords = &frameHistInfo->prevCoordsArray.pixCoord[l_idx];
             startCoords = &frameHistInfo->startCoordsArray.pixCoord[l_idx];
@@ -1850,7 +2954,7 @@ gap_track_detail_on_top_layers(gint32 imageId, gboolean doProgress, FilterValues
       }
       else
       {
-        //if(gap_debug)
+        if(gap_debug)
         {
           printf("(Bp) gap_track_detail_on_top_layers  CONTINUE Previous Layer is referenence l_nlayers:%d\n"
                 ,(int)l_nlayers
@@ -1875,15 +2979,15 @@ gap_track_detail_on_top_layers(gint32 imageId, gboolean doProgress, FilterValues
       previousLostTraceCount = frameHistInfo->lostTraceCount;
       targetCoordsArray.numberOfCoords = currCoordsArray.numberOfCoords;
       
-      //if(gap_debug)
+      if(gap_debug)
       {
         printf("DetailTrack before locating %d coordinates\n", currCoordsArray.numberOfCoords);
       }
       
       for (l_idx = 0; l_idx < currCoordsArray.numberOfCoords; l_idx++)
       {
-        PixelCoords  *currCoordsPtr;
-        PixelCoords  *targetCoordsPtr;
+        GapPixelCoords  *currCoordsPtr;
+        GapPixelCoords  *targetCoordsPtr;
         
         currCoordsPtr = &currCoordsArray.pixCoord[l_idx];
         targetCoordsPtr = &targetCoordsArray.pixCoord[l_idx];
@@ -1928,32 +3032,37 @@ gap_track_detail_on_top_layers(gint32 imageId, gboolean doProgress, FilterValues
      */
     if(TRUE)
     {
-      p_copy_src_to_dst_coords_array(&targetCoordsArray,  &frameHistInfo->prevCoordsArray);
+      // p_copy_src_to_dst_coords_array(&targetCoordsArray,  &frameHistInfo->prevCoordsArray);
 
       frameHistInfo->frameNr = currFrameNr;
       
       if (isTrackingToFrameImage != TRUE)
       {
-        bestIdx1 = p_selective_coords_logging(frameHistInfo->frameNr
+        bestIdx1 = p_selective_coords_logging(frameHistInfo
                       , &targetCoordsArray
                       , &frameHistInfo->startCoordsArray
+                      , &frameHistInfo->prevCoordsArray
                       , valPtr
                       , imageId
+                      , topLayerId
+                      , refLayerId
                       );
       }
       else
       {
-        gint32 bestIdx2;
-        p_select_best_coords(frameHistInfo->frameNr
+        
+        p_select_best_coords(imageId, frameHistInfo   //->frameNr
          , &targetCoordsArray
          , &frameHistInfo->startCoordsArray
          , valPtr
-         , &bestIdx1
-         , &bestIdx2
+         , &bestIndexes
+         , frameHistInfo
         );
+        bestIdx1 = bestIndexes.bestIdx[0];
       }
       p_set_n_vector_points(imageId, &targetCoordsArray, VECTORS_NAME_TRACKING_POINTS, TRUE, TRUE, bestIdx1);
     }
+    p_copy_src_to_dst_coords_array(&targetCoordsArray,  &frameHistInfo->prevCoordsArray);
 
     if (valPtr->removeMidlayers == TRUE)
     {
@@ -1974,14 +3083,106 @@ gap_track_detail_on_top_layers(gint32 imageId, gboolean doProgress, FilterValues
         gchar *basename;
         long   number;
         gint   l_rc;
+        gint32 transformedLayerId;
+        gint32 currVectorsId;
+        gint32 targetVectorsId;
+
+        // TODO enable via env or gimprc
+        // p_set_debug_vectors(currFrameNr, imageId, &targetCoordsArray, &frameHistInfo->startCoordsArray, 
&bestIndexes);
 
         basename = gap_lib_alloc_basename(&valPtr->moveLogFile[0], &number);
         frame_filename = gap_lib_alloc_fname_fixed_digits(basename, currFrameNr, l_extension, 6 /* digits*/ 
);
+
+        transformedLayerId = -1;
+        currVectorsId = -1;
+        targetVectorsId = -1;
+        if (valPtr->addTransformedLayer)
+        {
+         PixelCoordsArray alignCurrCoordsArray;
+         PixelCoordsArray alignTargetCoordsArray;
+         
+         
+         
+         
+         gint validPairCount;
+         gint idx;
+          GapPixelCoords  *trkPtr[4];   /* upto 4 coords in current frame  currCoords[4]; */
+          GapPixelCoords  *refPtr[4];   /* upto 4 coords of first processed (reference) frame  
startCoords[4]; */
+         
+          /* set paths "SRC" and "TARGET" respecting BestIndexes...   for the aligner call */
+          validPairCount = 0;
+          for(idx=0; idx < 4; idx++)
+         {
+           trkPtr[idx] = &targetCoordsArray.pixCoord[bestIndexes.bestIdx[idx]];
+           refPtr[idx] = &frameHistInfo->startCoordsArray.pixCoord[bestIndexes.bestIdx[idx]];        
+           if((trkPtr[idx]->valid == TRUE)
+           && (refPtr[idx]->valid == TRUE))
+           {
+              gap_geo_copy_src_to_dst_coords(trkPtr[idx]                    /* GapPixelCoords *srcCoords*/
+                                         ,&alignCurrCoordsArray.pixCoord[validPairCount]  /* 
GapPixelCoords*dstCoords */
+                                         );
+              gap_geo_copy_src_to_dst_coords(refPtr[idx]                    /* GapPixelCoords *srcCoords*/
+                                         ,&alignTargetCoordsArray.pixCoord[validPairCount]  /* 
GapPixelCoords*dstCoords */
+                                         );
+             validPairCount++;
+             alignCurrCoordsArray.numberOfCoords = validPairCount;
+             alignTargetCoordsArray.numberOfCoords = validPairCount;
+           }
+         }
+
+          
+          
+          /* set src Path name = "SRC" for the aligner and make it active vectors path */
+          currVectorsId = p_set_n_vector_points(imageId, &alignCurrCoordsArray, "SRC", TRUE, FALSE, 0);
+          if(currVectorsId >= 0)
+          {
+            /* set target Path name = "TARGET" for the aligner */
+            targetVectorsId = p_set_n_vector_points(imageId, &alignTargetCoordsArray, 
GAP_EXACT_ALIGNER_TARGET_PATH_NAME, TRUE, FALSE, 0);
+
+            gimp_image_set_active_vectors(imageId, currVectorsId);
+
+            /* duplicate Top Layer */
+            transformedLayerId = gap_layer_make_duplicate(topLayerId  /* gint32 src_layer_id */
+                                     , imageId
+                                     , "TRANS_"   /* const char *name_prefix */
+                                     , "\0"       /* const char *name_suffix */
+                                     );
+            /* only the transformed layer on top and the BG layer shall be visible in the saved frame image 
*/
+            gimp_item_set_visible(topLayerId, FALSE);
+            gimp_item_set_visible(belowTopLayerId, FALSE);
+            gimp_item_set_visible(transformedLayerId, TRUE);
+            gimp_item_set_visible(refLayerId, TRUE);
+              
+
+            /*  call exact aligner plugin to perform transformation according to paths active (SRC) and 
TARGET */
+            gap_detail_exact_align_via_4point_path(imageId, transformedLayerId
+                        , POINT_ORDER_MODE_1234_1234
+                        , GIMP_RUN_NONINTERACTIVE
+                        );
+          }
+           
+        }
+
+
     
         l_rc = gap_lib_save_named_frame(imageId, frame_filename);
     
         g_free(basename);
         g_free(frame_filename);
+
+
+        if (transformedLayerId >= 0)
+        {
+          gimp_image_remove_layer(imageId, transformedLayerId);
+        }
+        if (currVectorsId >= 0)
+        {
+          gimp_image_remove_vectors(imageId, currVectorsId);
+        }
+        if (targetVectorsId >= 0)
+        {
+          gimp_image_remove_vectors(imageId, targetVectorsId);
+        }
         
       }
       g_free(l_extension);
@@ -1989,6 +3190,7 @@ gap_track_detail_on_top_layers(gint32 imageId, gboolean doProgress, FilterValues
 
     }
 
+
     
     if((valPtr->bgLayerIsReference != TRUE)
     && (successfulTracedPointsCount >= valPtr->numPointsSelect))
@@ -2000,14 +3202,14 @@ gap_track_detail_on_top_layers(gint32 imageId, gboolean doProgress, FilterValues
     }
     
     frameHistInfo->trackedFramesCount++;
-    p_set_frameHistInfo(frameHistInfo);
+    p_set_frameHistInfo(frameHistInfo, imageId);
     g_free(l_layers_list);
 
     if ((successfulTracedPointsCount < valPtr->numPointsSelect)
     &&  (frameHistInfo->trackedFramesCount > 1)
     &&  (previousLostTraceCount == 0))
     {
-      // if (gap_debug)
+      if (gap_debug)
       {
         printf("Detail Tracking Stopped at frameNr:%d previousLostTraceCount:%d 
successfulTracedPointsCount:%d (required %d)\n"
           , (int)frameHistInfo->frameNr
@@ -2082,6 +3284,7 @@ gap_detail_tracking_get_values(FilterValues *fiVals)
   fiVals->refShapeRadius             = DEFAULT_refShapeRadius;
   fiVals->targetMoveRadius           = DEFAULT_targetMoveRadius;
   fiVals->loacteColodiffThreshold    = DEFAULT_loacteColodiffThreshold;
+  fiVals->numPointsSelect            = DEFAULT_numPointsSelect;
   fiVals->coordsRelToFrame1          = DEFAULT_coordsRelToFrame1;
   fiVals->offsX                      = DEFAULT_offsX;
   fiVals->offsY                      = DEFAULT_offsY;
@@ -2089,6 +3292,7 @@ gap_detail_tracking_get_values(FilterValues *fiVals)
   fiVals->enableScaling              = DEFAULT_enableScaling;
   fiVals->removeMidlayers            = DEFAULT_removeMidlayers;
   fiVals->bgLayerIsReference         = DEFAULT_bgLayerIsReference;
+  fiVals->addTransformedLayer        = DEFAULT_addTransformedLayer;
   fiVals->moveLogFile[0]             = '\0';
 
   l_len = gimp_get_data_size (GAP_DETAIL_TRACKING_PLUG_IN_NAME);
@@ -2137,7 +3341,7 @@ gboolean
 gap_detail_tracking_dialog(FilterValues *fiVals)
 {
 #define SPINBUTTON_ENTRY_WIDTH 80
-#define DETAIL_TRACKING_DIALOG_ARGC 13
+#define DETAIL_TRACKING_DIALOG_ARGC 15
 
   static GapArrArg  argv[DETAIL_TRACKING_DIALOG_ARGC];
   gint ii;
@@ -2152,6 +3356,7 @@ gap_detail_tracking_dialog(FilterValues *fiVals)
   gint ii_enableScaling;
   gint ii_removeMidlayers;
   gint ii_bgLayerIsReference;
+  gint ii_addTransformedLayer;
 
 
   ii=0; gap_arr_arg_init(&argv[ii], GAP_ARR_WGT_LABEL);
@@ -2257,6 +3462,13 @@ gap_detail_tracking_dialog(FilterValues *fiVals)
   argv[ii].int_default = DEFAULT_removeMidlayers;
 
 
+  ii++; gap_arr_arg_init(&argv[ii], GAP_ARR_WGT_TOGGLE); ii_addTransformedLayer = ii;
+  argv[ii].label_txt = _("add Transformed Layer:");
+  argv[ii].help_txt  = _("ON: add layer and apply detail_align transformation when tracking to XCF frame 
image.\n"
+                         "OFF: do not apply detail align transformation\n.");
+  argv[ii].int_ret   = fiVals->addTransformedLayer;
+  argv[ii].has_default = TRUE;
+  argv[ii].int_default = DEFAULT_addTransformedLayer;
 
 
 
@@ -2330,6 +3542,7 @@ gap_detail_tracking_dialog(FilterValues *fiVals)
       fiVals->enableScaling            = (gint32)(argv[ii_enableScaling].int_ret);
       fiVals->removeMidlayers          = (gint32)(argv[ii_removeMidlayers].int_ret);
       fiVals->bgLayerIsReference       = (gint32)(argv[ii_bgLayerIsReference].int_ret);
+      fiVals->addTransformedLayer      = (gint32)(argv[ii_addTransformedLayer].int_ret);
 
       gimp_set_data (GAP_DETAIL_TRACKING_PLUG_IN_NAME, fiVals, sizeof (FilterValues));
       return TRUE;
diff --git a/gap/gap_detail_tracking_exec.h b/gap/gap_detail_tracking_exec.h
index a128827..ad3f2ad 100644
--- a/gap/gap_detail_tracking_exec.h
+++ b/gap/gap_detail_tracking_exec.h
@@ -41,6 +41,7 @@
 #include <libgimp/gimp.h>
 #include <libgimp/gimpui.h>
 
+#include "gap_geo.h"
 #include "gap_libgapbase.h"
 #include "gap_locate.h"
 #include "gap_colordiff.h"
@@ -76,22 +77,16 @@ typedef struct FilterValues {
    gboolean   enableScaling;       /* on: use rotation and scaling  off: rotate only  */
    gboolean   bgLayerIsReference;
    gboolean   removeMidlayers;     /* on: keep 2 top layers and Bg layer, remove other layers  off: keep all 
layers  */
+   gboolean   addTransformedLayer; /* add layer and apply detail_align transformation when tracking to XCF 
image */
    char       moveLogFile[1600];
 } FilterValues;
 
-typedef struct PixelCoords
-{
-  gboolean  valid;
-  gint32  px;
-  gint32  py;
-  gdouble   avgColorDiff;    // 0 = best quality, 1 = worst quality
-} PixelCoords;
 
 #define MAX_PIXEL_COORDS_ARRAY 32
 
 typedef struct PixelCoordsArray
 {
-  PixelCoords  pixCoord[MAX_PIXEL_COORDS_ARRAY];
+  GapPixelCoords  pixCoord[MAX_PIXEL_COORDS_ARRAY];
   int          numberOfCoords;           /* number of used pixelCoord elements in the array */
   gint32       numValidOffsets;          /* number of valid coords involved in average Offset calculation */
   gdouble      avgOffsX;                 /* average horizontal movement vektor (extreme values are not 
included) */ 
@@ -108,6 +103,7 @@ typedef struct FrameHistInfo
 
   gint32       lostTraceCount;        /* count frames where the required number of detailspoints could not 
be located */
   gint32       trackedFramesCount;
+  gint32       bestIdx[4];            /* best indexes that were picked while processing the previous frame */
 } FrameHistInfo;
 
 
diff --git a/gap/gap_detail_tracking_main.c b/gap/gap_detail_tracking_main.c
index 7cb264c..b2f3711 100644
--- a/gap/gap_detail_tracking_main.c
+++ b/gap/gap_detail_tracking_main.c
@@ -1,5 +1,5 @@
 /*  gap_detail_tracking_main.c
- *  This main module provides multiple filters:
+ *  This main module provides multiple filters dealing wit video frame stabilisation:
  *
  *  A) Detail Tracking
  *
@@ -10,17 +10,20 @@
  *    in a series of video frames.
  *    The recorded positions can also be used as XML input for the XML aligner
  *    plug-in (available in this module) and can be used as filter
- *    in the frames modify feature.
+ *    in the frames modify feature for video stabilisation purpose.
  *
  *
  *    Applying the recorded position can compensate unwanted camera moves
- *    when static scenes where shot without using a stativ.
+ *    when static scenes where shot without using a tripod.
  *  Note that the recording of positions is usually triggered by the
  *  Player's Snaphot feature where this filter runs on the 2 topmost layers
  *  (or on top and BG layer)
  *  in the snapshot image that is created and updated by the player.
  *
  *  B) Transformation of a Layer by 4 or 2 controlpoints.
+ *    2x4 points: performs a perspective transformation on the layer in a way that 4 reference points
+ *        match 4 target points (in the TARGET path). 
+ *        Perspective transformation can copmensate both moves and rotations of the camera.
  *    4 points: rotate scale and move the layer in a way that 2 reference points match 2 target points. 
  *       (Note that the 4 point mode works similar to the Exact Aligner script in the plug-in registry)
  *    2 points: simple move the layer from reference point to target point.
@@ -28,6 +31,7 @@
  *    This filter transformation is available in 2 variants:
  *    B1) controlpoints input from current path
  *    B2) controlpoints from an xml input file recorded by GAP detail tracking feature.
+ *        (intended for use as filter applied with the GIMP-GAP modify frames feature on multiple frames)
  *
  *  2011/12/01
  */
@@ -116,6 +120,9 @@ static const GimpParamDef in_args[] =
                                                 "(the shape is searched in the target layer only within this 
radius." },
     { GIMP_PDB_FLOAT,    "loacteColodiffThreshold",     "0.0 upto 1.0 threshold that defines tolerated 
average colordiff for successful detail tracking."
                                                         " ." },
+    { GIMP_PDB_INT32,    "numPointsSelect",      "1 .. select best matching single point for simple point 
alignment via layermovement"
+                                                 "2 .. select best 2 points for alignment via scaling, 
rotation and movement"
+                                                 "4 .. select 4 points for perspective alignment 
(compensates all sorts of camera shake)" },
     { GIMP_PDB_INT32,    "coordsRelToFrame1",    "1 .. substract coords of initial position from all 
recorded positions."
                                                  "     (i.e. recording starts with px=0 py=0) "
                                                  "0 .. record absolute positions" },
@@ -130,16 +137,24 @@ static const GimpParamDef in_args[] =
                                                  "   (this is typically the previous frame of the sequence)" 
},
     { GIMP_PDB_INT32,    "removeMidlayers",      "1: delete all layers except BG layer and 2 layer on top of 
the layerstack, "
                                                  "0: do not delete anything and keep all layers." },
-    { GIMP_PDB_STRING,   "moveLogFile",          "optional name of a move path controlpoint xml file. (use - 
to write to stdout) " }
+    { GIMP_PDB_INT32,    "addTransformedLayer",  "add a transformed copy of the tracked layer "
+                                                 "when saving tracking snaphots to XCF frame images. (video 
stabilized frames)   " },
+    { GIMP_PDB_STRING,   "moveXmlFile",          "optional output file for detail tracking information"
+                                                 "use - for logging to stdout "
+                                                 "or provide a name name of a move path controlpoint xml 
file.  "
+                                                 "In case the name ends with extension .xcf, the tracked 
snapshot image is saved "
+                                                 "as frame image in the GIMP XCF imageformat with tracking 
information added as vector pathes" }
 };
 
 static const GimpParamDef in_xml_args[] =
 {
-    { GIMP_PDB_INT32,    "run-mode",      "Interactive, non-interactive" },
-    { GIMP_PDB_IMAGE,    "image",         "Input image"                  },
-    { GIMP_PDB_DRAWABLE, "drawable",      "layer to be aligned"               },
-    { GIMP_PDB_INT32,    "framePhase",    "frame number i.e. phase to render (1 upto n recorded points in 
the xml file)." },
-    { GIMP_PDB_STRING,   "moveLogFile",          "optional name of a move path controlpoint xml file. (use - 
to write to stdout) " }
+    { GIMP_PDB_INT32,    "run-mode",       "Interactive, non-interactive" },
+    { GIMP_PDB_IMAGE,    "image",          "Input image"                  },
+    { GIMP_PDB_DRAWABLE, "drawable",       "layer to be aligned"               },
+    { GIMP_PDB_INT32,    "framePhase",     "frame number i.e. phase to render (1 upto n recorded points in 
the xml file)." },
+    { GIMP_PDB_FLOAT,    "precision",      "precision in pixels for calculation of perspective 
transformation coordinates (range 0.001 to 1.0)" },
+    { GIMP_PDB_FLOAT,    "precisionThres", "precision threshold in pixels for iterative fine tuning attempts 
(range 0.0 to 2.0)" },
+    { GIMP_PDB_STRING,   "moveXmlFile",    "name of a controlpoint xml file. (containing the transformation 
coordinates recorded by detail tracking feature) " }
 };
 
 static const GimpParamDef in_exalign_args[] =
@@ -147,8 +162,9 @@ static const GimpParamDef in_exalign_args[] =
     { GIMP_PDB_INT32,    "run-mode",      "Interactive, non-interactive" },
     { GIMP_PDB_IMAGE,    "image",         "Input image"                  },
     { GIMP_PDB_DRAWABLE, "drawable",      "layer to be aligned"          },
-    { GIMP_PDB_INT32,    "pointOrder",    "0: use path coordinate points in order: 3 --> 1, 4 --> 2 "
-                                          "1: use path coordinate points in order: 2 --> 1, 4 --> 3 " }
+    { GIMP_PDB_INT32,    "pointOrder",    "0: use current path coordinate points in order: 3 --> 1, 4 --> 2 "
+                                          "1: use current path coordinate points in order: 2 --> 1, 4 --> 3 "
+                                          "2: use coordinate points in order: current path 1234 --> 1234 
TARGET path " }
 };
 
 
@@ -172,6 +188,9 @@ MAIN ()
 
 static void query (void)
 {
+  gchar *descriptionText;
+  gchar *fineTuningText;
+
   static GimpLastvalDef lastvals[] =
   {
     GIMP_LASTVALDEF_GINT32       (GIMP_ITER_FALSE,  fiVals.refShapeRadius,             "refShapeRadius"),
@@ -191,9 +210,11 @@ static void query (void)
 
   static GimpLastvalDef xaLastvals[] =
   {
-    GIMP_LASTVALDEF_GINT32       (GIMP_ITER_TRUE,   xaVals.framePhase,                 "framePhase"),
-    GIMP_LASTVALDEF_ARRAY        (GIMP_ITER_FALSE,  xaVals.moveLogFile,                "moveLogFileArray"),
-    GIMP_LASTVALDEF_GCHAR        (GIMP_ITER_FALSE,  xaVals.moveLogFile[0],             "moveLogFileChar"),
+    GIMP_LASTVALDEF_GINT32       (GIMP_ITER_TRUE,   xaVals.framePhase,                  "framePhase"),
+    GIMP_LASTVALDEF_GDOUBLE      (GIMP_ITER_FALSE,  xaVals.transformPrecision,          
"transformPrecision"),
+    GIMP_LASTVALDEF_GDOUBLE      (GIMP_ITER_FALSE,  xaVals.transformPrecisionThreshold, 
"transformPrecisionThreshold"),
+    GIMP_LASTVALDEF_ARRAY        (GIMP_ITER_FALSE,  xaVals.moveLogFile,                 "moveLogFileArray"),
+    GIMP_LASTVALDEF_GCHAR        (GIMP_ITER_FALSE,  xaVals.moveLogFile[0],              "moveLogFileChar"),
 
   };
 
@@ -225,7 +246,7 @@ static void query (void)
                           "This new position is logged in XML format, suitable as input for the MovePath 
plug-in."
                           "Note that this filter is typically invoked from the Player on the snapshot image, 
"
                           "whenever the player puts the next frame on top of the snaphot image and detail 
tracking is enabled. "
-                          "Detail tracking can record the unwanted camera movements in a static scene of a 
video shot freehand (without a stativ) "
+                          "Detail tracking can record the unwanted camera movements in a static scene of a 
video shot freehand (without a tripod) "
                           "Applying the recorded movements with the MovePath feature can compensate such 
unwanted movements. "
                           " ",
                           PLUG_IN_AUTHOR,
@@ -250,8 +271,9 @@ static void query (void)
                           "This new position is logged in XML format, suitable as input for the MovePath 
plug-in."
                           "Note that this filter is typically invoked from the Player on the snapshot image, 
"
                           "whenever the player puts the next frame on top of the snaphot image and detail 
tracking is enabled. "
-                          "Detail tracking can record the unwanted camera movements in a static scene of a 
video shot freehand (without a stativ) "
-                          "Applying the recorded movements with the MovePath feature can compensate such 
unwanted movements. "
+                          "Detail tracking can record the unwanted camera movements in a static scene of a 
video shot freehand (without a tripod) "
+                          "Applying the recorded movements with the MovePath feature (or the detail align 
filter via frames modify feature) "
+                          "can compensate such unwanted camera movements and rotations. "
                           " ",
                           PLUG_IN_AUTHOR,
                           PLUG_IN_COPYRIGHT,
@@ -264,17 +286,43 @@ static void query (void)
                           in_args,
                           return_vals);
 
+
+
+
+  fineTuningText = g_strdup_printf(_("optional fine tuning "
+                            "is triggered when the frame image has an additional Layer "
+                            "with the special name '%s.' "
+                            "in this case the transformation is done in more probe variants with slightly 
different values "
+                            "and the result is compared with the opaque areas in the '%s.' layer "
+                            "for final rendering, the variant is picked that has the minumum difference in 
the compared areas "
+                            "The performance intensive fine tuning is intended to reduce unwanted jitter 
effects "
+                            "with minimal amplitude of just 1 pixel or below "
+                            "when alignment is applied to many frames of a videoclip for stabilsation 
purpose. "
+                            "The the '%s.' layer shall have a layer mask that marks comparable background 
white (opaque)."
+                            " ")
+                            , GAP_EXACT_ALIGNER_REF_LAYER_NAME
+                            , GAP_EXACT_ALIGNER_REF_LAYER_NAME
+                            , GAP_EXACT_ALIGNER_REF_LAYER_NAME
+                            );
+
+  descriptionText = g_strdup_printf(_("This video frame stabilisation filter transforms the specified layer. 
"
+                            "It uses the relevant controlpoint (that matches the framePhase parameter) in 
the recorded XML file as input.  "
+                            "and calculates offsts, scaling and rotation or perspective corner points to 
transform the layer in a way that "
+                            "the points p1x p1y p2x p2y (p3x p3y p4x p4y) "
+                            "will exactly match with the points s1x s1y s2x s2y (s3x s3y s4x s4y) in the 
same controlpoint in the XML file."
+                            "(calling this filter with framePhase 1 typically does no transformation) "
+                            "This filter is intended to run under control of the gimp-gap frames modify 
feature "
+                            "to align multiple frames according to the controlpoints recorded in an XML file 
(via Detail tracking feature)."
+                            "%s")
+                            , fineTuningText
+                            );
+
+
+
   /* the  installation of the xml based aligner plugin */
   gimp_install_procedure (GAP_DETAIL_TRACKING_XML_ALIGNER_PLUG_IN_NAME,
                           "Exact Align Layer via transformation according to current phase of detail 
tracking (recorded in XML file).",
-                          "This filter tranforms the specified layer. "
-                          "It uses the relevant controlpoint (that matches the framePhase parameter) in the 
recorded XML file as input.  "
-                          "and calculates offsts, scaling and rotation to transform the layer in a way that 
the points p1x p1y p2x p2y "
-                          "will exactly match with the points p1x p1y p2x p2y of the 1st controlpoint in the 
XML file."
-                          "(calling this filter with framePhase 1 does no transformation) "
-                          "This filter is intended to run under control of the gimp-gap frames modify 
feature "
-                          "to align multiple frames according to the controlpoints recorde in an XML file 
(via Detail tracking feature)."
-                          " ",
+                          descriptionText,
                           PLUG_IN_AUTHOR,
                           PLUG_IN_COPYRIGHT,
                           GAP_VERSION_WITH_DATE,
@@ -286,15 +334,27 @@ static void query (void)
                           in_xml_args,
                           return_vals);
 
-  /* the  installation of the 4-point path based aligner plugin */
-  gimp_install_procedure (GAP_EXACT_ALIGNER_PLUG_IN_NAME,
-                          "Exact Align Layer via transformation according 4 points specified in the current 
path.",
-                          "This filter expects a current path with 4 points as input where point 1 and 2 
mark positions "
+  g_free(descriptionText);
+  descriptionText = g_strdup_printf(_("This filter "
+                          "expects a current path with 4 points as input where point 1 and 2 mark positions "
                           "within a reference layer and points 3 and 4 mark 2 corresponding point in the 
target layer. "
                           "The transformation is applied to the target layer and sets offsets, scaling and 
rotation "
                           "in a way that point3 is placed on position of point1, and point4 is placed on 
position of point2."
                           " "
-                          " ",
+                          "As alternitive this filter also provides exact alignment via Perspective 
Transformation. "
+                          "Therefore 4 points are required in the current path, and another 4 points are 
required in an "
+                          "additional path that must have the name '%s'. The layer will be transformed in a 
way "
+                          "that all 4 points in the current path will be placed on their corresponding 
points in the '%s' path."
+                          "%s")
+                          , GAP_EXACT_ALIGNER_TARGET_PATH_NAME
+                          , GAP_EXACT_ALIGNER_TARGET_PATH_NAME
+                          , fineTuningText
+                          );
+
+  /* the  installation of the 4-point path based aligner plugin */
+  gimp_install_procedure (GAP_EXACT_ALIGNER_PLUG_IN_NAME,
+                          "Exact Align Layer via transformation according 4 points specified in the current 
path.",
+                          descriptionText,
                           PLUG_IN_AUTHOR,
                           PLUG_IN_COPYRIGHT,
                           GAP_VERSION_WITH_DATE,
@@ -306,15 +366,18 @@ static void query (void)
                           in_exalign_args,
                           return_vals);
 
+  g_free(descriptionText);
+  g_free(fineTuningText);
 
   {
     /* Menu names */
     const char *menupath_image_layer_enhance = N_("<Image>/Video/Layer/Enhance/");
     const char *menupath_image_layer_transform = N_("<Image>/Layer/Transform/");
+    const char *menupath_image_video_layer_transform = N_("<Image>/Video/Layer/Transform/");
 
     gimp_plugin_menu_register (PLUG_IN_NAME_CFG, menupath_image_layer_enhance);
     gimp_plugin_menu_register (PLUG_IN_NAME, menupath_image_layer_enhance);
-    gimp_plugin_menu_register (GAP_DETAIL_TRACKING_XML_ALIGNER_PLUG_IN_NAME, menupath_image_layer_enhance);
+    gimp_plugin_menu_register (GAP_DETAIL_TRACKING_XML_ALIGNER_PLUG_IN_NAME, 
menupath_image_video_layer_transform);
     gimp_plugin_menu_register (GAP_EXACT_ALIGNER_PLUG_IN_NAME, menupath_image_layer_transform);
   }
 
@@ -444,6 +507,22 @@ runXmlAlign (const gchar *name,  /* name of plugin */
     case GIMP_RUN_INTERACTIVE:
       {
         gboolean dialogOk;
+        char   *imagename;
+        
+        imagename = gimp_image_get_filename(image_id);
+        if (imagename != NULL)
+        {
+          gint32 frameNumber;
+          
+          frameNumber = gap_lib_get_frame_nr_from_name(imagename);
+          if (frameNumber > 0)
+          {
+            xaVals.framePhase = frameNumber;
+          }
+         g_free(imagename);
+        }
+        
+        
 
         dialogOk = gap_detail_xml_align_dialog(&xaVals);
         if( dialogOk != TRUE)
@@ -460,11 +539,13 @@ runXmlAlign (const gchar *name,  /* name of plugin */
       /* check to see if invoked with the correct number of parameters */
       if (nparams == global_number_in_args)
       {
-          xaVals.framePhase               = param[3].data.d_int32;
+          xaVals.framePhase                   = param[3].data.d_int32;
+          xaVals.transformPrecision           = param[4].data.d_float;
+          xaVals.transformPrecisionThreshold  = param[5].data.d_float;
           xaVals.moveLogFile[0] = '\0';
-          if(param[4].data.d_string != NULL)
+          if(param[6].data.d_string != NULL)
           {
-            g_snprintf(xaVals.moveLogFile, sizeof(xaVals.moveLogFile) -1, "%s", param[4].data.d_string);
+            g_snprintf(xaVals.moveLogFile, sizeof(xaVals.moveLogFile) -1, "%s", param[6].data.d_string);
           }
 
       }
@@ -652,16 +733,18 @@ run (const gchar *name,          /* name of plugin */
           fiVals.refShapeRadius               = param[3].data.d_int32;
           fiVals.targetMoveRadius             = param[4].data.d_int32;
           fiVals.loacteColodiffThreshold      = param[5].data.d_float;
-          fiVals.coordsRelToFrame1            = (param[6].data.d_int32 == 0) ? FALSE : TRUE;
-          fiVals.offsX                        = param[7].data.d_int32;
-          fiVals.offsY                        = param[8].data.d_int32;
-          fiVals.offsRotate                   = param[9].data.d_float;
-          fiVals.enableScaling                = (param[10].data.d_int32 == 0) ? FALSE : TRUE;
-          fiVals.bgLayerIsReference           = (param[11].data.d_int32 == 0) ? FALSE : TRUE;
-          fiVals.removeMidlayers              = (param[12].data.d_int32 == 0) ? FALSE : TRUE;
+          fiVals.numPointsSelect              = param[6].data.d_int32;
+          fiVals.coordsRelToFrame1            = (param[7].data.d_int32 == 0) ? FALSE : TRUE;
+          fiVals.offsX                        = param[8].data.d_int32;
+          fiVals.offsY                        = param[9].data.d_int32;
+          fiVals.offsRotate                   = param[10].data.d_float;
+          fiVals.enableScaling                = (param[11].data.d_int32 == 0) ? FALSE : TRUE;
+          fiVals.bgLayerIsReference           = (param[12].data.d_int32 == 0) ? FALSE : TRUE;
+          fiVals.removeMidlayers              = (param[13].data.d_int32 == 0) ? FALSE : TRUE;
+          fiVals.addTransformedLayer          = (param[14].data.d_int32 == 0) ? FALSE : TRUE;
 
           fiVals.moveLogFile[0] = '\0';
-          if(param[13].data.d_string != NULL)
+          if(param[15].data.d_string != NULL)
           {
             g_snprintf(fiVals.moveLogFile, sizeof(fiVals.moveLogFile) -1, "%s", param[13].data.d_string);
           }
diff --git a/gap/gap_edge_detection.c b/gap/gap_edge_detection.c
index a7e0ce5..d5a84fd 100644
--- a/gap/gap_edge_detection.c
+++ b/gap/gap_edge_detection.c
@@ -1,5 +1,11 @@
 /* gap_edge_detection.c
  *    by hof (Wolfgang Hofer)
+ *
+ *   This module implements 2 different edge detection methods.
+ *   - One of them [gap_edgeDetection] is used for internal purposes in other GAP features
+ *   - The alternative method [gap_edgeDetectionByShiftBlurDiff]  based on shifted and or blured copies is 
reachable via the Video menu 
+ *     and callable via PDB
+ *
  *  2010/08/10
  *
  */
@@ -58,6 +64,11 @@ typedef struct GapEdgeContext { /* nickname: ectx */
   } GapEdgeContext;
 
 
+static gboolean      p_call_plug_in_gauss_iir2(gint32 imageId, gint32 layerId, gdouble radiusX, gdouble 
radiusY);
+static gint32        p_createEdgeLayer(gint32 imageId, gint32 activeDrawableId, GapEdgeValues *edvalPtr, 
+                         gint32 offset1X, gint32 offset1Y, gint32 offset2X, gint32 offset2Y);
+
+
 /* ----------------------------------
  * p_get_debug_coords_from_guides
  * ----------------------------------
@@ -501,303 +512,293 @@ gint32 gap_edgeDetection(gint32  refDrawableId
 
 
 /*
- * Stuff for the alternative algorithm:
+ * Stuff for the alternative algorithm: (reachable via GUI dialog)
  *
  * ----------------------------------------------
- * Edge detection via Difference to Blurred Copy 
+ * Edge detection via Difference to optional Shifted and Blurred Copy 
  * ----------------------------------------------
- * 
+ * This can be used same as the Difference of Gaussian Blur (DoG) edge detection
+ * that is shiped as one of the GIMP's standard edge detection methods,
+ * but provides additional features based on shifting before compare.
+ * where shift and or blur may be combined or just use only shifting (when blur radius values are 0.0)
+ * or use only blur (when all the shift values are 0)
+ *
  */
- 
- 
- /* ---------------------------------
-  * p_colordiffProcessingForOneRegion
-  * ---------------------------------
-  * subtract RGB channels of the refPR from edgePR
-  * and set edgePR pixels to desaturated result of the subtraction.
-  * (desaturation is done by lightness)
-  */
- static void
- p_colordiffProcessingForOneRegion (const GimpPixelRgn *edgePR
-                     , const GimpPixelRgn *refPR
-                     , const GimpPixelRgn *ref2PR
-                     , gdouble threshold01f, gboolean invert
-                     , gint cx, gint cy
-                     )
- {
-   guint    row;
-   guchar* ref = refPR->data;
-   guchar* ref2 = ref2PR->data;
-   guchar* edge = edgePR->data;
-   gdouble colordiff;
-   
-   if(gap_debug)
-   {
-     printf("p_colordiffProcessingForOneRegion START Edge:w:%d h:%d x:%d y:%d  Ref:w:%d h:%d x:%d y:%d \n"
-        ,edgePR->w
-        ,edgePR->h
-        ,edgePR->x
-        ,edgePR->y
-        ,refPR->w
-        ,refPR->h
-        ,refPR->x
-        ,refPR->y
-         );
-   }
-   
-   for (row = 0; row < edgePR->h; row++)
-   {
-     guint  col;
-     guint  idxref;
-     guint  idxref2;
-     guint  idxedge;
- 
-     idxref = 0;
-     idxref2 = 0;
-     idxedge = 0;
-     for(col = 0; col < edgePR->w; col++)
-     {
-       gint value;
-       gboolean  debugPrint;
-       gdouble colordiff1;
-       gdouble colordiff2;
- 
-       debugPrint = FALSE;
-       
-       if((cx == edgePR->x + col)
-       && (cy == edgePR->y + row))
-       {
-         debugPrint = TRUE;
-         printf("threshold01f:%.4f\n", (float)threshold01f);
-       }
-       
-//        colordiff = gap_colordiff_simple_guchar(&ref[idxref]
-//                      , &edge[idxref]
-//                      , debugPrint    /* debugPrint */
-//                      );
-//        colordiff = gap_colordiff_guchar(&ref[idxref]
-//                             ,  &edge[idxref]
-//                         , 1.15            /* gdouble color sensitivity 1.0 to 2.0 */
-//                             , debugPrint
-//                             );
-       colordiff1 = gap_colordiff_hvmax_guchar(&ref[idxref]
-                   , &edge[idxref]
-                   , debugPrint
-                   );
-       colordiff2 = gap_colordiff_hvmax_guchar(&ref2[idxref]
-                   , &edge[idxref]
-                   , debugPrint
-                   );
-       colordiff = MAX(colordiff1, colordiff2);          
-       value = 0;
-       if(colordiff > threshold01f)
-       {
-         gdouble valuef;
-         
-         valuef = colordiff * 255.0;
-         value = CLAMP(valuef, 0, 255);
-       }
 
-       if (invert)
-       {
-         value = 255 - value;
-       }
-       
-       if(debugPrint)
-       {
-         printf("value: %d\n"
-               ,(int)value
-               );
-       }
-       
-       edge[idxedge]    = value;
-       edge[idxedge +1] = value;
-       edge[idxedge +2] = value;
- 
- 
-       idxref += refPR->bpp;
-       idxref2 += ref2PR->bpp;
-       idxedge += edgePR->bpp;
-     }
- 
-     ref += refPR->rowstride;
-     ref2 += ref2PR->rowstride;
-     edge += edgePR->rowstride;
- 
-   }
- 
- 
- }  /* end p_colordiffProcessingForOneRegion */
- 
- 
- /* ----------------------------------------
-  * p_subtract_ref_layer
-  * ----------------------------------------
-  * setup pixel regions and perform edge detection by subtracting RGB channels 
-  * of the orignal (refDrawable) from the blurred copy (edgeDrawable)
-  * and convert the rgb differences to lightness.
-  *
-  * as result of this processing in the edgeDrawable contains a desaturated
-  * colordifference of the original versus blured copy.
-  */
- static void
- p_subtract_ref_layer(gint32 image_id, GimpDrawable *edgeDrawable, GimpDrawable *refDrawable
-   , gdouble threshold, gint32 shift, gboolean invert)
- {
-   GimpPixelRgn edgePR;
-   GimpPixelRgn refPR;
-   GimpPixelRgn ref2PR;
-   gpointer  pr;
-   gdouble   threshold01f;
-   gdouble   threshold255f;
-   //gint      threshold255;
-   gint      cx;
-   gint      cy;
-   
-   threshold01f = CLAMP((threshold / 100.0), 0, 1);
-   threshold255f = 255.0 * threshold01f;
-   //threshold255 = threshold255f;
-
-   p_get_debug_coords_from_guides(image_id, &cx, &cy);
-   
-   gimp_pixel_rgn_init (&edgePR, edgeDrawable, 0, 0
-                       , edgeDrawable->width - shift, edgeDrawable->height - shift
-                       , TRUE     /* dirty */
-                       , FALSE    /* shadow */
-                        );
-
-   /* start at shifted offset 0/+1 */ 
-   gimp_pixel_rgn_init (&refPR, refDrawable, 0, shift
-                       , refDrawable->width - shift, refDrawable->height - shift
-                       , FALSE     /* dirty */
-                       , FALSE     /* shadow */
-                        );
-   /* start at shifted offset +1/0 */ 
-   gimp_pixel_rgn_init (&ref2PR, refDrawable, shift, 0
-                       , refDrawable->width - shift, refDrawable->height - shift
-                       , FALSE     /* dirty */
-                       , FALSE     /* shadow */
-                        );
- 
-   /* compare pixel areas in tiled portions via pixel region processing loops.
-    */
-   for (pr = gimp_pixel_rgns_register (3, &edgePR, &refPR, &ref2PR);
-        pr != NULL;
-        pr = gimp_pixel_rgns_process (pr))
-   {
-     p_colordiffProcessingForOneRegion (&edgePR, &refPR, &ref2PR, threshold01f, invert, cx, cy);
-   }
- 
-   gimp_drawable_flush (edgeDrawable);
-   gimp_drawable_update (edgeDrawable->drawable_id
-                          , 0, 0
-                          , edgeDrawable->width, edgeDrawable->height
-                          );
- 
- }  /* end  p_subtract_ref_layer */
- 
- 
- 
  /* ---------------------------------
   * p_call_plug_in_gauss_iir2
   * ---------------------------------
   */
- gboolean
- p_call_plug_in_gauss_iir2(gint32 imageId, gint32 edgeLayerId, gdouble radiusX, gdouble radiusY)
- {
-    static char     *l_called_proc = "plug-in-gauss-iir2";
-    GimpParam       *return_vals;
-    int              nreturn_vals;
- 
-    return_vals = gimp_run_procedure (l_called_proc,
-                                  &nreturn_vals,
-                                  GIMP_PDB_INT32,     GIMP_RUN_NONINTERACTIVE,
-                                  GIMP_PDB_IMAGE,     imageId,
-                                  GIMP_PDB_DRAWABLE,  edgeLayerId,
-                                  GIMP_PDB_FLOAT,     radiusX,
-                                  GIMP_PDB_FLOAT,     radiusY,
-                                  GIMP_PDB_END);
- 
-    if (return_vals[0].data.d_status == GIMP_PDB_SUCCESS)
-    {
-       gimp_destroy_params(return_vals, nreturn_vals);
-       return (TRUE);   /* OK */
-    }
-    gimp_destroy_params(return_vals, nreturn_vals);
-    printf("GAP: Error: PDB call of %s failed, d_status:%d %s\n"
+static gboolean
+p_call_plug_in_gauss_iir2(gint32 imageId, gint32 layerId, gdouble radiusX, gdouble radiusY)
+{
+  static char     *l_called_proc = "plug-in-gauss-iir2";
+  GimpParam       *return_vals;
+  int              nreturn_vals;
+  
+  if(gap_debug)
+  {
+    printf("calling %s  imageId:%d layerId:%d rX:%f rY:%f\n"
        , l_called_proc
-       , (int)return_vals[0].data.d_status
-       , gap_status_to_string(return_vals[0].data.d_status)
+       , (int)imageId
+       , (int)layerId
+       , (float)radiusX
+       , (float)radiusY
        );
-    return(FALSE);
- }       /* end p_call_plug_in_gauss_iir2 */
+  }
  
+  return_vals = gimp_run_procedure (l_called_proc,
+                                &nreturn_vals,
+                                GIMP_PDB_INT32,     GIMP_RUN_NONINTERACTIVE,
+                                GIMP_PDB_IMAGE,     imageId,
+                                GIMP_PDB_DRAWABLE,  layerId,
+                                GIMP_PDB_FLOAT,     radiusX,
+                                GIMP_PDB_FLOAT,     radiusY,
+                                GIMP_PDB_END);
  
+  if (return_vals[0].data.d_status == GIMP_PDB_SUCCESS)
+  {
+     gimp_destroy_params(return_vals, nreturn_vals);
+     return (TRUE);   /* OK */
+  }
+  gimp_destroy_params(return_vals, nreturn_vals);
+  printf("GAP: Error: PDB call of %s failed, d_status:%d %s\n"
+     , l_called_proc
+     , (int)return_vals[0].data.d_status
+     , gap_status_to_string(return_vals[0].data.d_status)
+     );
+  return(FALSE);
+}       /* end p_call_plug_in_gauss_iir2 */
  
  /* ---------------------------------
-  * gap_edgeDetectionByBlurDiff
+  * p_createEdgeLayer
   * ---------------------------------
+  * returns the layerId of the created edge layer.
+  *
+  * inserts 3 temporarty layers above the activeDrawableId:
+  *
+  *   shiftLayer2Id        (top, SUBTRACT mode, optionally shifted & blured)
+  *   shiftLayer1Id        (NORMAL mode, optionally shifted & blured)
+  *   blackLayerId         (NORMAL mode, filled black)
+  *   activeDrawableId     (base layer for the 3 duplicates)
+  *
+  * and then merges down the shifted layers to the resulting edgeLayer.
+  *
+  * Note that the black_layer provides "area without edges" information on the borders
+  * and provides black background in desired size in the 2nd mergeDown operation.
   */
- gint32
- gap_edgeDetectionByBlurDiff(gint32 activeDrawableId, gdouble blurRadius, gdouble blurResultRadius
-   , gdouble threshold, gint32 shift, gboolean doLevelsAutostretch
-   , gboolean invert)
- {
-   //gint32 blurLayerId;
-   gint32 edgeLayerId;
-   gint32 imageId;
-   GimpDrawable *edgeDrawable;
-   GimpDrawable *refDrawable;
-   GimpDrawable *blurDrawable;
+static gint32
+p_createEdgeLayer(gint32 imageId, gint32 activeDrawableId, GapEdgeValues *edvalPtr, 
+   gint32 offset1X, gint32 offset1Y, gint32 offset2X, gint32 offset2Y)
+{
+  gint32 shiftLayer1Id;
+  gint32 shiftLayer2Id;
+  gint32 blackLayerId;
+  gint32 mergedLayerId;
+  gint32 edgeLayerId;
+  
+  if(gap_debug)
+  {
+    printf("p_createEdgeLayer: START imageId:%d  activeDrawableId:%d offset1(X,Y): (%d %d) offset2(X,Y): (%d 
%d)\n"
+      , (int)imageId
+      , (int)activeDrawableId
+      , (int)offset1X
+      , (int)offset1Y
+      , (int)offset2X
+      , (int)offset2Y
+      );
+  }
+
+  gimp_image_set_active_layer(imageId, activeDrawableId); 
+
+  blackLayerId = gimp_layer_copy(activeDrawableId);
+  gimp_image_insert_layer (imageId, blackLayerId, 0, -1 /* stackposition -1 above th active drawable */ );
+  gimp_item_set_name(blackLayerId, "blackLayer");
+    
+  /* initial fill the edge base layer with black opaque color */
+  gap_layer_clear_to_color(blackLayerId
+                             ,0.0 /* red */
+                             ,0.0 /* green */
+                             ,0.0 /* blue */
+                             ,1.0 /* alpha */
+                           );
+                           
+  gimp_image_set_active_layer(imageId, blackLayerId); 
+  
+  shiftLayer1Id = gimp_layer_copy(activeDrawableId);
+  gimp_image_insert_layer (imageId, shiftLayer1Id, 0, -1 /* stackposition -1 above th active blackLayerId */ 
);
+  gimp_layer_set_offsets(shiftLayer1Id, offset1X, offset1Y);
+  gimp_item_set_name(shiftLayer1Id, "shiftLayer1");
+
+  if ((edvalPtr->blurRadius1X > 0.0) || (edvalPtr->blurRadius1Y > 0.0))
+  {
+    p_call_plug_in_gauss_iir2(imageId, shiftLayer1Id, edvalPtr->blurRadius1X, edvalPtr->blurRadius1Y);
+  }
+
+  gimp_image_set_active_layer(imageId, shiftLayer1Id); 
+
+  
+  shiftLayer2Id = gimp_layer_copy(activeDrawableId);
+  gimp_image_insert_layer (imageId, shiftLayer2Id, 0, -1 /* stackposition -1 above th active shiftLayer1Id 
*/ );
+  gimp_layer_set_offsets(shiftLayer2Id, offset2X, offset2Y);
+  gimp_layer_set_mode(shiftLayer2Id, GIMP_SUBTRACT_MODE);
+  gimp_item_set_name(shiftLayer2Id, "shiftLayer2");
+
+  if ((edvalPtr->blurRadius2X > 0.0) || (edvalPtr->blurRadius2Y > 0.0))
+  {
+    p_call_plug_in_gauss_iir2(imageId, shiftLayer2Id, edvalPtr->blurRadius2X, edvalPtr->blurRadius2Y);
+  }
+
+
+
+
+  if(gap_debug)
+  {
+    printf("p_createEdgeLayer: activeDrawableId:%d blackLayerId:%d shiftLayer1Id:%d shiftLayer2Id:%d\n"
+      , (int)activeDrawableId
+      , (int)blackLayerId
+      , (int)shiftLayer1Id
+      , (int)shiftLayer2Id
+      );
+  }
+  
+  /* merge down topmost shiftLayer2Id into shiftLayer1Id */
+  mergedLayerId = gimp_image_merge_down(imageId, shiftLayer2Id, GIMP_EXPAND_AS_NECESSARY);
+  gimp_item_set_name(mergedLayerId, "mergedLayer");
+
+  if(gap_debug)
+  {
+    printf("p_createEdgeLayer: mergedLayerId=%d\n", mergedLayerId);
+  }
+
+  /* further merge down  into blackLayerId */
+  edgeLayerId = gimp_image_merge_down(imageId, mergedLayerId, GIMP_CLIP_TO_BOTTOM_LAYER);
+  gimp_item_set_name(edgeLayerId, "edgeLayer");
+
+  gimp_image_set_active_layer(imageId, activeDrawableId); 
+
+  return (edgeLayerId);
+
+  
+}  /* end p_createEdgeLayer */
 
  
- 
- 
-   imageId = gimp_item_get_image(activeDrawableId);
- 
-   edgeLayerId = gimp_layer_copy(activeDrawableId);
-   gimp_image_insert_layer (imageId, edgeLayerId, 0, 0 /* stackposition */ );
- 
-   edgeDrawable = gimp_drawable_get(edgeLayerId);
-   refDrawable = gimp_drawable_get(activeDrawableId);
- 
-   if(blurRadius > 0.0)
-   {
-     p_call_plug_in_gauss_iir2(imageId, edgeLayerId, blurRadius, blurRadius);
-   }
-   
-   blurDrawable = NULL;
-//    blurLayerId = gimp_layer_copy(edgeLayerId);
-//    gimp_image_insert_layer (imageId, blurLayerId, 0, 0 /* stackposition */ );
-//    blurDrawable = gimp_drawable_get(blurLayerId);
-
-   p_subtract_ref_layer(imageId, edgeDrawable, refDrawable, threshold, shift, invert);
-   //p_subtract_ref_layer(imageId, edgeDrawable, blurDrawable, threshold, shift, invert);
-   if (doLevelsAutostretch)
-   {
-     gimp_levels_stretch(edgeLayerId);
-   }
-
-   if(blurResultRadius > 0.0)
-   {
-     p_call_plug_in_gauss_iir2(imageId, edgeLayerId, blurResultRadius, blurResultRadius);
-   }
- 
-   if(refDrawable)
-   {
-     gimp_drawable_detach(refDrawable);
-   }
-   
-   if(edgeDrawable)
-   {
-     gimp_drawable_detach(edgeDrawable);
-   }
-   if(blurDrawable)
-   {
-     gimp_drawable_detach(blurDrawable);
-   }
- 
-   return (edgeLayerId);
-   
- }  /* end gap_edgeDetectionByBlurDiff */
+/* ---------------------------------
+ * gap_edgeDetectionByShiftBlurDiff
+ * ---------------------------------
+ */
+gint32
+gap_edgeDetectionByShiftBlurDiff(gint32 activeDrawableId, GapEdgeValues *edvalPtr)
+{
+  gint32 edgeLayerId;
+  gint32 edgeHorizontalLayerId;
+  gint32 edgeVerticalLayerId;
+  gint32 imageId;
+  gint32 retLayerId;
+  gint   src_offset_x;
+  gint   src_offset_y;
+
+
+  imageId = gimp_item_get_image(activeDrawableId);
+  gimp_drawable_offsets(activeDrawableId, &src_offset_x, &src_offset_y);
+  
+  
+  
+  edgeHorizontalLayerId = p_createEdgeLayer(imageId, activeDrawableId, edvalPtr 
+     , src_offset_x - edvalPtr->shiftLeft   /* offset1X */
+     , src_offset_y                         /* offset1Y */
+     , src_offset_x + edvalPtr->shiftRight  /* offset2X */
+     , src_offset_y                         /* offset2Y */
+     );
+
+
+  edgeVerticalLayerId = -1;
+  if ((edvalPtr->shiftUp != 0)
+  ||  (edvalPtr->shiftDown != 0)
+  ||  (edvalPtr->blurRadius1X > 0.0)
+  ||  (edvalPtr->blurRadius1Y > 0.0)
+  ||  (edvalPtr->blurRadius2X > 0.0)
+  ||  (edvalPtr->blurRadius2Y > 0.0))
+  {
+  
+    edgeVerticalLayerId = p_createEdgeLayer(imageId, activeDrawableId, edvalPtr 
+     , src_offset_x                        /* offset1X */
+     , src_offset_y - edvalPtr->shiftUp    /* offset1Y */
+     , src_offset_x                        /* offset2X */
+     , src_offset_y + edvalPtr->shiftDown  /* offset2Y */
+     );
+
+    gimp_layer_set_mode(edgeHorizontalLayerId, GIMP_LIGHTEN_ONLY_MODE);
+
+    /* for operation in both directions
+     * the layerstack at this point shall look like this:
+     *
+     *  edgeHorizontalLayerId (topmost)
+     *  edgeVerticalLayerId
+     *  activeDrawableId      
+     *
+     */
+
+    
+    /* merge edgeHorizontalLayerId down into edgeVerticalLayerId 
+     * note that the upper layer edgeHorizontalLayerId has GIMP_LIGHTEN_ONLY_MODE mode
+     * so the result has a mix of both horizontal and vertiacal detected edge lines that are brighter
+     * than the other areas in both edgeHorizontalLayerId and edgeVerticalLayerId)
+     */
+    edgeLayerId = gimp_image_merge_down(imageId, edgeHorizontalLayerId, GIMP_CLIP_TO_BOTTOM_LAYER);
+
+  }
+  else
+  {
+    edgeLayerId = edgeHorizontalLayerId;
+  }
+  
+  
+  /* postprocessing options */
+  if (edvalPtr->autoLevels)
+  {
+    /* for gimp-2.8.xx use the old name gimp_levels_stretch (that is deprected since gimp-2.9) */
+    gimp_levels_stretch(edgeLayerId);
+    
+    // GIMP-2.9 gimp_drawable_levels_stretch(edgeLayerId);
+  }
+
+  if (edvalPtr->desaturate)
+  {
+    /* for gimp-2.8.xx use the old name gimp_desaturate_full (that is deprected since gimp-2.9) */
+    gimp_desaturate_full(edgeLayerId, GIMP_DESATURATE_LIGHTNESS);
+
+    // GIMP-2.9 calls should look like this:
+    //  desaturatedDrawableId = gimp_drawable_desaturate(drawable_id
+    //                       , cuvals->desaturate_mode
+    //                       );
+  }
+
+  if (edvalPtr->invert)
+  {
+    /* for gimp-2.8.xx use the old name gimp_levels_stretch (that is deprected since gimp-2.9) */
+    gimp_invert(edgeLayerId);
+    
+    // GIMP-2.9  gimp_drawable_invert(edgeLayerId);
+  }
+
+  if (edvalPtr->createEdgeAsNewLayer)
+  {
+    retLayerId = edgeLayerId;
+  }
+  else
+  {
+    /* in case no new layer is requested replace the active layer */
+    gap_layer_copy_content (activeDrawableId,  edgeLayerId /* src_drawable_id */ );
+  
+    /* and delete the edgeLayerId after copying */
+    gimp_image_remove_layer(imageId, edgeLayerId);
+    
+    retLayerId = activeDrawableId;
+  }
+  
+  
+  return (retLayerId);
+  
+}  /* end gap_edgeDetectionByShiftBlurDiff */
  
diff --git a/gap/gap_edge_detection.h b/gap/gap_edge_detection.h
index d6cecd2..abaa418 100644
--- a/gap/gap_edge_detection.h
+++ b/gap/gap_edge_detection.h
@@ -36,7 +36,21 @@
 #include "gtk/gtk.h"
 #include "libgimp/gimp.h"
 
+typedef struct GapEdgeValues { /* nickname: edval */
+     gdouble       blurRadius1X;
+     gdouble       blurRadius1Y;
+     gdouble       blurRadius2X;
+     gdouble       blurRadius2Y;
+     gint32        shiftLeft;
+     gint32        shiftRight;
+     gint32        shiftUp;
+     gint32        shiftDown;
+     gboolean      autoLevels;
+     gboolean      desaturate;
+     gboolean      invert;
+     gboolean      createEdgeAsNewLayer;
 
+  } GapEdgeValues;
 
 /* ----------------------------------------
  * gap_edgeDetection
@@ -56,18 +70,18 @@ gint32 gap_edgeDetection(gint32  refDrawableId
 
 
 /* ---------------------------------
- * gap_edgeDetectionByBlurDiff
+ * gap_edgeDetectionByShiftBlurDiff
  * ---------------------------------
- * create a new layer representing edges of the specified activeDrawableId.
- * The edge detection is done based on difference versus a blured
- * copy of the activeDrawableId.
- * (optionalyy with auto streched levels)
- * returns the drawable id of a newly created layer
+ * replace the content of specified activeDrawableId
+ * with results of edgeDetection.
+ * The edge detection is done based on differences of copies of the original activeLayer
+ * optionally shifted by small amount (1 or 2 pixels) horizontally and/or vertically and optionally blured.
+ *
+ * the edge detection result is furter optionally auto streched (levels) and inverted.
+ * returns the drawable id of the edgeMask
  */
 gint32
-gap_edgeDetectionByBlurDiff(gint32 activeDrawableId, gdouble blurRadius, gdouble blurResultRadius
-   , gdouble threshold, gint32 shift, gboolean doLevelsAutostretch
-   , gboolean invert);
+gap_edgeDetectionByShiftBlurDiff(gint32 activeDrawableId, GapEdgeValues *edvalPtr);
 
 
 
diff --git a/gap/gap_edge_detection_dialog.c b/gap/gap_edge_detection_dialog.c
new file mode 100644
index 0000000..8c8bef4
--- /dev/null
+++ b/gap/gap_edge_detection_dialog.c
@@ -0,0 +1,1280 @@
+/* gap_edge_detection_dialog.c
+ *  by hof (Wolfgang Hofer)
+ *
+ * GAP ... Gimp Animation Plugins
+ *
+ * This Module contains the GAP Edge detection Filter dialog
+ *
+ */
+/* 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, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+/* revision history:
+ * gimp    2.8.xx; 2017/03/10  hof: creation
+ */
+
+
+/* SYTEM (UNIX) includes */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* GIMP includes */
+#include <gtk/gtk.h>
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+/* GAP includes */
+#include "config.h"
+#include "gap-intl.h"
+#include "gap_stock.h"
+
+#include "gap_image.h"
+#include "gap_layer_copy.h"
+#include "gap_lib.h"
+#include "gap_pdb_calls.h"
+#include "gap_edge_detection.h"
+#include "gap_edge_detection_dialog.h"
+
+/* instant apply is implemented via timer, configured to fire 10 times per second (100 msec)
+ * this collects instant_apply_requests set by other widget callbacks and events
+ * and then update only once.
+ * The timer is completely removed, when instant_preview is OFF
+ * instant_preview requires much CPU and IO power especially on big images
+ * and images with many layers
+ */
+#define INSTANT_TIMERINTERVAL_MILLISEC  100
+
+
+#define GAP_EDGE_RESPONSE_RESET 1
+
+
+/*  some definitions   */
+
+#define SCALE_WIDTH        180
+#define SPIN_BUTTON_WIDTH   75
+
+
+/* ------------------------
+ * gap DEBUG switch
+ * ------------------------
+ */
+
+/* int gap_debug = 1; */    /* print debug infos */
+/* int gap_debug = 0; */    /* 0: dont print debug infos */
+
+extern int gap_debug;
+
+typedef struct GapEdgeDetectGuiParams {  /* nick edguiPtr */
+  gboolean     run_flag;
+  gint32       image_id;
+  gint32       drawable_id;   /* drawable id of the invoking layer (used both for preview and apply) */
+  gint32       layer_id;      /* layer to apply the bluebox effect */
+
+  gint32       pv_image_id;      /* id of the preview image */
+  gint32       pv_layer_id;      /* layer to apply the edge detection filter */
+  gint32       pv_display_id;    /* id of the disply connected to the preview image with pv_image_id */
+  gdouble      pv_master_layer_id;  /* copy of the original layer scaled to preview size */
+  gdouble      pv_size_percent;  /* size od the preview (ignored if run_flag == TRUE) */
+  gboolean     instant_preview;  /* instant apply on preview image */
+  gint32       instant_timertag;
+  gboolean     instant_apply_request;
+
+
+  /* GUI widget pointers */
+  GtkWidget *shell;  
+  GtkWidget *master_table;
+  GtkObject *blurRadius1X_adj;
+  GtkObject *blurRadius1Y_adj;
+  GtkObject *blurRadius2X_adj;
+  GtkObject *blurRadius2Y_adj;
+  GtkObject *shiftLeft_adj;
+  GtkObject *shiftRight_adj;
+  GtkObject *shiftUp_adj;
+  GtkObject *shiftDown_adj;
+  GtkWidget *autoLevels_toggle;  
+  GtkWidget *desaturate_toggle;  
+  GtkWidget *invert_toggle;  
+  GtkWidget *createEdgeAsNewLayer_toggle;  
+  GtkWidget *instant_preview_toggle;
+  // GtkWidget *feather_edges_toggle;  
+  // GtkObject *grow_adj;
+  // GtkObject *feather_radius_adj;
+
+  
+  GdkCursor *cursor_wait;
+  GdkCursor *cursor_acitve;
+
+  GapEdgeValues    *vals;
+  
+} GapEdgeDetectGuiParams;
+
+
+
+/* -----------------------
+ * procedure declarations
+ * -----------------------
+ */
+static void       p_edge_init_default_vals(GapEdgeValues *edvalPtr);
+static GtkWidget * gap_edge_create_dialog (GapEdgeDetectGuiParams *edguiPtr);
+static void       p_reset_callback(GtkWidget *w, GapEdgeDetectGuiParams *edguiPtr);
+static void       p_quit_callback(GtkWidget *w, GapEdgeDetectGuiParams *edguiPtr);
+static void       p_edge_response(GtkWidget *w, gint response_id, GapEdgeDetectGuiParams *edguiPtr);
+static void       p_set_waiting_cursor(GapEdgeDetectGuiParams *edguiPtr);
+static void       p_set_active_cursor(GapEdgeDetectGuiParams *edguiPtr);
+static void       p_apply_callback(GtkWidget *w, GapEdgeDetectGuiParams *edguiPtr);
+static void       p_gdouble_adjustment_callback(GtkObject *obj, gpointer val);
+static void       p_gint32_adjustment_callback(GtkObject *obj, gpointer val);
+static void       p_toggle_update_callback(GtkWidget *widget, gint *val);
+
+static void       p_set_instant_apply_request(GapEdgeDetectGuiParams *edguiPtr);
+static void       p_install_timer(GapEdgeDetectGuiParams *edguiPtr);
+static void       p_remove_timer(GapEdgeDetectGuiParams *edguiPtr);
+static void       p_instant_timer_callback (gpointer   user_data);
+static void       p_clear_GapEdgeDetectGuiParams(GapEdgeDetectGuiParams *edguiPtr);
+static void       p_remove_additonal_layers_from_preview_image(GapEdgeDetectGuiParams *edguiPtr);
+static gboolean   p_edge_detect_apply(GapEdgeDetectGuiParams *edguiPtr);
+
+/* ---------------------------------
+ * p_edge_init_default_vals
+ * ---------------------------------
+ */
+static void
+p_edge_init_default_vals(GapEdgeValues *edvalPtr)
+{
+  edvalPtr->blurRadius1X = 0.0;
+  edvalPtr->blurRadius1Y  = 0.0;;
+  edvalPtr->blurRadius2X = 0.0;
+  edvalPtr->blurRadius2Y = 0.0;
+  edvalPtr->shiftLeft = 1;
+  edvalPtr->shiftRight = 1;
+  edvalPtr->shiftUp = 1;
+  edvalPtr->shiftDown = 1;
+  edvalPtr->autoLevels = TRUE;
+  edvalPtr->desaturate = TRUE;
+  edvalPtr->invert = FALSE;
+  edvalPtr->createEdgeAsNewLayer = TRUE;
+
+}  /* end p_edge_init_default_vals */
+
+/* ---------------------------------
+ * deliver new values (initialized with the defaault settings)
+ * ---------------------------------
+ */
+GapEdgeValues * 
+gap_edge_edval_new(gint32 acgtivDrawableId)
+{
+  GapEdgeValues *edvalPtr;
+  
+  edvalPtr = g_new(GapEdgeValues, 1);
+  p_edge_init_default_vals(edvalPtr);
+  
+  return (edvalPtr);
+}
+
+
+static void
+p_clear_GapEdgeDetectGuiParams(GapEdgeDetectGuiParams *edguiPtr)
+{
+  edguiPtr->shell = NULL;
+
+  edguiPtr->instant_preview = FALSE;
+  edguiPtr->instant_apply_request = FALSE;
+  edguiPtr->instant_timertag = -1;
+  edguiPtr->master_table = NULL;
+  edguiPtr->blurRadius1X_adj = NULL;
+  edguiPtr->blurRadius1Y_adj = NULL;
+  edguiPtr->blurRadius2X_adj = NULL;
+  edguiPtr->blurRadius2Y_adj = NULL;
+  edguiPtr->shiftLeft_adj = NULL;
+  edguiPtr->shiftRight_adj = NULL;
+  edguiPtr->shiftUp_adj = NULL;
+  edguiPtr->shiftDown_adj = NULL;
+  edguiPtr->autoLevels_toggle = NULL;
+  edguiPtr->desaturate_toggle = NULL;
+  edguiPtr->invert_toggle = NULL;
+  edguiPtr->createEdgeAsNewLayer_toggle = NULL;
+  edguiPtr->instant_preview_toggle = NULL;
+
+  edguiPtr->cursor_wait = NULL;
+  edguiPtr->cursor_acitve = NULL;
+  
+  edguiPtr->pv_image_id = -1;
+  edguiPtr->pv_layer_id = -1;
+  edguiPtr->pv_display_id = -1;
+  edguiPtr->pv_master_layer_id = -1;
+  
+  edguiPtr->pv_size_percent = 100;
+}
+
+/* ---------------------------------
+ * the Edge Detect MAIN dialog
+ * ---------------------------------
+ * return -1 on Error
+ *         0 .. OK
+ */
+int
+gap_edge_dialog(gint32 activeDrawableId, GapEdgeValues *edvalPtr)
+{
+  GapEdgeDetectGuiParams *edguiPtr;
+  
+  if(gap_debug) 
+  {
+    printf("\nSTART gap_edge_dialog\n");
+  }
+  
+  edguiPtr = g_new(GapEdgeDetectGuiParams, 1);
+  p_clear_GapEdgeDetectGuiParams(edguiPtr);
+  edguiPtr->drawable_id = activeDrawableId;
+  edguiPtr->image_id = gimp_item_get_image(activeDrawableId);
+  edguiPtr->vals = edvalPtr;
+
+  gimp_ui_init ("gap_edge_detect", FALSE);
+  gap_stock_init();
+
+  edguiPtr->run_flag = FALSE;
+  edguiPtr->cursor_wait = gdk_cursor_new (GDK_WATCH);
+  edguiPtr->cursor_acitve = NULL; /* use the default cursor */
+
+  gap_edge_create_dialog(edguiPtr);
+  gtk_widget_show (edguiPtr->shell);
+
+
+  if(gap_debug) printf("gap_edge_dialog.c BEFORE  gtk_main\n");
+  gtk_main ();
+  gdk_flush ();
+
+  if(gap_debug) printf("gap_edge_dialog.c END gap_edge_dialog\n");
+
+
+  if(edguiPtr->run_flag)
+  {
+    g_free(edguiPtr);
+    return 0;  /* OK, request to run the bluebox effect now */
+  }
+  g_free(edguiPtr);
+  return -1;  /* for cancel or close dialog without run request */
+
+}  /* end gap_edge_dialog */
+
+
+/* ------------------------
+ * gap_edge_create_dialog
+ * ------------------------
+ */
+static GtkWidget *
+gap_edge_create_dialog (GapEdgeDetectGuiParams *edguiPtr)
+{
+  GtkWidget *dlg;
+  GtkWidget *main_vbox;
+  GtkWidget *frame;
+  GtkWidget *table;
+  GtkWidget *label;
+  GtkWidget *button;
+  GtkWidget *check_button;
+  GtkWidget *hseparator;
+  gint       row;
+  GtkObject *adj;
+  
+  if(gap_debug)
+  {
+    printf("gap_edge_create_dialog START\n");
+  }
+
+  dlg = gimp_dialog_new (_("Edge Detect (DoSoG)"), GAP_EDGE_PLUGIN_NAME,
+                         NULL, 0,
+                         gimp_standard_help_func, GAP_EDGE_HELP_ID,
+
+                         GIMP_STOCK_RESET, GAP_EDGE_RESPONSE_RESET,
+                         GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+                         GTK_STOCK_OK,     GTK_RESPONSE_OK,
+                         NULL);
+
+  edguiPtr->shell = dlg;
+
+  g_signal_connect (G_OBJECT (edguiPtr->shell), "response",
+                    G_CALLBACK (p_edge_response),
+                    edguiPtr);
+
+
+  main_vbox = gtk_vbox_new (FALSE, 4);
+  gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 6);
+  gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dlg)->vbox), main_vbox);
+
+  /*  the frame  */
+
+  frame = gimp_frame_new (_("Edge Detect by Shift and Blur"));
+  gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0);
+  gtk_widget_show (frame);
+
+  table = gtk_table_new (10, 3, FALSE);
+  edguiPtr->master_table = table;
+  gtk_container_set_border_width (GTK_CONTAINER (table), 4);
+  gtk_table_set_col_spacings (GTK_TABLE (table), 4);
+  gtk_table_set_row_spacings (GTK_TABLE (table), 2);
+  gtk_container_add (GTK_CONTAINER (frame), table);
+  gtk_widget_show (table);
+
+  row = 0;
+
+  adj = gimp_scale_entry_new (GTK_TABLE (edguiPtr->master_table), 0, row++,
+                              _("Blur R1 (X):"), SCALE_WIDTH, SPIN_BUTTON_WIDTH,
+                              edguiPtr->vals->blurRadius1X,
+                              0.0, 30.0,     /* lower/upper */
+                              0.1, 1.0,      /* step, page */
+                              1,              /* digits */
+                              TRUE,           /* constrain */
+                              0.0, 1.0,       /* lower/upper unconstrained */
+                              _("Blur radius 1 X direction"), NULL);
+  edguiPtr->blurRadius1X_adj = adj;
+  g_object_set_data(G_OBJECT(adj), "edguiPtr", edguiPtr);
+  g_signal_connect (adj, "value_changed",
+                    G_CALLBACK (p_gdouble_adjustment_callback),
+                    &edguiPtr->vals->blurRadius1X);
+
+  row++;
+
+
+  adj = gimp_scale_entry_new (GTK_TABLE (edguiPtr->master_table), 0, row++,
+                              _("Blur R1 (Y):"), SCALE_WIDTH, SPIN_BUTTON_WIDTH,
+                              edguiPtr->vals->blurRadius1Y,
+                              0.0, 30.0,     /* lower/upper */
+                              0.1, 1.0,      /* step, page */
+                              1,              /* digits */
+                              TRUE,           /* constrain */
+                              0.0, 1.0,       /* lower/upper unconstrained */
+                              _("Blur radius 1 Y direction"), NULL);
+  edguiPtr->blurRadius1Y_adj = adj;
+  g_object_set_data(G_OBJECT(adj), "edguiPtr", edguiPtr);
+  g_signal_connect (adj, "value_changed",
+                    G_CALLBACK (p_gdouble_adjustment_callback),
+                    &edguiPtr->vals->blurRadius1Y);
+
+
+  row++;
+
+  adj = gimp_scale_entry_new (GTK_TABLE (edguiPtr->master_table), 0, row++,
+                              _("Blur R2 (X):"), SCALE_WIDTH, SPIN_BUTTON_WIDTH,
+                              edguiPtr->vals->blurRadius2X,
+                              0.0, 30.0,     /* lower/upper */
+                              0.1, 1.0,      /* step, page */
+                              1,              /* digits */
+                              TRUE,           /* constrain */
+                              0.0, 1.0,       /* lower/upper unconstrained */
+                              _("Blur radius 2 X direction"), NULL);
+  edguiPtr->blurRadius2X_adj = adj;
+  g_object_set_data(G_OBJECT(adj), "edguiPtr", edguiPtr);
+  g_signal_connect (adj, "value_changed",
+                    G_CALLBACK (p_gdouble_adjustment_callback),
+                    &edguiPtr->vals->blurRadius2X);
+
+
+
+
+
+  row++;
+  
+  adj = gimp_scale_entry_new (GTK_TABLE (edguiPtr->master_table), 0, row++,
+                                _("Blur R2 (Y):"), SCALE_WIDTH, SPIN_BUTTON_WIDTH,
+                                edguiPtr->vals->blurRadius2Y,
+                                0.0, 30.0,     /* lower/upper */
+                                0.1, 1.0,      /* step, page */
+                                1,              /* digits */
+                                TRUE,           /* constrain */
+                                0.0, 1.0,       /* lower/upper unconstrained */
+                                _("Blur radius 2 Y direction"), NULL);
+   edguiPtr->blurRadius2Y_adj = adj;
+   g_object_set_data(G_OBJECT(adj), "edguiPtr", edguiPtr);
+   g_signal_connect (adj, "value_changed",
+                     G_CALLBACK (p_gdouble_adjustment_callback),
+                     &edguiPtr->vals->blurRadius2Y);
+                    
+                    
+  row++;
+  
+  adj = gimp_scale_entry_new (GTK_TABLE (edguiPtr->master_table), 0, row++,
+                                _("Shift Left:"), SCALE_WIDTH, SPIN_BUTTON_WIDTH,
+                                edguiPtr->vals->shiftLeft,
+                                0.0, 5.0,     /* lower/upper */
+                                1.0, 1.0,      /* step, page */
+                                0,              /* digits */
+                                TRUE,           /* constrain */
+                                0.0, 1.0,       /* lower/upper unconstrained */
+                                _("Shift left by n pixels"), NULL);
+   edguiPtr->shiftLeft_adj = adj;
+   g_object_set_data(G_OBJECT(adj), "edguiPtr", edguiPtr);
+   g_signal_connect (adj, "value_changed",
+                     G_CALLBACK (p_gint32_adjustment_callback),
+                     &edguiPtr->vals->shiftLeft);
+
+
+  row++;
+  
+  adj = gimp_scale_entry_new (GTK_TABLE (edguiPtr->master_table), 0, row++,
+                                _("Shift Right:"), SCALE_WIDTH, SPIN_BUTTON_WIDTH,
+                                edguiPtr->vals->shiftRight,
+                                0.0, 5.0,     /* lower/upper */
+                                1.0, 1.0,      /* step, page */
+                                0,              /* digits */
+                                TRUE,           /* constrain */
+                                0.0, 1.0,       /* lower/upper unconstrained */
+                                _("Shift right by n pixels"), NULL);
+   edguiPtr->shiftRight_adj = adj;
+   g_object_set_data(G_OBJECT(adj), "edguiPtr", edguiPtr);
+   g_signal_connect (adj, "value_changed",
+                     G_CALLBACK (p_gint32_adjustment_callback),
+                     &edguiPtr->vals->shiftRight);
+
+  row++;
+  
+  adj = gimp_scale_entry_new (GTK_TABLE (edguiPtr->master_table), 0, row++,
+                                _("Shift Up:"), SCALE_WIDTH, SPIN_BUTTON_WIDTH,
+                                edguiPtr->vals->shiftUp,
+                                0.0, 5.0,     /* lower/upper */
+                                1.0, 1.0,      /* step, page */
+                                0,              /* digits */
+                                TRUE,           /* constrain */
+                                0.0, 1.0,       /* lower/upper unconstrained */
+                                _("Shift up by n pixels"), NULL);
+   edguiPtr->shiftUp_adj = adj;
+   g_object_set_data(G_OBJECT(adj), "edguiPtr", edguiPtr);
+   g_signal_connect (adj, "value_changed",
+                     G_CALLBACK (p_gint32_adjustment_callback),
+                     &edguiPtr->vals->shiftUp);
+
+
+  row++;
+  
+  adj = gimp_scale_entry_new (GTK_TABLE (edguiPtr->master_table), 0, row++,
+                                _("Shift Down:"), SCALE_WIDTH, SPIN_BUTTON_WIDTH,
+                                edguiPtr->vals->shiftDown,
+                                0.0, 5.0,     /* lower/upper */
+                                1.0, 1.0,      /* step, page */
+                                0,              /* digits */
+                                TRUE,           /* constrain */
+                                0.0, 1.0,       /* lower/upper unconstrained */
+                                _("Shift down by n pixels"), NULL);
+   edguiPtr->shiftDown_adj = adj;
+   g_object_set_data(G_OBJECT(adj), "edguiPtr", edguiPtr);
+   g_signal_connect (adj, "value_changed",
+                     G_CALLBACK (p_gint32_adjustment_callback),
+                     &edguiPtr->vals->shiftDown);
+
+  row++;
+
+  label = gtk_label_new(_("Auto Levels:"));
+  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
+  gtk_table_attach(GTK_TABLE (table), label, 0, 1, row, row + 1
+                  , GTK_FILL, GTK_FILL, 0, 0);
+  gtk_widget_show(label);
+
+  /* check button */
+  check_button = gtk_check_button_new_with_label (" ");
+  gtk_table_attach ( GTK_TABLE (table), check_button, 1, 3, row, row+1, GTK_FILL, 0, 0, 0);
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button),
+                                edguiPtr->vals->autoLevels);
+  gimp_help_set_help_data(check_button, _("ON: apply auto strech levels"), NULL);
+  gtk_widget_show(check_button);
+  edguiPtr->autoLevels_toggle = check_button;
+  g_object_set_data(G_OBJECT(check_button), "edguiPtr", edguiPtr);
+  g_signal_connect (G_OBJECT (check_button), "toggled",
+                    G_CALLBACK (p_toggle_update_callback),
+                    &edguiPtr->vals->autoLevels);
+
+  row++;
+
+
+  label = gtk_label_new(_("Desaturate:"));
+  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
+  gtk_table_attach(GTK_TABLE (table), label, 0, 1, row, row + 1
+                  , GTK_FILL, GTK_FILL, 0, 0);
+  gtk_widget_show(label);
+
+  /* check button */
+  check_button = gtk_check_button_new_with_label (" ");
+  gtk_table_attach ( GTK_TABLE (table), check_button, 1, 3, row, row+1, GTK_FILL, 0, 0, 0);
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button),
+                                edguiPtr->vals->desaturate);
+  gimp_help_set_help_data(check_button, _("ON: Desaturate result to shades of grey"), NULL);
+  gtk_widget_show(check_button);
+  edguiPtr->desaturate_toggle = check_button;
+  g_object_set_data(G_OBJECT(check_button), "edguiPtr", edguiPtr);
+  g_signal_connect (G_OBJECT (check_button), "toggled",
+                    G_CALLBACK (p_toggle_update_callback),
+                    &edguiPtr->vals->desaturate);
+
+
+  row++;
+
+  label = gtk_label_new(_("Invert:"));
+  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
+  gtk_table_attach(GTK_TABLE (table), label, 0, 1, row, row + 1
+                  , GTK_FILL, GTK_FILL, 0, 0);
+  gtk_widget_show(label);
+
+  /* check button */
+  check_button = gtk_check_button_new_with_label (" ");
+  gtk_table_attach ( GTK_TABLE (table), check_button, 1, 3, row, row+1, GTK_FILL, 0, 0, 0);
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button),
+                                edguiPtr->vals->invert);
+  gimp_help_set_help_data(check_button, _("ON: Invert (Black edge lines on white area) OFF: White lines on 
black area"), NULL);
+  gtk_widget_show(check_button);
+  edguiPtr->invert_toggle = check_button;
+  g_object_set_data(G_OBJECT(check_button), "edguiPtr", edguiPtr);
+  g_signal_connect (G_OBJECT (check_button), "toggled",
+                    G_CALLBACK (p_toggle_update_callback),
+                    &edguiPtr->vals->invert);
+
+
+  row++;
+
+
+  label = gtk_label_new(_("Create Layer:"));
+  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
+  gtk_table_attach(GTK_TABLE (table), label, 0, 1, row, row + 1
+                  , GTK_FILL, GTK_FILL, 0, 0);
+  gtk_widget_show(label);
+
+  /* check button */
+  check_button = gtk_check_button_new_with_label (" ");
+  gtk_table_attach ( GTK_TABLE (table), check_button, 1, 3, row, row+1, GTK_FILL, 0, 0, 0);
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button),
+                                edguiPtr->vals->createEdgeAsNewLayer);
+  gimp_help_set_help_data(check_button, _("ON: Render result as new layer OFF: render replaces original 
layers content"), NULL);
+  gtk_widget_show(check_button);
+  edguiPtr->createEdgeAsNewLayer_toggle = check_button;
+  g_object_set_data(G_OBJECT(check_button), "edguiPtr", edguiPtr);
+  g_signal_connect (G_OBJECT (check_button), "toggled",
+                    G_CALLBACK (p_toggle_update_callback),
+                    &edguiPtr->vals->createEdgeAsNewLayer);
+
+
+//  row++;
+//
+//   label = gtk_label_new(_("Feather Edges:"));
+//   gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
+//   gtk_table_attach(GTK_TABLE (table), label, 0, 1, row, row + 1
+//                   , GTK_FILL, GTK_FILL, 0, 0);
+//   gtk_widget_show(label);
+// 
+//   /* check button */
+//   check_button = gtk_check_button_new_with_label (" ");
+//   gtk_table_attach ( GTK_TABLE (table), check_button, 1, 3, row, row+1, GTK_FILL, 0, 0, 0);
+//   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button),
+//                                 edguiPtr->vals->feather_edges);
+//   gimp_help_set_help_data(check_button, _("ON: Feather edges using feather radius"), NULL);
+//   gtk_widget_show(check_button);
+//   edguiPtr->feather_edges_toggle = check_button;
+//   g_object_set_data(G_OBJECT(check_button), "edguiPtr", edguiPtr);
+//   g_signal_connect (G_OBJECT (check_button), "toggled",
+//                     G_CALLBACK (p_toggle_update_callback),
+//                     &edguiPtr->vals->feather_edges);
+//   row++;
+// 
+//   adj = gimp_scale_entry_new (GTK_TABLE (edguiPtr->master_table), 0, row++,
+//                               _("Feather Radius:"), SCALE_WIDTH, SPIN_BUTTON_WIDTH,
+//                               edguiPtr->vals->feather_radius,
+//                               0.0, 100.0,     /* lower/upper */
+//                               1.0, 10.0,      /* step, page */
+//                               1,              /* digits */
+//                               TRUE,           /* constrain */
+//                               0.0, 100.0,       /* lowr/upper unconstrained */
+//                               _("Feather radius for smoothing the alpha channel"), NULL);
+//   edguiPtr->feather_radius_adj = adj;
+//   g_object_set_data(G_OBJECT(adj), "edguiPtr", edguiPtr);
+//   g_signal_connect (adj, "value_changed",
+//                     G_CALLBACK (p_gdouble_adjustment_callback),
+//                     &edguiPtr->vals->feather_radius);
+// 
+//   row++;
+// 
+//   adj = gimp_scale_entry_new (GTK_TABLE (edguiPtr->master_table), 0, row++,
+//                               _("Shrink/Grow:"), SCALE_WIDTH, SPIN_BUTTON_WIDTH,
+//                               edguiPtr->vals->grow,
+//                               -20.0, 20.0,     /* lower/upper */
+//                               1.0, 10.0,      /* step, page */
+//                               0,              /* digits */
+//                               TRUE,           /* constrain */
+//                               -20.0, 20.0,       /* lowr/upper unconstrained */
+//                               _("Grow selection in pixels (use negative values for shrink)"), NULL);
+//   edguiPtr->grow_adj = adj;
+//   g_object_set_data(G_OBJECT(adj), "edguiPtr", edguiPtr);
+//   g_signal_connect (adj, "value_changed",
+//                     G_CALLBACK (p_gdouble_adjustment_callback),
+//                     &edguiPtr->vals->grow);
+
+  row++;
+
+  label = gtk_label_new(_("Automatic Preview:"));
+  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
+  gtk_table_attach(GTK_TABLE (table), label, 0, 1, row, row + 1, GTK_FILL, GTK_FILL, 0, 0);
+  gtk_widget_show(label);
+
+
+  /* check button */
+  check_button = gtk_check_button_new_with_label (" ");
+  gtk_table_attach ( GTK_TABLE (table), check_button, 1, 2, row, row+1, GTK_FILL, 0, 0, 0);
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button),
+                                edguiPtr->instant_preview);
+  gimp_help_set_help_data(check_button, _("ON: Keep preview image up to date"), NULL);
+  gtk_widget_show(check_button);
+  edguiPtr->instant_preview_toggle = check_button;
+  g_object_set_data(G_OBJECT(check_button), "edguiPtr", edguiPtr);
+  g_signal_connect (G_OBJECT (check_button), "toggled",
+                    G_CALLBACK (p_toggle_update_callback),
+                    &edguiPtr->instant_preview);
+
+  /* button */
+  button = gtk_button_new_with_label(_("Preview"));
+  gtk_table_attach(GTK_TABLE (table), button, 2, 3, row, row + 1, GTK_FILL, GTK_FILL, 0, 0);
+  gtk_widget_show(button);
+  gimp_help_set_help_data(button, _("Show preview as separate image"), NULL);
+  g_signal_connect (button, "clicked",
+                    G_CALLBACK (p_apply_callback),
+                    edguiPtr);
+
+  row++;
+
+  adj = gimp_scale_entry_new (GTK_TABLE (edguiPtr->master_table), 0, row++,
+                              _("Previewsize:"), SCALE_WIDTH, SPIN_BUTTON_WIDTH,
+                              edguiPtr->pv_size_percent,
+                              5.0, 100.0,     /* lower/upper */
+                              1.0, 10.0,      /* step, page */
+                              1,              /* digits */
+                              TRUE,           /* constrain */
+                              5.0, 100.0,       /* lowr/upper unconstrained */
+                              _("Size of the preview image in percent of the original"), NULL);
+  g_object_set_data(G_OBJECT(adj), "edguiPtr", edguiPtr);
+  g_signal_connect (adj, "value_changed",
+                    G_CALLBACK (p_gdouble_adjustment_callback),
+                    &edguiPtr->pv_size_percent);
+
+
+  hseparator = gtk_hseparator_new ();
+  gtk_widget_show (hseparator);
+  gtk_box_pack_start (GTK_BOX (main_vbox), hseparator, FALSE, FALSE, 0);
+
+  /*  Show the main containers  */
+
+  gtk_widget_show (main_vbox);
+
+  if(gap_debug)
+  {
+    printf("gap_edge_create_dialog DONE\n");
+  }
+
+
+  return(dlg);
+}  /* end gap_edge_create_dialog */
+
+
+/* ---------------------------------
+ * p_reset_callback
+ * ---------------------------------
+ */
+static void
+p_reset_callback(GtkWidget *w, GapEdgeDetectGuiParams *edguiPtr)
+{
+  if(edguiPtr)
+  {
+    p_edge_init_default_vals(edguiPtr->vals);
+
+    if(edguiPtr->blurRadius1X_adj)
+    {
+      gtk_adjustment_set_value (GTK_ADJUSTMENT (edguiPtr->blurRadius1X_adj), 
(gfloat)edguiPtr->vals->blurRadius1X);
+    }
+    if(edguiPtr->blurRadius1Y_adj)
+    {
+      gtk_adjustment_set_value (GTK_ADJUSTMENT (edguiPtr->blurRadius1Y_adj), 
(gfloat)edguiPtr->vals->blurRadius1Y);
+    }
+    if(edguiPtr->blurRadius2X_adj)
+    {
+      gtk_adjustment_set_value (GTK_ADJUSTMENT (edguiPtr->blurRadius2X_adj), 
(gfloat)edguiPtr->vals->blurRadius2X);
+    }
+    if(edguiPtr->blurRadius2Y_adj)
+    {
+      gtk_adjustment_set_value (GTK_ADJUSTMENT (edguiPtr->blurRadius2Y_adj), 
(gfloat)edguiPtr->vals->blurRadius2Y);
+    }
+    if(edguiPtr->shiftLeft_adj)
+    {
+      gtk_adjustment_set_value (GTK_ADJUSTMENT (edguiPtr->shiftLeft_adj), (gfloat)edguiPtr->vals->shiftLeft);
+    }
+    if(edguiPtr->shiftRight_adj)
+    {
+      gtk_adjustment_set_value (GTK_ADJUSTMENT (edguiPtr->shiftRight_adj), 
(gfloat)edguiPtr->vals->shiftRight);
+    }
+    if(edguiPtr->shiftUp_adj)
+    {
+      gtk_adjustment_set_value (GTK_ADJUSTMENT (edguiPtr->shiftUp_adj), (gfloat)edguiPtr->vals->shiftUp);
+    }
+    if(edguiPtr->shiftDown_adj)
+    {
+      gtk_adjustment_set_value (GTK_ADJUSTMENT (edguiPtr->shiftDown_adj), (gfloat)edguiPtr->vals->shiftDown);
+    }
+
+    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (edguiPtr->autoLevels_toggle)
+                                  ,edguiPtr->vals->autoLevels);
+    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (edguiPtr->desaturate_toggle)
+                                  ,edguiPtr->vals->desaturate);
+    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (edguiPtr->invert_toggle)
+                                  ,edguiPtr->vals->invert);
+    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (edguiPtr->createEdgeAsNewLayer_toggle)
+                                  ,edguiPtr->vals->createEdgeAsNewLayer);
+
+    //gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (edguiPtr->feather_edges_toggle)
+    //                              ,edguiPtr->vals->feather_edges);
+    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (edguiPtr->instant_preview_toggle)
+                                  ,edguiPtr->instant_preview);
+
+  }
+
+}  /* end p_reset_callback */
+
+
+/* ---------------------------------
+ * p_quit_callback
+ * ---------------------------------
+ */
+static void
+p_quit_callback(GtkWidget *w, GapEdgeDetectGuiParams *edguiPtr)
+{
+  static gboolean main_quit_flag = TRUE;
+
+  /* finish pending que draw ops before we close */
+  gdk_flush ();
+
+  if(edguiPtr)
+  {
+     p_remove_timer(edguiPtr);
+     if(edguiPtr->shell)
+     {
+       GtkWidget *l_shell;
+
+       l_shell = edguiPtr->shell;
+       main_quit_flag = TRUE;
+
+       /* cleanup wiget stuff */
+       p_clear_GapEdgeDetectGuiParams(edguiPtr);
+
+       /* p_quit_callback is the signal handler for the "destroy"
+        * signal of the shell window.
+        * the gtk_widget_destroy call will immediate reenter this procedure.
+        * (for this reason the edguiPtr->shell is set to NULL
+        *  before the gtk_widget_destroy call)
+        */
+       gtk_widget_destroy(l_shell);
+     }
+
+
+  }
+
+  if(main_quit_flag)
+  {
+    main_quit_flag = FALSE;
+    gtk_main_quit();
+  }
+
+}  /* end p_quit_callback */
+
+
+/* ---------------------------------
+ * p_edge_response
+ * ---------------------------------
+ */
+static void
+p_edge_response (GtkWidget *widget,
+                 gint       response_id,
+                 GapEdgeDetectGuiParams *edguiPtr)
+{
+  switch (response_id)
+  {
+    case GAP_EDGE_RESPONSE_RESET:
+      p_reset_callback(widget, edguiPtr);
+      break;
+
+    case GTK_RESPONSE_OK:
+      edguiPtr->run_flag = TRUE;
+
+    default:
+      gtk_widget_hide (widget);
+      p_quit_callback (widget, edguiPtr);
+      break;
+  }
+}  /* end p_edge_response */
+
+/* ---------------------------------
+ * p_set_waiting_cursor
+ * ---------------------------------
+ */
+static void
+p_set_waiting_cursor(GapEdgeDetectGuiParams *edguiPtr)
+{
+  if(edguiPtr == NULL) return;
+
+  gdk_window_set_cursor(GTK_WIDGET(edguiPtr->shell)->window, edguiPtr->cursor_wait);
+  gdk_flush();
+
+  /* g_main_context_iteration makes sure that waiting cursor is displayed */
+  while(g_main_context_iteration(NULL, FALSE));
+
+  gdk_flush();
+}  /* end p_set_waiting_cursor */
+
+/* ---------------------------------
+ * p_set_active_cursor
+ * ---------------------------------
+ */
+static void
+p_set_active_cursor(GapEdgeDetectGuiParams *edguiPtr)
+{
+  if(edguiPtr == NULL) return;
+
+  gdk_window_set_cursor(GTK_WIDGET(edguiPtr->shell)->window, edguiPtr->cursor_acitve);
+  gdk_flush();
+}  /* end p_set_active_cursor */
+
+/* ---------------------------------
+ * p_apply_callback
+ * ---------------------------------
+ */
+static void
+p_apply_callback(GtkWidget *w, GapEdgeDetectGuiParams *edguiPtr)
+{
+  if(edguiPtr)
+  {
+     if(!edguiPtr->instant_preview)
+     {
+       p_set_waiting_cursor(edguiPtr);
+     }
+     edguiPtr->layer_id = edguiPtr->drawable_id;
+     if(!p_edge_detect_apply(edguiPtr))
+     {
+       p_quit_callback(NULL, edguiPtr);
+     }
+     edguiPtr->layer_id = -1;
+     p_set_active_cursor(edguiPtr);
+  }
+}  /* end p_apply_callback */
+
+/* ---------------------------------
+ * p_gdouble_adjustment_callback
+ * ---------------------------------
+ */
+static void
+p_gdouble_adjustment_callback(GtkObject *obj, gpointer val)
+{
+  GapEdgeDetectGuiParams *edguiPtr;
+
+  gimp_double_adjustment_update(GTK_ADJUSTMENT(obj), val);
+
+  edguiPtr = g_object_get_data( G_OBJECT(obj), "edguiPtr" );
+  if(edguiPtr)
+  {
+    p_set_instant_apply_request(edguiPtr);
+  }
+
+}  /* end p_gdouble_adjustment_callback */
+
+
+
+
+/* ---------------------------------
+ * p_gint32_adjustment_callback
+ * ---------------------------------
+ */
+static void
+p_gint32_adjustment_callback(GtkObject *obj, gpointer val)
+{
+  GapEdgeDetectGuiParams *edguiPtr;
+
+  gimp_int_adjustment_update(GTK_ADJUSTMENT(obj), val);
+
+  edguiPtr = g_object_get_data( G_OBJECT(obj), "edguiPtr" );
+  if(edguiPtr)
+  {
+    p_set_instant_apply_request(edguiPtr);
+  }
+
+}  /* end p_gint32_adjustment_callback */
+
+
+/* ---------------------------------
+ * p_toggle_update_callback
+ * ---------------------------------
+ */
+static void
+p_toggle_update_callback(GtkWidget *widget, gint *val)
+{
+  GapEdgeDetectGuiParams *edguiPtr;
+
+  if(val)
+  {
+    if (GTK_TOGGLE_BUTTON (widget)->active)
+    {
+      *val = TRUE;
+    }
+    else
+    {
+      *val = FALSE;
+    }
+  }
+
+  edguiPtr = g_object_get_data( G_OBJECT(widget), "edguiPtr" );
+
+  if(edguiPtr)
+  {
+//     GtkWidget *spinbutton;
+//     GtkWidget *scale;
+// 
+//     spinbutton = GTK_WIDGET(g_object_get_data (G_OBJECT (edguiPtr->feather_radius_adj), "spinbutton"));
+//     scale = GTK_WIDGET(g_object_get_data (G_OBJECT (edguiPtr->feather_radius_adj), "scale"));
+//     gtk_widget_set_sensitive(spinbutton, edguiPtr->vals->feather_edges);
+//     gtk_widget_set_sensitive(scale, edguiPtr->vals->feather_edges);
+
+    p_set_instant_apply_request(edguiPtr);
+    if(edguiPtr->instant_preview)
+    {
+      p_install_timer(edguiPtr);
+    }
+  }
+
+}  /* end p_toggle_update_callback */
+
+
+/* ---------------------------------
+ * p_set_instant_apply_request
+ * ---------------------------------
+ */
+static void
+p_set_instant_apply_request(GapEdgeDetectGuiParams *edguiPtr)
+{
+  if(edguiPtr)
+  {
+    edguiPtr->instant_apply_request = TRUE; /* request is handled by timer */
+  }
+}  /* end p_set_instant_apply_request */
+
+/* --------------------------
+ * install / remove timer
+ * --------------------------
+ */
+static void
+p_install_timer(GapEdgeDetectGuiParams *edguiPtr)
+{
+  if(edguiPtr->instant_timertag < 0)
+  {
+    edguiPtr->instant_timertag = (gint32) g_timeout_add(INSTANT_TIMERINTERVAL_MILLISEC,
+                                      (GtkFunction)p_instant_timer_callback
+                                      , edguiPtr);
+  }
+}  /* end p_install_timer */
+
+static void
+p_remove_timer(GapEdgeDetectGuiParams *edguiPtr)
+{
+  if(edguiPtr->instant_timertag >= 0)
+  {
+    g_source_remove(edguiPtr->instant_timertag);
+    edguiPtr->instant_timertag = -1;
+  }
+}  /* end p_remove_timer */
+
+/* --------------------------
+ * p_instant_timer_callback
+ * --------------------------
+ * This procedure is called periodically via timer
+ * when the instant_preview checkbox is ON
+ */
+static void
+p_instant_timer_callback(gpointer   user_data)
+{
+  GapEdgeDetectGuiParams *edguiPtr;
+
+  edguiPtr = user_data;
+  if(edguiPtr == NULL)
+  {
+    return;
+  }
+
+  p_remove_timer(edguiPtr);
+
+  if(edguiPtr->instant_apply_request)
+  {
+    if(gap_debug) printf("FIRE p_instant_timer_callback >>>> REQUEST is TRUE\n");
+    p_apply_callback (NULL, edguiPtr);  /* the request is cleared in this procedure */
+  }
+
+  /* restart timer for next cycle */
+  if(edguiPtr->instant_preview)
+  {
+     p_install_timer(edguiPtr);
+  }
+}  /* end p_instant_timer_callback */
+
+
+/* ---------------------------------
+ * p_remove_additonal_layers_from_preview_image
+ * ---------------------------------
+ * check and remove additional layer(s) in the preview image
+ * (in case previous rendering or the user added new layers)
+ */
+static void
+p_remove_additonal_layers_from_preview_image(GapEdgeDetectGuiParams *edguiPtr)
+{
+  gint          l_nlayers;
+  gint32       *l_layers_list;
+  gint32        l_idx;
+
+  l_layers_list = gimp_image_get_layers(edguiPtr->pv_image_id, &l_nlayers);
+  if(l_layers_list != NULL)
+  {
+    for(l_idx = 0; l_idx < l_nlayers; l_idx++)
+    {
+      if((l_layers_list[l_idx] == edguiPtr->pv_layer_id)
+      || (l_layers_list[l_idx] == edguiPtr->pv_master_layer_id))
+      {
+        continue;
+      }
+      else
+      {
+        gimp_image_remove_layer(edguiPtr->pv_image_id, l_layers_list[l_idx]);
+      }
+    }
+    g_free (l_layers_list);
+  }
+
+  
+}  /* end p_remove_additonal_layers_from_preview_image */
+
+
+/* ---------------------------------
+ * p_edge_detect_apply
+ * ---------------------------------
+ * this procedure always does the processing on a preview image
+ * the preview image has full layersize (not imagesize)
+ *
+ * the preview image size may be reduced py pv_size_percent.
+ * an already existing preview image is reused.
+ *
+ * Show the preview image (by adding a display)
+ *
+ * return FALSE on Error
+ *        TRUE  .. OK
+ */
+gboolean
+p_edge_detect_apply(GapEdgeDetectGuiParams *edguiPtr)
+{
+  GimpDrawable *src_drawable;
+  gboolean replace_pv_image;
+  gint l_width;
+  gint l_height;
+
+  if(edguiPtr == NULL)                              { return FALSE; }
+
+  if(gap_debug)
+  {
+    printf("Edge Detect Params:\n");
+    printf("edguiPtr->image_id :%d\n", (int)edguiPtr->image_id);
+    printf("edguiPtr->layer_id :%d\n", (int)edguiPtr->layer_id);
+    printf("edguiPtr->pv_size_percent :%.3f\n", (float)edguiPtr->pv_size_percent);
+    printf("edguiPtr->vals->blurRadius1X     :%.3f\n", (float)edguiPtr->vals->blurRadius1X);
+    printf("edguiPtr->vals->blurRadius1Y     :%.3f\n", (float)edguiPtr->vals->blurRadius1Y);
+    printf("edguiPtr->vals->blurRadius2X     :%.3f\n", (float)edguiPtr->vals->blurRadius2X);
+    printf("edguiPtr->vals->blurRadius2Y     :%.3f\n", (float)edguiPtr->vals->blurRadius2Y);
+    
+    printf("edguiPtr->vals->shiftLeft        :%d\n", (int)edguiPtr->vals->shiftLeft);
+    printf("edguiPtr->vals->shiftRight       :%d\n", (int)edguiPtr->vals->shiftRight);
+    printf("edguiPtr->vals->shiftUp          :%d\n", (int)edguiPtr->vals->shiftUp);
+    printf("edguiPtr->vals->shiftDown        :%d\n", (int)edguiPtr->vals->shiftDown);
+
+    printf("edguiPtr->vals->autoLevels       :%s\n", edguiPtr->vals->autoLevels ? "ON" : "off" );
+    printf("edguiPtr->vals->desaturate       :%s\n", edguiPtr->vals->desaturate ? "ON" : "off" );
+    printf("edguiPtr->vals->invert           :%s\n", edguiPtr->vals->invert ? "ON" : "off" );
+    printf("edguiPtr->vals->createNewLayer   :%s\n", edguiPtr->vals->createEdgeAsNewLayer ? "ON" : "off" );
+    
+    //printf("edguiPtr->vals->grow        :%.3f\n", (float)edguiPtr->vals->grow);
+    //printf("edguiPtr->vals->feather_edges   :%d\n", (int)edguiPtr->vals->feather_edges);
+    //printf("edguiPtr->vals->feather_radius  :%.3f\n", (float)edguiPtr->vals->feather_radius);
+  }
+
+  if(!gap_image_is_alive(edguiPtr->image_id))
+  {
+    g_message(_("Error: Image '%d' not found"), edguiPtr->image_id);
+    return FALSE;
+  }
+  if(!gimp_drawable_is_layer(edguiPtr->layer_id))
+  {
+    g_message(_("Error: This Edge detection method operates only on layers"));
+    return FALSE;
+  }
+
+  if(!gimp_drawable_has_alpha(edguiPtr->layer_id))
+  {
+    gimp_layer_add_alpha(edguiPtr->layer_id);
+  }
+
+  src_drawable =  gimp_drawable_get (edguiPtr->layer_id);
+
+
+  /* set size (or reduced size) */
+  l_width = src_drawable->width;
+  l_height = src_drawable->height;
+  if(edguiPtr->pv_size_percent < 100.0)
+  {
+    l_width = ((gdouble)src_drawable->width * edguiPtr->pv_size_percent) / 100.0;
+    l_height = ((gdouble)src_drawable->height * edguiPtr->pv_size_percent) / 100.0;
+  }
+
+  replace_pv_image = FALSE;
+  if(gap_image_is_alive(edguiPtr->pv_image_id))
+  {
+    if(gimp_image_base_type(edguiPtr->image_id) != gimp_image_base_type(edguiPtr->pv_image_id))
+    {
+      replace_pv_image = TRUE;
+    }
+    else
+    {
+      if((l_width     != gimp_image_width(edguiPtr->pv_image_id))
+      || (l_height    != gimp_image_height(edguiPtr->pv_image_id)))
+      {
+        /* check if we can reuse the preview image
+         * (that was built in a previus call)
+         */
+        if(edguiPtr->pv_master_layer_id < 0)
+        {
+          replace_pv_image = TRUE;
+        }
+        else
+        {
+          if(l_width > gimp_image_width(edguiPtr->pv_image_id))
+          {
+            /* must force recreate the master copy for quality reasons */
+            if(edguiPtr->pv_master_layer_id >= 0)
+            {
+              gimp_image_remove_layer(edguiPtr->pv_image_id, edguiPtr->pv_master_layer_id);
+              edguiPtr->pv_master_layer_id = -1;
+            }
+          }
+          gimp_image_scale(edguiPtr->pv_image_id, l_width, l_height);
+
+        }
+      }
+    }
+    if(replace_pv_image)
+    {
+      gimp_image_delete(edguiPtr->pv_image_id);
+      edguiPtr->pv_image_id = -1;
+      edguiPtr->pv_master_layer_id = -1;
+      edguiPtr->pv_display_id = -1;
+    }
+  }
+  else
+  {
+    replace_pv_image = TRUE;
+  }
+
+
+  /* (re)create the preview image if there is none */
+  if(replace_pv_image)
+  {
+    edguiPtr->pv_display_id = -1;
+    edguiPtr->pv_master_layer_id = -1;
+    edguiPtr->pv_image_id = gimp_image_new(l_width
+                                     ,l_height
+                                     ,GIMP_RGB
+                                     );
+    gimp_image_set_filename(edguiPtr->pv_image_id, _("EdgeDetectionPreview.xcf"));
+    edguiPtr->pv_layer_id = gimp_layer_new(edguiPtr->pv_image_id, _("Previewlayer")
+                                 ,l_width
+                                 ,l_height
+                                 ,GIMP_RGBA_IMAGE
+                                 ,100.0            /* Opacity full opaque */
+                                 ,GIMP_NORMAL_MODE
+                                 );
+    gimp_image_insert_layer(edguiPtr->pv_image_id, edguiPtr->pv_layer_id, 0, 0);
+    gimp_layer_set_offsets(edguiPtr->pv_layer_id, 0, 0);
+
+    if(!gimp_drawable_has_alpha(edguiPtr->pv_layer_id))
+    {
+      gimp_layer_add_alpha(edguiPtr->pv_layer_id);
+    }
+  }
+
+  
+  if(edguiPtr->pv_master_layer_id < 0)
+  {
+    /* at 1.st call create a mastercopy of the original layer
+     * at the bottom of the layerstack
+     * (and scale to preview size when sizes are different)
+     */
+    edguiPtr->pv_master_layer_id = gimp_layer_new(edguiPtr->pv_image_id, _("Masterlayer")
+                                 ,src_drawable->width
+                                 ,src_drawable->height
+                                 ,GIMP_RGBA_IMAGE
+                                 ,100.0            /* Opacity full opaque */
+                                 ,GIMP_NORMAL_MODE
+                                 );
+    gimp_image_insert_layer(edguiPtr->pv_image_id, edguiPtr->pv_master_layer_id, 0, -1);
+
+    if(!gimp_drawable_has_alpha(edguiPtr->pv_master_layer_id))
+    {
+      gimp_layer_add_alpha(edguiPtr->pv_master_layer_id);
+    }
+    gap_layer_copy_content(edguiPtr->pv_master_layer_id  /* dest */
+                          , edguiPtr->layer_id           /* src */
+                          );
+    if ((l_width != src_drawable->width) || (l_height != src_drawable->height))
+    {
+      gimp_layer_scale(edguiPtr->pv_master_layer_id, l_width, l_height, 0);
+    }
+    gimp_layer_set_offsets(edguiPtr->pv_master_layer_id, 0, 0);
+    gimp_item_set_visible(edguiPtr->pv_master_layer_id, FALSE);
+  }
+
+  /* copy from the master (that is a probabl scaled copy of the 
+   * original when operating with reduced preview size) 
+   */
+  gap_layer_copy_content(edguiPtr->pv_layer_id           /* dest */
+                       , edguiPtr->pv_master_layer_id    /* src */
+                       );
+  
+
+  /* keep only 2 layers (master and pv_layer_id) */
+  p_remove_additonal_layers_from_preview_image(edguiPtr);
+  
+  /* reorder the layerstack (master at bottom pv_layer_id on top and set active) */
+  gimp_image_lower_item_to_bottom(edguiPtr->pv_image_id, edguiPtr->pv_master_layer_id);
+
+  gimp_image_set_active_layer(edguiPtr->pv_image_id, edguiPtr->pv_layer_id); 
+
+
+
+  /* call the edge detection RENDER method */
+  gap_edgeDetectionByShiftBlurDiff (edguiPtr->pv_layer_id, edguiPtr->vals);
+
+  if((edguiPtr->pv_display_id < 0)
+  && (edguiPtr->pv_image_id >= 0))
+  {
+    edguiPtr->pv_display_id = gimp_display_new(edguiPtr->pv_image_id);
+  }
+
+  gimp_displays_flush();
+
+  edguiPtr->instant_apply_request = FALSE;
+  return TRUE;
+}  /* end p_edge_detect_apply */
diff --git a/gap/gap_edge_detection_dialog.h b/gap/gap_edge_detection_dialog.h
new file mode 100644
index 0000000..ae5df94
--- /dev/null
+++ b/gap/gap_edge_detection_dialog.h
@@ -0,0 +1,64 @@
+/* gap_edge_detection_dialog.h
+ *    by hof (Wolfgang Hofer)
+ *  2017/03/10
+ *
+ */
+/* 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, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+/* revision history:
+ * version 2.7.0;             hof: created
+ */
+
+#ifndef _GAP_EDGE_DETECTION_DIALOG_H
+#define _GAP_EDGE_DETECTION_DIALOG_H
+
+/* SYTEM (UNIX) includes */
+#include <stdio.h>
+#include <stdlib.h>
+
+/* GIMP includes */
+#include "gtk/gtk.h"
+#include "libgimp/gimp.h"
+
+
+#include "gap_edge_detection.h"
+
+
+
+#define GAP_EDGE_PLUGIN_NAME "plug-in-edge-dosog"
+#define GAP_EDGE_HELP_ID     "plug-in-edge-dosog"
+
+/* ---------------------------------
+ * deliver new values (initialized with the defaault settings)
+ * ---------------------------------
+ */
+GapEdgeValues * 
+gap_edge_edval_new(gint32 acgtivDrawableId);
+
+/* ---------------------------------
+ * the Edge Detect MAIN dialog
+ * ---------------------------------
+ * return -1 on Error
+ *         0 .. OK
+ */
+int
+gap_edge_dialog(gint32 activeDrawableId, GapEdgeValues *edvalPtr);
+
+
+#endif
diff --git a/gap/gap_edge_detection_main.c b/gap/gap_edge_detection_main.c
new file mode 100644
index 0000000..cac0ba3
--- /dev/null
+++ b/gap/gap_edge_detection_main.c
@@ -0,0 +1,293 @@
+/* gap_edge_detection_main.c
+ *  by hof (Wolfgang Hofer)
+ *
+ * GAP ... Gimp Animation Plugins
+ *
+ * This Module contains the GAP Edge Detection Filter PDB Registration and MAIN
+ *
+ */
+/* 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, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+/* revision history:
+ * gimp    2.8.xx; 201/03/10  hof: creation
+ */
+
+static char *gap_edge_version = "1.0; 2017/03/10";
+
+
+/* SYTEM (UNIX) includes */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* GIMP includes */
+#include <gtk/gtk.h>
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+/* GAP includes */
+#include "config.h"
+#include "gap-intl.h"
+#include "gap_lastvaldesc.h"
+#include "gap_edge_detection.h"
+#include "gap_edge_detection_dialog.h"
+#include "gap_edge_detection_dialog.h"
+
+
+#define GAP_EDGE_DETECT_DATA_KEY_VALS "plug-in-edge-dosog"
+
+/* ------------------------
+ * gap DEBUG switch
+ * ------------------------
+ */
+
+/* int gap_debug = 1; */    /* print debug infos */
+/* int gap_debug = 0; */    /* 0: dont print debug infos */
+
+int gap_debug = 0;
+
+static GimpParamDef args_edge_detect[] =
+  {
+    {GIMP_PDB_INT32, "run_mode", "Interactive"},
+    {GIMP_PDB_IMAGE, "image", "the image"},
+    {GIMP_PDB_DRAWABLE, "drawable", "the drawable"},
+
+    {GIMP_PDB_FLOAT, "blurRadius1X", "Horizontal Blur Radius (used in the 1st and 3rd internal copy)"},
+    {GIMP_PDB_FLOAT, "blurRadius1Y", "Vertical Blur Radius (used in the 1st and 3rd internal copy)"},
+    {GIMP_PDB_FLOAT, "blurRadius2X", "Horizontal Blur Radius (used in the 2nd and 4th internal copy)"},
+    {GIMP_PDB_FLOAT, "blurRadius2Y", "Vertical Blur Radius (used in the 2st and 4th internal copy)"},
+    {GIMP_PDB_INT32, "shiftLeft",    "Deplacement by small amount of pixels (used in the 1st internal 
copy)"},
+    {GIMP_PDB_INT32, "shiftRight",   "Deplacement by small amount of pixels (used in the 2nd internal 
copy)"},
+    {GIMP_PDB_INT32, "shiftUp",      "Deplacement by small amount of pixels (used in the 3rd internal 
copy)"},
+    {GIMP_PDB_INT32, "shiftDown",    "Deplacement by small amount of pixels (used in the 4th internal 
copyl)"},
+    {GIMP_PDB_INT32, "autoLevels",   "TRUE: xxx, FALSE: xxx"},
+    {GIMP_PDB_INT32, "desaturate",   "TRUE: Desaturate the result to shades of gray, FALSE: dont 
desaturate"},
+    {GIMP_PDB_INT32, "invert",   "TRUE: invert result (edge lines black on white area), FALSE: keep (edge 
lines white on black area)"},
+    {GIMP_PDB_INT32, "createEdgeAsNewLayer",   "TRUE: delifer result as new layer (above the original), 
FALSE: replace the original layer content with the result"}
+  };
+
+/* -----------------------
+ * procedure declarations
+ * -----------------------
+ */
+
+static void query(void);
+static void run(const gchar *name
+              , gint n_params
+              , const GimpParam *param
+              , gint *nreturn_vals
+              , GimpParam **return_vals);
+
+
+GimpPlugInInfo PLUG_IN_INFO =
+{
+  NULL,  /* init_proc */
+  NULL,  /* quit_proc */
+  query, /* query_proc */
+  run,   /* run_proc */
+};
+
+/* ------------------------
+ * MAIN, query & run
+ * ------------------------
+ */
+
+MAIN ()
+
+/* ---------------------------------
+ * query
+ * ---------------------------------
+ */
+static void
+query ()
+{
+  static GapEdgeValues edvals;  /* this structure is only used as structure model
+                                 * for common iterator procedure registration
+                                 */
+  static GimpLastvalDef lastvals[] =
+  {
+    GIMP_LASTVALDEF_GDOUBLE         (GIMP_ITER_TRUE,   edvals.blurRadius1X, "blurRadius1X"),
+    GIMP_LASTVALDEF_GDOUBLE         (GIMP_ITER_TRUE,   edvals.blurRadius1Y, "blurRadius1Y"),
+    GIMP_LASTVALDEF_GDOUBLE         (GIMP_ITER_TRUE,   edvals.blurRadius2X, "blurRadius2X"),
+    GIMP_LASTVALDEF_GDOUBLE         (GIMP_ITER_TRUE,   edvals.blurRadius2Y, "blurRadius2Y"),
+    GIMP_LASTVALDEF_GINT32          (GIMP_ITER_TRUE,   edvals.shiftLeft,    "shiftLeft"),
+    GIMP_LASTVALDEF_GINT32          (GIMP_ITER_TRUE,   edvals.shiftRight,   "shiftRight"),
+    GIMP_LASTVALDEF_GINT32          (GIMP_ITER_TRUE,   edvals.shiftUp,      "shiftUp"),
+    GIMP_LASTVALDEF_GINT32          (GIMP_ITER_TRUE,   edvals.shiftDown,    "shiftDown"),
+    GIMP_LASTVALDEF_GBOOLEAN        (GIMP_ITER_FALSE,  edvals.autoLevels,   "autoLevels"),
+    GIMP_LASTVALDEF_GBOOLEAN        (GIMP_ITER_FALSE,  edvals.desaturate,   "desaturate"),
+    GIMP_LASTVALDEF_GBOOLEAN        (GIMP_ITER_FALSE,  edvals.invert,       "invert"),
+    GIMP_LASTVALDEF_GBOOLEAN        (GIMP_ITER_FALSE,  edvals.createEdgeAsNewLayer, "createEdgeAsNewLayer")
+  };
+
+
+  static GimpParamDef *return_vals = NULL;
+  static int nreturn_vals = 0;
+
+  gimp_plugin_domain_register (GETTEXT_PACKAGE, LOCALEDIR);
+
+
+  /* registration for last values buffer structure (useful for animated filter apply) */
+  gimp_lastval_desc_register(GAP_EDGE_PLUGIN_NAME,
+                             &edvals,
+                             sizeof(edvals),
+                             G_N_ELEMENTS (lastvals),
+                             lastvals);
+
+  gimp_install_procedure(GAP_EDGE_PLUGIN_NAME,
+                         "The edge detection filter detects and renders the edges of the specified drawable",
+                         "This plug-in performs edge detection "
+                         "by building difference of optionally shifted and or gaussian blured versions of 
the specified drawable. "
+                         "the result replaces the original or is optional put into a newly created layer.",
+                         "Wolfgang Hofer (hof gimp org)",
+                         "Wolfgang Hofer",
+                         gap_edge_version,
+                         N_("Edge Detect (DoSoG) ..."),
+                         "RGB*",
+                         GIMP_PLUGIN,
+                         G_N_ELEMENTS (args_edge_detect), nreturn_vals,
+                         args_edge_detect, return_vals);
+
+ gimp_plugin_menu_register (GAP_EDGE_PLUGIN_NAME, N_("<Image>/Video/Layer/Render"));
+
+}       /* end query */
+
+
+/* ---------------------------------
+ * run
+ * ---------------------------------
+ */
+static void
+run(const gchar *name
+   , gint n_params
+   , const GimpParam *param
+   , gint *nreturn_vals
+   , GimpParam **return_vals)
+{
+  const gchar *l_env;
+
+  static GimpParam values[2];
+  GimpRunMode run_mode;
+  GimpPDBStatusType status = GIMP_PDB_SUCCESS;
+  GapEdgeValues  *edvalPtr;
+  gint32 imageId;
+  gint32 activeDrawableId;
+
+  gint32     l_rc;
+
+  *nreturn_vals = 1;
+  *return_vals = values;
+  l_rc = 0;
+
+  INIT_I18N();
+
+  l_env = g_getenv("GAP_DEBUG");
+  if(l_env != NULL)
+  {
+    if((*l_env != 'n') && (*l_env != 'N')) gap_debug = 1;
+  }
+
+
+  run_mode = param[0].data.d_int32;
+  imageId = param[1].data.d_image;
+  activeDrawableId = param[2].data.d_drawable;
+  edvalPtr = gap_edge_edval_new(activeDrawableId);
+
+
+  if(gap_debug) printf("\n\ngap_edge_detection main: debug name = %s\n", name);
+
+  if (strcmp (name, GAP_EDGE_PLUGIN_NAME) == 0)
+  {
+      switch (run_mode)
+      {
+        case GIMP_RUN_INTERACTIVE:
+          {
+            /*  Possibly retrieve data  */
+            gimp_get_data (GAP_EDGE_DETECT_DATA_KEY_VALS, edvalPtr);
+            l_rc = gap_edge_dialog(activeDrawableId, edvalPtr);
+            if(l_rc < 0)
+            {
+              status = GIMP_PDB_CANCEL;
+            }
+          }
+          break;
+        case GIMP_RUN_NONINTERACTIVE:
+          {
+            if (n_params != G_N_ELEMENTS (args_edge_detect))
+              {
+                status = GIMP_PDB_CALLING_ERROR;
+              }
+            else
+              {
+                edvalPtr->blurRadius1X = param[3].data.d_float;
+                edvalPtr->blurRadius1Y = param[4].data.d_float;
+                edvalPtr->blurRadius2X = param[5].data.d_float;
+                edvalPtr->blurRadius2Y = param[6].data.d_float;
+                edvalPtr->shiftLeft = param[7].data.d_int32;
+                edvalPtr->shiftRight = param[8].data.d_int32;
+                edvalPtr->shiftUp = param[9].data.d_int32;
+                edvalPtr->shiftDown = param[10].data.d_int32;
+                edvalPtr->autoLevels = param[11].data.d_int32;
+                edvalPtr->desaturate = param[12].data.d_int32;
+                edvalPtr->invert = param[13].data.d_int32;
+                edvalPtr->createEdgeAsNewLayer = param[14].data.d_int32;
+              }
+          }
+          break;
+        case GIMP_RUN_WITH_LAST_VALS:
+          {
+            /*  Possibly retrieve data  */
+            gimp_get_data (GAP_EDGE_DETECT_DATA_KEY_VALS, edvalPtr);
+          }
+          break;
+        default:
+          break;
+      }
+  }
+
+  if(status == GIMP_PDB_SUCCESS)
+  {
+    gint32 resultLayerId;
+    
+    gimp_image_undo_group_start (imageId);
+    resultLayerId = gap_edgeDetectionByShiftBlurDiff(activeDrawableId, edvalPtr);
+    gimp_image_undo_group_end (imageId);
+
+    if(resultLayerId < 0)
+    {
+      status = GIMP_PDB_EXECUTION_ERROR;
+    }
+    else
+    {
+      gimp_set_data (GAP_EDGE_DETECT_DATA_KEY_VALS, edvalPtr, sizeof (struct GapEdgeValues));
+    }
+     /* If run mode is interactive, flush displays, else (script) don't
+        do it, as the screen updates would make the scripts slow */
+     if (run_mode != GIMP_RUN_NONINTERACTIVE)
+     {
+       gimp_displays_flush ();
+     }
+  }
+
+  /* ---------- return handling --------- */
+
+
+  values[0].type = GIMP_PDB_STATUS;
+  values[0].data.d_status = status;
+}  /* end run */
diff --git a/gap/gap_geo.c b/gap/gap_geo.c
new file mode 100644
index 0000000..252cafa
--- /dev/null
+++ b/gap/gap_geo.c
@@ -0,0 +1,1283 @@
+/*  gap_geo.c
+ *    This module provides structures and procedures to handle 2d geometric stuff
+ *    such as perspective transformation.
+ *  2016/04/18
+ */
+/* 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, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+/* Revision history
+ *  (2016/04/18)  2.8.x       hof: created
+ */
+extern int gap_debug;
+
+#include "config.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <math.h>
+
+#include <gtk/gtk.h>
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#include "gap_geo.h"
+
+
+/* TRANSFORM_MAX_ITERATIONCOUNT is the number of maxiaml iteration attempts 
+ * to findout the relevant 4 corners for perspective transformation
+ * In test frames it took typical 
+ *   less than  25 iterations to reach a transformPrecision of  0.5 pixels
+ *   less than  36 iterations to reach a transformPrecision of  0.2 pixels
+ *   less than 106 iterations to reach a transformPrecision of  0.005 pixels
+ *   less than 128 iterations to reach a transformPrecision of  0.001 pixels
+ *
+ */
+#define TRANSFORM_MAX_ITERATIONCOUNT      500
+
+
+static void  p_save_perCoords_as_array_value(GapPerspectiveTransCoords *perCoords, gdouble precision);
+
+
+/* ------------------------------------
+ * p_save_perCoords_as_array_value
+ * ------------------------------------
+ * save perspective coords in the internal array values of perCoords.
+ * the number of saved coordinate sets is limited to MAX_ITER_ATTEMPTS_PERSPECTIVE
+ * in case th limit is reached, the value with 
+ */
+static void
+p_save_perCoords_as_array_value(GapPerspectiveTransCoords *perCoords, gdouble precision)
+{
+  gint ida;
+  gdouble maxPrecision;
+  
+  ida = MAX(perCoords->numberOfArrayValues, 0);
+  maxPrecision = 0.0;
+  if(ida < MAX_ITER_ATTEMPTS_PERSPECTIVE)
+  {
+    /* as long as there are free array elements
+     * save coordinates as new arraylement 
+     */
+    perCoords->numberOfArrayValues++;
+  }
+  else
+  {
+     gint idx;
+     
+     /* the array is already full, therefore
+      * find index ida to overwrite the array value with worst (greatest) precision 
+      */
+     for(idx = 0; idx < MAX_ITER_ATTEMPTS_PERSPECTIVE; idx++)
+     {
+       if(perCoords->aprecision[idx] > maxPrecision)
+       {
+         ida = idx;
+         maxPrecision = perCoords->aprecision[idx];
+       }
+     }
+     
+     if (precision > maxPrecision)
+     {
+       /* all recorded values so far, are better than the new precision
+        * therefore ignore the new coordinates.
+        */
+       return;
+     }
+  }
+  
+  perCoords->aprecision[ida] = precision;
+  perCoords->ax0[ida] = perCoords->x0;    
+  perCoords->ay0[ida] = perCoords->y0;
+  perCoords->ax1[ida] = perCoords->x1; 
+  perCoords->ay1[ida] = perCoords->y1;
+  perCoords->ax2[ida] = perCoords->x2;
+  perCoords->ay2[ida] = perCoords->y2;
+  perCoords->ax3[ida] = perCoords->x3;
+  perCoords->ay3[ida] = perCoords->y3;
+
+  if(gap_debug)
+  {
+    printf("p_save_perCoords_as_array_value (SAVE precision:%f at array index IDA:%d) width:%d height:%d\n"
+           "    p0: %f %f\n"
+           "    p1: %f %f\n"
+           "    p2: %f %f\n"
+           "    p3: %f %f\n"
+      ,(float)precision
+      ,(int)ida
+      ,(int)perCoords->width
+      ,(int)perCoords->height
+      ,(float)perCoords->x0
+      ,(float)perCoords->y0
+      ,(float)perCoords->x1
+      ,(float)perCoords->y1
+      ,(float)perCoords->x2
+      ,(float)perCoords->y2
+      ,(float)perCoords->x3
+      ,(float)perCoords->y3
+      );
+  }  
+}    /* end p_save_perCoords_as_array_value */
+
+/* ------------------------------------
+ * gap_geo_copy_src_to_dst_coords
+ * ------------------------------------
+ */
+void
+gap_geo_copy_src_to_dst_coords(GapPixelCoords *srcCoords, GapPixelCoords *dstCoords)
+{
+  dstCoords->valid = srcCoords->valid;
+  dstCoords->avgColorDiff = srcCoords->avgColorDiff;
+  dstCoords->px = srcCoords->px;
+  dstCoords->py = srcCoords->py;
+}
+
+
+
+/* ----------------------------
+ * gap_geo_calculateSqrDistX2Y2
+ * ----------------------------
+ * returns the square distance between coordA and point px/py
+ *
+ */
+gdouble
+gap_geo_calculateSqrDistX2Y2(GapPixelCoords *coordA, gdouble px, gdouble py)
+{
+  gdouble lenX;
+  gdouble lenY;
+  gdouble sqrDist;
+  
+  lenX = (gdouble)coordA->px - px;
+  lenY = (gdouble)coordA->py - py;
+  
+  sqrDist = (lenX * lenX) + (lenY * lenY);
+  return sqrDist;
+}  /* end gap_geo_calculateSqrDistX2Y2 */
+
+/* ----------------------------
+ * gap_geo_calculateSqrDist
+ * ----------------------------
+ * returns the square distance between coordA and coordB
+ *
+ */
+gdouble
+gap_geo_calculateSqrDist(GapPixelCoords *coordA, GapPixelCoords *coordB)
+{
+  gdouble lenX;
+  gdouble lenY;
+  gdouble sqrDist;
+  
+  lenX = coordA->px - coordB->px;
+  lenY = coordA->py - coordB->py;
+  
+  sqrDist = (lenX * lenX) + (lenY * lenY);
+  return sqrDist;
+
+} /* end gap_geo_calculateSqrDist */
+
+gdouble
+gap_geo_calculateDist(GapPixelCoords *coordA, GapPixelCoords *coordB)
+{
+  gdouble sqrDist;
+  gdouble dist;
+  
+  sqrDist = gap_geo_calculateSqrDist(coordA, coordB);
+  dist = sqrt(sqrDist);
+  
+  return (dist);
+  
+} /* end gap_geo_calculateDist */
+
+
+/* -----------------------------------------
+ * gap_geo_line_description_from_2Points
+ * -----------------------------------------
+ * Ermittlung der konstanten a,b,c der Geradengleichung aus 2 punkten x1,y1  und x2,y2
+ * Spezialfaelle
+ *   Falls a=0 ist, verlauuft die Gerade parallel zur x-Achse, und falls b=0 ist, parallel zur y-Achse.
+ *   Falls c=0 ist, handelt es sich bei der Gerade um eine Ursprungsgerade.
+ *   Falls c=1 ist, liegt die Geradengleichung in Achsenabschnittsform vor; die Achsenschnittpunkte sind 
dann (1/a,0) und (0,1/b).
+ *
+ * a = y1 - y2;
+ * b = x2 - x1; 
+ * c = (x2 * y1) - (x1 * y2);
+ */
+void
+gap_geo_line_description_from_2Points(gdouble x1, gdouble y1, gdouble x2, gdouble y2, 
GapLineDescriptionConsts *lineConst)
+{
+
+  lineConst->a = y1 - y2;
+  lineConst->b = x2 - x1;
+  lineConst->c = (x2 * y1) - (x1 * y2);
+
+}  /* end gap_geo_line_description_from_2Points */
+
+void
+gap_geo_line_description_from_2GapPixelCoords(GapPixelCoords *p1, GapPixelCoords *p2, 
GapLineDescriptionConsts *lineConst)
+{
+  gap_geo_line_description_from_2Points(p1->px, p1->py, p2->px, p2->py, lineConst);
+}  /* end gap_geo_line_description_from_2Points */
+
+
+/* -----------------------------------------
+ * gap_geo_line_getX
+ * -----------------------------------------
+ * return X coordiate at specified y coordinate.
+ *     (a * x) + (b * y) = c    // line description
+ *      x = (c - (b * y)) / a
+ * 
+ * Does not work in case the line is parallel to the x axis (a == 0)
+ */
+gdouble gap_geo_line_getX(GapLineDescriptionConsts *lineConst, gdouble y)
+{
+  gdouble x;
+  
+  x = 0;
+  if (lineConst->a != 0.0)
+  {
+    x = (lineConst->c - (lineConst->b * y)) / lineConst->a;
+  }
+  return (x);
+
+}  /* end gap_geo_line_getX */
+
+
+/* -----------------------------------------
+ * gap_geo_line_getY
+ * -----------------------------------------
+ * return Y coordiate at specified x coordinate.
+ *     (a * x) + (b * y) = c    // line description
+ *     y = (c - (a * x)) / b
+ * 
+ * Does not work in case the line is parallel to the y axis (b == 0)
+ */
+gdouble gap_geo_line_getY(GapLineDescriptionConsts *lineConst, gdouble x)
+{
+  gdouble y;
+  
+  y = 0;
+  if (lineConst->b != 0.0)
+  {
+    y = (lineConst->c - (lineConst->a * x)) / lineConst->b;
+  }
+  return (y);
+
+}  /* end gap_geo_line_getY */
+
+
+/* -----------------------------------------
+ * gap_geo_line_intersection
+ * -----------------------------------------
+ * Ermittlung des Schnittpunktes xs,ys zweier geraden
+ *
+ * denominator = ((a1 * b2) - (a2 * b1));
+ *
+ * xs = ((c1 * b2) - (c2 * b1)) / denominator;
+ * ys = ((a1 * c2) - (a2 * c1)) / denominator;
+ *
+ */
+void
+gap_geo_line_intersection(GapLineDescriptionConsts *line1, GapLineDescriptionConsts *line2, GapDoubleCoords 
*interPt)
+{
+  gdouble denominator = ((line1->a * line2->b) - (line2->a * line1->b));
+  
+  if (denominator == 0)
+  {
+    interPt->valid = FALSE;
+  }
+  else
+  {
+    interPt->valid = TRUE;
+    interPt->x = ((line1->c * line2->b) - (line2->c * line1->b)) / denominator;
+    interPt->y = ((line1->a * line2->c) - (line2->a * line1->c)) / denominator;
+  }
+  
+}  /* end  gap_geo_line_intersection */
+
+/**
+ * gap_matrix3_mult:
+ * @matrix1: The first input matrix.
+ * @matrix2: The second input matrix which will be overwritten by the result.
+ *
+ * Multiplies two matrices and puts the result into the second one.
+ */
+void
+gap_matrix3_mult (const GimpMatrix3 *matrix1,
+                   GimpMatrix3       *matrix2)
+{
+  gint         i, j;
+  GimpMatrix3  tmp;
+  gdouble      t1, t2, t3;
+
+  for (i = 0; i < 3; i++)
+    {
+      t1 = matrix1->coeff[i][0];
+      t2 = matrix1->coeff[i][1];
+      t3 = matrix1->coeff[i][2];
+
+      for (j = 0; j < 3; j++)
+        {
+          tmp.coeff[i][j]  = t1 * matrix2->coeff[0][j];
+          tmp.coeff[i][j] += t2 * matrix2->coeff[1][j];
+          tmp.coeff[i][j] += t3 * matrix2->coeff[2][j];
+        }
+    }
+
+  *matrix2 = tmp;
+}
+
+/**
+ * gap_matrix3_translate:
+ * @matrix: The matrix that is to be translated.
+ * @x: Translation in X direction.
+ * @y: Translation in Y direction.
+ *
+ * Translates the matrix by x and y.
+ */
+void
+gap_matrix3_translate (GimpMatrix3 *matrix,
+                        gdouble      x,
+                        gdouble      y)
+{
+  gdouble g, h, i;
+
+  g = matrix->coeff[2][0];
+  h = matrix->coeff[2][1];
+  i = matrix->coeff[2][2];
+
+  matrix->coeff[0][0] += x * g;
+  matrix->coeff[0][1] += x * h;
+  matrix->coeff[0][2] += x * i;
+  matrix->coeff[1][0] += y * g;
+  matrix->coeff[1][1] += y * h;
+  matrix->coeff[1][2] += y * i;
+}
+
+/**
+ * gap_matrix3_scale:
+ * @matrix: The matrix that is to be scaled.
+ * @x: X scale factor.
+ * @y: Y scale factor.
+ *
+ * Scales the matrix by x and y
+ */
+void
+gap_matrix3_scale (GimpMatrix3 *matrix,
+                    gdouble      x,
+                    gdouble      y)
+{
+  matrix->coeff[0][0] *= x;
+  matrix->coeff[0][1] *= x;
+  matrix->coeff[0][2] *= x;
+
+  matrix->coeff[1][0] *= y;
+  matrix->coeff[1][1] *= y;
+  matrix->coeff[1][2] *= y;
+}
+
+/**
+ * gap_matrix3_identity:
+ * @matrix: A matrix.
+ *
+ * Sets the matrix to the identity matrix.
+ */
+void
+gap_matrix3_identity (GimpMatrix3 *matrix)
+{
+  static const GimpMatrix3 identity = { { { 1.0, 0.0, 0.0 },
+                                          { 0.0, 1.0, 0.0 },
+                                          { 0.0, 0.0, 1.0 } } };
+
+  *matrix = identity;
+}
+
+void
+gap_transform_matrix_perspective (GimpMatrix3 *matrix,
+                                   gint         x,
+                                   gint         y,
+                                   gint         width,
+                                   gint         height,
+                                   gdouble      t_x1,
+                                   gdouble      t_y1,
+                                   gdouble      t_x2,
+                                   gdouble      t_y2,
+                                   gdouble      t_x3,
+                                   gdouble      t_y3,
+                                   gdouble      t_x4,
+                                   gdouble      t_y4)
+{
+  GimpMatrix3 trafo;
+  gdouble     scalex;
+  gdouble     scaley;
+
+  g_return_if_fail (matrix != NULL);
+
+  scalex = scaley = 1.0;
+
+  if (width > 0)
+    scalex = 1.0 / (gdouble) width;
+
+  if (height > 0)
+    scaley = 1.0 / (gdouble) height;
+
+  gap_matrix3_translate (matrix, -x, -y);
+  gap_matrix3_scale     (matrix, scalex, scaley);
+
+  /* Determine the perspective transform that maps from
+   * the unit cube to the transformed coordinates
+   */
+  {
+    gdouble dx1, dx2, dx3, dy1, dy2, dy3;
+
+    dx1 = t_x2 - t_x4;
+    dx2 = t_x3 - t_x4;
+    dx3 = t_x1 - t_x2 + t_x4 - t_x3;
+
+    dy1 = t_y2 - t_y4;
+    dy2 = t_y3 - t_y4;
+    dy3 = t_y1 - t_y2 + t_y4 - t_y3;
+
+    /*  Is the mapping affine?  */
+    if ((dx3 == 0.0) && (dy3 == 0.0))
+      {
+        trafo.coeff[0][0] = t_x2 - t_x1;
+        trafo.coeff[0][1] = t_x4 - t_x2;
+        trafo.coeff[0][2] = t_x1;
+        trafo.coeff[1][0] = t_y2 - t_y1;
+        trafo.coeff[1][1] = t_y4 - t_y2;
+        trafo.coeff[1][2] = t_y1;
+        trafo.coeff[2][0] = 0.0;
+        trafo.coeff[2][1] = 0.0;
+      }
+    else
+      {
+        gdouble det1, det2;
+
+        det1 = dx3 * dy2 - dy3 * dx2;
+        det2 = dx1 * dy2 - dy1 * dx2;
+
+        trafo.coeff[2][0] = (det2 == 0.0) ? 1.0 : det1 / det2;
+
+        det1 = dx1 * dy3 - dy1 * dx3;
+
+        trafo.coeff[2][1] = (det2 == 0.0) ? 1.0 : det1 / det2;
+
+        trafo.coeff[0][0] = t_x2 - t_x1 + trafo.coeff[2][0] * t_x2;
+        trafo.coeff[0][1] = t_x3 - t_x1 + trafo.coeff[2][1] * t_x3;
+        trafo.coeff[0][2] = t_x1;
+
+        trafo.coeff[1][0] = t_y2 - t_y1 + trafo.coeff[2][0] * t_y2;
+        trafo.coeff[1][1] = t_y3 - t_y1 + trafo.coeff[2][1] * t_y3;
+        trafo.coeff[1][2] = t_y1;
+      }
+
+    trafo.coeff[2][2] = 1.0;
+  }
+
+  gap_matrix3_mult (&trafo, matrix);
+}
+
+
+
+
+
+/* ----------------------------------------------------
+ * gap_geo_assemble_perspective_transformation_matrix
+ * ----------------------------------------------------
+ *
+ */
+void
+gap_geo_assemble_perspective_transformation_matrix(GimpMatrix3  *matrix, GapPerspectiveTransCoords 
*perCoords, gint32 activeDrawableId)
+{
+   gint x, y;
+   gint off_x, off_y;
+   
+   x = 0;
+   y = 0;
+   
+   if(activeDrawableId >= 0)
+   {
+     gimp_drawable_offsets(activeDrawableId, &off_x, &off_y);
+     x += off_x;
+     y += off_y;
+   }
+
+   
+   /* Assemble the transformation matrix */
+   gap_matrix3_identity (matrix);
+   gap_transform_matrix_perspective (matrix
+                                      , x, y
+                                      , perCoords->width, perCoords->height
+                                      , perCoords->x0, perCoords->y0
+                                      , perCoords->x1, perCoords->y1
+                                      , perCoords->x2, perCoords->y2
+                                      , perCoords->x3, perCoords->y3
+                                      );   
+}  /* end gap_geo_assemble_perspective_transformation_matrix */
+
+
+/* ---------------------------------------
+ * gap_geo_DuplicateAlignCoords
+ * ---------------------------------------
+ */
+void
+gap_geo_DuplicateAlignCoords(GapAlignCoords *alignCoords, GapAlignCoords *alignCoordsDup)
+{
+  gint idx;
+  
+  for(idx=0; idx < GAP_ALIGN_COORDS_MAX; idx++)
+  {
+    gap_geo_copy_src_to_dst_coords(&alignCoords->currCoords[idx], &alignCoordsDup->currCoords[idx]);
+    alignCoordsDup->currCoords[idx].status = alignCoords->currCoords[idx].status;
+
+    gap_geo_copy_src_to_dst_coords(&alignCoords->startCoords[idx], &alignCoordsDup->startCoords[idx]);
+    alignCoordsDup->startCoords[idx].status = alignCoords->startCoords[idx].status;
+
+    alignCoordsDup->trkPtr[idx] = alignCoords->trkPtr[idx];
+    alignCoordsDup->refPtr[idx] = alignCoords->refPtr[idx];
+
+  }
+}  /* end gap_geo_DuplicateAlignCoords */
+
+/* ---------------------------------------
+ * gap_geo_pick_corners_from_align_coords
+ * ---------------------------------------
+ * This procedure picks alignCoords near the 4 corners
+ * and assigns the pointer refPtr and trkPtr in the GapAlignCoords structure
+ * in the following sorted order:
+ *  Ptr[0] shall point to coord nearest to upper left corner
+ *  Ptr[1] shall point to coord nearest to upper right corner
+ *  Ptr[2] shall point to coord nearest to lower left corner
+ *  Ptr[3] shall point to coord nearest to lower right corner
+ *
+ * returns TRUE in case of success
+ */
+gboolean
+gap_geo_pick_corners_from_align_coords(GapAlignCoords *alignCoords, gdouble width, gdouble height)
+{
+  gint    idn;
+  gint    idx;
+  gint    idcPick;
+  gdouble   minSqDist;
+  gdouble   minSqDistCorner[4];
+  gboolean  cornerSelected[4];
+  gint      cornerSelectCount;
+  
+  for(idx=0; idx < GAP_ALIGN_COORDS_MAX; idx++)
+  {
+    alignCoords->refPtr[idx] = NULL;
+    alignCoords->trkPtr[idx] = NULL;
+    alignCoords->startCoords[idx].status = -1;  /* indicates unusable pair */
+    
+    if(gap_debug)
+    {
+      printf("PERc: idx[%d] start px:%d py:%d valid:%d cur px:%d py:%d valid:%d\n"
+         ,(int)idx
+         ,(int)alignCoords->startCoords[idx].px
+         ,(int)alignCoords->startCoords[idx].py
+         ,(int)alignCoords->startCoords[idx].valid
+         ,(int)alignCoords->currCoords[idx].px
+         ,(int)alignCoords->currCoords[idx].py
+         ,(int)alignCoords->currCoords[idx].valid
+         );
+    }
+
+    
+    if((alignCoords->startCoords[idx].valid)
+    && (alignCoords->currCoords[idx].valid))
+    {
+      alignCoords->startCoords[idx].status = 0;  /* indicates selectable pair */
+    }
+    else
+    {
+      if(gap_debug)
+      {
+        printf("PER: ret(1)\n");
+      }
+      return (FALSE);  /* stop in case invalid points are detected */
+    }
+  }
+  
+
+  /* alignCoords->refPtr and alignCoords->trkPtr are assigned (picked) in sorted order:
+   * Ptr[0] shall point to coord nearest to upper left corner
+   * Ptr[1] shall point to coord nearest to upper right corner
+   * Ptr[2] shall point to coord nearest to lower left corner
+   * Ptr[3] shall point to coord nearest to lower right corner
+   */
+
+
+  /* pick 4 coordinate near ideally near to the 4 corners in loop for idn = 0 to 3 */
+
+  cornerSelected[0] = FALSE;
+  cornerSelected[1] = FALSE;
+  cornerSelected[2] = FALSE;
+  cornerSelected[3] = FALSE;
+  cornerSelectCount = 0;
+
+  
+  for(idn = 0; idn < 4; idn++)
+  {
+    minSqDist = (width + height) * (width + height);
+    minSqDistCorner[0] = minSqDist;
+    minSqDistCorner[1] = minSqDist;
+    minSqDistCorner[2] = minSqDist;
+    minSqDistCorner[3] = minSqDist;
+  
+    idcPick = -1;
+    for(idx=0; idx < GAP_ALIGN_COORDS_MAX; idx++)
+    {
+      gdouble sqDist[4];
+      gint idc;
+
+      if(alignCoords->startCoords[idx].status != 0)
+      {
+        continue;  /* Skip already selected coorinates */
+      }
+
+      /* square distance to all 4 corners */
+      sqDist[0] = gap_geo_calculateSqrDistX2Y2(&alignCoords->startCoords[idx], 0.0, 0.0);
+      sqDist[1] = gap_geo_calculateSqrDistX2Y2(&alignCoords->startCoords[idx], (gdouble)width, 0.0);
+      sqDist[2] = gap_geo_calculateSqrDistX2Y2(&alignCoords->startCoords[idx], 0.0, (gdouble)height);
+      sqDist[3] = gap_geo_calculateSqrDistX2Y2(&alignCoords->startCoords[idx], (gdouble)width, 
(gdouble)height);
+    
+      for(idc = 0; idc < 4; idc++)
+      {
+        if (cornerSelected[idc] == TRUE)
+        {
+          continue; /* skip already picked corners */
+        }
+        if (sqDist[idc] < minSqDistCorner[idc])
+        {
+          minSqDistCorner[idc] = sqDist[idc];
+          if (minSqDistCorner[idc] < minSqDist)
+          {
+            minSqDist = minSqDistCorner[idc];
+            idcPick = idc;
+            alignCoords->refPtr[idc] = &alignCoords->startCoords[idx];
+            alignCoords->trkPtr[idc] = &alignCoords->currCoords[idx];
+          }
+        }
+      }
+    }      /* end for idx loop over all available coordinates */
+    
+    if (idcPick >= 0)
+    {
+      cornerSelected[idcPick] = TRUE;
+      cornerSelectCount++;
+      alignCoords->refPtr[idcPick]->status = 1; /* mark this coord as already selected */
+    }
+  }
+  
+  if (cornerSelectCount != 4)
+  {
+    return (FALSE);
+  }
+  
+  for(idx=0; idx < GAP_ALIGN_COORDS_MAX; idx++)
+  {
+    if((alignCoords->refPtr[idx] == NULL) 
+    || (alignCoords->trkPtr[idx] == NULL))
+    {
+      /* stop in case some of the coordinate pointers could not be set to valid coordinates. */
+      if(gap_debug)
+      {
+        printf("PERc: ret(2) idx:%d refPtr:%ld trkPtr:%ld\n"
+          ,(int)idx
+          ,(long)alignCoords->refPtr[idx]
+          ,(long)alignCoords->trkPtr[idx]
+          );
+      }
+      return (FALSE);
+    }
+  }  
+  
+  
+  return (TRUE);
+
+}  /* end gap_geo_pick_corners_from_align_coords */
+
+
+/* --------------------------------------------------
+ * gap_geo_perspective_trans_coords_from_align_coords
+ * --------------------------------------------------
+ * calculate GapPerspectiveTransCoords from given align coordinates.
+ * The align coordinates must contain 4 valid start coordinates
+ *  (refrence points) 
+ * and 4 valid currentPoints in the activeDrawable (as typical recorded by detail tracking).
+ * This procedure calculates new corner points
+ * for a gimp perspective transformation
+ * that will transform the activeDrawable in a way that all
+ * 4 current coordinates will be placed on the 4 corresponding reference start Points.
+ * (typical usage is to compensate unwanted camera movement and rotations)
+ *
+ *
+ *    dc0x: -13               dc1x:  -14
+ *    dc0y: -12               dc1y:  -10
+ *       +-------------------+
+ *       |                   |
+ *       |                   |
+ *       |                   |
+ *       |                   |
+ *       +-------------------+
+ *   dc2x: -44                dc3x: -43
+ *   dc2y: -5                 dc3y: -6
+ *
+ */
+gboolean
+gap_geo_perspective_trans_coords_from_align_coords(gint32 activeDrawableId, GapAlignCoords *alignCoords, 
GapPerspectiveTransCoords *perCoords)
+{
+  gint    idx;
+  
+  GapLineDescriptionConsts upperBorderLine;
+  GapLineDescriptionConsts lowerBorderLine;
+  GapLineDescriptionConsts leftBorderLine;
+  GapLineDescriptionConsts rightBorderLine;
+  GapLineDescriptionConsts refLine;
+  GapLineDescriptionConsts trkLine;
+  GapLineDescriptionConsts extLine;
+  
+  GapDoubleCoords refInterPt;
+  GapDoubleCoords trkInterPt;
+  
+  gdouble d0x, d1x, d2x, d3x;      /* horizontal delta offsets at border intersection */ 
+  gdouble d0y, d1y, d2y, d3y;      /* vertical   delta offsets at border intersection */
+  
+  /* extended reference lines interscetion with border lines */
+  gdouble riTop0x,   riTop1x;
+  gdouble riBot2x,   riBot3x;
+  gdouble riLeft0y,  riLeft1y;
+  gdouble riRight2y, riRight3y;
+  
+  gdouble dc0x, dc1x, dc2x, dc3x;  /* horizontal delta offsets 4 corners */ 
+  gdouble dc0y, dc1y, dc2y, dc3y;  /* vertical   delta offsets 4 corners */ 
+  
+  gdouble width;
+  gdouble height;
+  
+  if(activeDrawableId < 0)
+  {
+    width = perCoords->width;
+    height = perCoords->height;
+  }
+  else
+  {
+    width = (gdouble)gimp_drawable_width(activeDrawableId);
+    height = (gdouble)gimp_drawable_height(activeDrawableId);
+    perCoords->width = width;
+    perCoords->height = height;
+  }
+
+  if(gap_debug)
+  {
+        printf("PER: activeDrawableId: %d  width:%f height:%f\n"
+           , (int)activeDrawableId
+           , (float)width
+           , (float)height
+           );
+  }
+
+
+  /* alignCoords->refPtr and alignCoords->trkPtr are assigned (picked) in sorted order:
+   * Ptr[0] shall point to coord nearest to upper left corner
+   * Ptr[1] shall point to coord nearest to upper right corner
+   * Ptr[2] shall point to coord nearest to lower left corner
+   * Ptr[3] shall point to coord nearest to lower right corner
+   */
+  if(TRUE != gap_geo_pick_corners_from_align_coords(alignCoords, width, height))
+  {
+    return (FALSE);  /* stop in case picking coords near the corners failed */
+  }
+
+  perCoords->numberOfArrayValues = 0;
+
+  /* The initial new x/y coordinates of upper-left corner */
+  perCoords->x0 = 0;    
+  perCoords->y0 = 0;
+      
+  /* The initial new x/y coordinates of upper-right corner */
+  perCoords->x1 = width; 
+  perCoords->y1 = 0;
+      
+  /* The initial new x/y coordinate of lower-left corner */
+  perCoords->x2 = 0;
+  perCoords->y2 = height;
+    
+  /* The initial new x/y coordinate of lower-right corner */
+  perCoords->x3 = width;
+  perCoords->y3 = height;
+
+  
+  /* ---- the following not correct working code part may be is skipped complete
+   * ---- and use only the iterative Calculation workaround...
+   * ---- ... but it is now executed to have better inital values
+   * ----     that leads to less iterations and typical faster execution.
+   * ---------------------------------------------------------------------
+   */
+  // goto iterativeCalculation;
+
+
+
+  
+  /* calculate line description for the drawable border lines */
+  gap_geo_line_description_from_2Points(0.0,     0.0        /* x1,y1 */
+                                       ,width, 0.0        /* x2,y2 */ 
+                                       ,&upperBorderLine);
+  gap_geo_line_description_from_2Points(0.0,     height   /* x1,y1 */
+                                       ,width, height   /* x2,y2 */
+                                       ,&lowerBorderLine);
+  gap_geo_line_description_from_2Points(0.0,     0.0        /* x1,y1 */
+                                       ,0.0,     height   /* x2,y2 */ 
+                                       ,&leftBorderLine);
+  gap_geo_line_description_from_2Points(width, 0.0        /* x1,y1 */
+                                       ,width, height   /* x2,y2 */
+                                       ,&rightBorderLine);
+  
+  /* lines from point [0] to point [2] on left side shall have vertical orientation */
+  gap_geo_line_description_from_2GapPixelCoords(alignCoords->refPtr[0], alignCoords->refPtr[2], &refLine);
+  gap_geo_line_description_from_2GapPixelCoords(alignCoords->trkPtr[0], alignCoords->trkPtr[2], &trkLine);
+
+  gap_geo_line_intersection(&upperBorderLine, &refLine,  &refInterPt);
+  gap_geo_line_intersection(&upperBorderLine, &trkLine,  &trkInterPt);
+  if((refInterPt.valid != TRUE) || (trkInterPt.valid != TRUE))
+  {
+    if(gap_debug)
+    {
+      printf("PER: ret(3)\n");
+    }
+    return (FALSE);
+  }
+  d0x = refInterPt.x - trkInterPt.x;
+  riTop0x = refInterPt.x;
+  
+  gap_geo_line_intersection(&lowerBorderLine, &refLine,  &refInterPt);
+  gap_geo_line_intersection(&lowerBorderLine, &trkLine,  &trkInterPt);
+  if((refInterPt.valid != TRUE) || (trkInterPt.valid != TRUE))
+  {
+    if(gap_debug)
+    {
+      printf("PER: ret(4)\n");
+    }
+    return (FALSE);
+  }
+  d2x = refInterPt.x - trkInterPt.x;
+  riBot2x = refInterPt.x;
+
+  /* lines from point [1] to point [3] on right side shall have vertical orientation */
+  gap_geo_line_description_from_2GapPixelCoords(alignCoords->refPtr[1], alignCoords->refPtr[3], &refLine);
+  gap_geo_line_description_from_2GapPixelCoords(alignCoords->trkPtr[1], alignCoords->trkPtr[3], &trkLine);
+
+  gap_geo_line_intersection(&upperBorderLine, &refLine,  &refInterPt);
+  gap_geo_line_intersection(&upperBorderLine, &trkLine,  &trkInterPt);
+  if((refInterPt.valid != TRUE) || (trkInterPt.valid != TRUE))
+  {
+    if(gap_debug)
+    {
+      printf("PER: ret(5)\n");
+    }
+    return (FALSE);
+  }
+  d1x = refInterPt.x - trkInterPt.x;
+  riTop1x = refInterPt.x;
+
+  gap_geo_line_intersection(&lowerBorderLine, &refLine,  &refInterPt);
+  gap_geo_line_intersection(&lowerBorderLine, &trkLine,  &trkInterPt);
+  if((refInterPt.valid != TRUE) || (trkInterPt.valid != TRUE))
+  {
+    if(gap_debug)
+    {
+      printf("PER: ret(6)\n");
+    }
+    return (FALSE);
+  }
+  d3x = refInterPt.x - trkInterPt.x;
+  riBot3x = refInterPt.x;
+
+
+  /* lines from point [0] to point [1] on upper side shall have horizontal orientation */
+  gap_geo_line_description_from_2GapPixelCoords(alignCoords->refPtr[0], alignCoords->refPtr[1], &refLine);
+  gap_geo_line_description_from_2GapPixelCoords(alignCoords->trkPtr[0], alignCoords->trkPtr[1], &trkLine);
+
+  gap_geo_line_intersection(&leftBorderLine, &refLine,  &refInterPt);
+  gap_geo_line_intersection(&leftBorderLine, &trkLine,  &trkInterPt);
+  if((refInterPt.valid != TRUE) || (trkInterPt.valid != TRUE))
+  {
+    if(gap_debug)
+    {
+      printf("PER: ret(7)\n");
+    }
+    return (FALSE);
+  }
+  d0y = refInterPt.y - trkInterPt.y;
+  riLeft0y = refInterPt.y;
+
+  gap_geo_line_intersection(&rightBorderLine, &refLine,  &refInterPt);
+  gap_geo_line_intersection(&rightBorderLine, &trkLine,  &trkInterPt);
+  if((refInterPt.valid != TRUE) || (trkInterPt.valid != TRUE))
+  {
+    if(gap_debug)
+    {
+      printf("PER: ret(8)\n");
+    }
+    return (FALSE);
+  }
+  d1y = refInterPt.y - trkInterPt.y;
+  riRight2y = refInterPt.y;
+
+
+  /* lines from point [2] to point [3] on lower side shall have horizontal orientation */
+  gap_geo_line_description_from_2GapPixelCoords(alignCoords->refPtr[2], alignCoords->refPtr[3], &refLine);
+  gap_geo_line_description_from_2GapPixelCoords(alignCoords->trkPtr[2], alignCoords->trkPtr[3], &trkLine);
+
+  gap_geo_line_intersection(&leftBorderLine, &refLine,  &refInterPt);
+  gap_geo_line_intersection(&leftBorderLine, &trkLine,  &trkInterPt);
+  if((refInterPt.valid != TRUE) || (trkInterPt.valid != TRUE))
+  {
+    if(gap_debug)
+    {
+      printf("PER: ret(9)\n");
+    }
+    return (FALSE);
+  }
+  d2y = refInterPt.y - trkInterPt.y;
+  riLeft1y = refInterPt.y;
+
+  gap_geo_line_intersection(&rightBorderLine, &refLine,  &refInterPt);
+  gap_geo_line_intersection(&rightBorderLine, &trkLine,  &trkInterPt);
+  if((refInterPt.valid != TRUE) || (trkInterPt.valid != TRUE))
+  {
+    if(gap_debug)
+    {
+      printf("PER: ret(10)\n");
+    }
+    return (FALSE);
+  }
+  d3y = refInterPt.y - trkInterPt.y;
+  riRight3y = refInterPt.y;
+
+
+  /* delta distances at border intersection point are now extrapolated to the corner points */
+  gap_geo_line_description_from_2Points(riTop0x, d0x, riTop1x, d1x, &extLine);
+  dc0x = gap_geo_line_getY(&extLine, 0);
+  dc1x = gap_geo_line_getY(&extLine, width);
+
+  gap_geo_line_description_from_2Points(riBot2x, d2x, riBot3x, d3x, &extLine);
+  dc2x = gap_geo_line_getY(&extLine, 0);
+  dc3x = gap_geo_line_getY(&extLine, width);
+  
+  gap_geo_line_description_from_2Points(riLeft0y, d0y, riLeft1y, d2y, &extLine);
+  dc0y = gap_geo_line_getY(&extLine, 0);
+  dc2y = gap_geo_line_getY(&extLine, height);
+
+  gap_geo_line_description_from_2Points(riRight2y, d1y, riRight3y, d3y, &extLine);
+  dc1y = gap_geo_line_getY(&extLine, 0);
+  dc3y = gap_geo_line_getY(&extLine, height);
+  
+  
+  
+  
+  
+  /* The new x/y coordinates of upper-left corner */
+  perCoords->x0 = dc0x;    
+  perCoords->y0 = dc0y;
+  
+  /* The new x/y coordinates of upper-right corner */
+  perCoords->x1 = width + dc1x; 
+  perCoords->y1 = dc1y;
+  
+  /* The new x/y coordinate of lower-left corner */
+  perCoords->x2 = dc2x;
+  perCoords->y2 = height + dc2y;
+
+  /* The new x/y coordinate of lower-right corner */
+  perCoords->x3 = width + dc3x;
+  perCoords->y3 = height + dc3y;
+  
+
+  if(gap_debug)
+  {
+    printf("gap_geo_perspective_trans_coords_from_align_coords: activeDrawableId:%d width:%d height:%d\n"
+           "    p0: %f %f\n"
+           "    p1: %f %f\n"
+           "    p2: %f %f\n"
+           "    p3: %f %f\n"
+           "    d0: %f %f\n"
+           "    d1: %f %f\n"
+           "    d2: %f %f\n"
+           "    d3: %f %f\n"
+           "    riTop0x: %f riTop1x: %f\n"
+           "    riBot2x: %f riBot3x: %f\n"
+           "    riLeft0y: %f riLeft1y: %f\n"
+           "    riRight2y: %f riRight3y: %f\n"
+           
+           "    dc0: %f %f\n"
+           "    dc1: %f %f\n"
+           "    dc2: %f %f\n"
+           "    dc3: %f %f\n"
+      ,(int)activeDrawableId
+      ,(int)perCoords->width
+      ,(int)perCoords->height
+      ,(float)perCoords->x0
+      ,(float)perCoords->y0
+      ,(float)perCoords->x1
+      ,(float)perCoords->y1
+      ,(float)perCoords->x2
+      ,(float)perCoords->y2
+      ,(float)perCoords->x3
+      ,(float)perCoords->y3
+      ,(float)d0x
+      ,(float)d0y
+      ,(float)d1x
+      ,(float)d1y
+      ,(float)d2x
+      ,(float)d2y
+      ,(float)d3x
+      ,(float)d3y
+      ,(float) riTop0x,   (float) riTop1x
+      ,(float) riBot2x,   (float)riBot3x
+      ,(float) riLeft0y,  (float)riLeft1y
+      ,(float) riRight2y, (float)riRight3y
+      ,(float)dc0x
+      ,(float)dc0y
+      ,(float)dc1x
+      ,(float)dc1y
+      ,(float)dc2x
+      ,(float)dc2y
+      ,(float)dc3x
+      ,(float)dc3y
+      );
+  }
+
+iterativeCalculation:
+
+  /* perspective transformation iteratve corner_tuning 
+   * Note that the calculation of 
+   *   GapPerspectiveTransCoords *perCoords at this point
+   * is not yet correct, but is used as 1st guess for the itarative part
+   * that follows now to fix it via try and error attempts in a loop.
+   */
+  if(TRUE)
+  {
+    gint            iterationCount;
+    gdouble         maxDifX;
+    gdouble         maxDifY;
+    gdouble         iterPrecision;
+    gint            idxMaxDifX;
+    gint            idxMaxDifY;
+    
+    GimpMatrix3     matrix;
+    
+      
+    for(iterationCount = 0; iterationCount < TRANSFORM_MAX_ITERATIONCOUNT; iterationCount++)
+    {
+      gdouble         difX[4];
+      gdouble         difY[4];
+    
+      if(gap_debug)
+      {
+        printf("gap_geo_perspective_trans_coords_from_align_coords (ITERATION-Attempt): iterationCount:%d 
width:%d height:%d\n"
+             "    p0: %f %f\n"
+             "    p1: %f %f\n"
+             "    p2: %f %f\n"
+             "    p3: %f %f\n"
+          ,(int)iterationCount
+          ,(int)perCoords->width
+          ,(int)perCoords->height
+          ,(float)perCoords->x0
+          ,(float)perCoords->y0
+          ,(float)perCoords->x1
+          ,(float)perCoords->y1
+          ,(float)perCoords->x2
+          ,(float)perCoords->y2
+          ,(float)perCoords->x3
+          ,(float)perCoords->y3
+          );
+       }
+    
+      /* calculate the matrix from 4 displaced corners 
+       * (thes ame way as GIMP does for perspective transformation tools) 
+       */
+      gap_geo_assemble_perspective_transformation_matrix(&matrix, perCoords, activeDrawableId);
+
+      
+      /* calculate transformed trace coordinates and difference 
+       * to the expected corresponding refernce coordinates that should
+       * ideally be equal (or differ in perecision below 1 pixel)
+       */
+      maxDifX = 0;
+      maxDifY = 0;
+      idxMaxDifX = 0;
+      idxMaxDifY = 0;
+      for(idx=0; idx < 4; idx++)
+      {
+        GapDoubleCoords inPoint;
+        GapDoubleCoords outPoint;
+        
+        inPoint.x = (gdouble)alignCoords->trkPtr[idx]->px;
+        inPoint.y = (gdouble)alignCoords->trkPtr[idx]->py;
+        
+        gimp_matrix3_transform_point (&matrix
+                                     , inPoint.x     // gdouble x
+                                     , inPoint.y     // gdouble y
+                                     , &outPoint.x   // gdouble *newx
+                                     , &outPoint.y   // gdouble *newy
+                                     );
+
+        
+        difX[idx] = (gdouble)alignCoords->refPtr[idx]->px - outPoint.x;
+        difY[idx] = (gdouble)alignCoords->refPtr[idx]->py - outPoint.y;
+        
+        if(fabs(difX[idx]) > fabs(maxDifX))
+        {
+           maxDifX = difX[idx];
+           idxMaxDifX = idx;;
+        }
+    
+        if(fabs(difY[idx]) > fabs(maxDifY))
+        {
+           maxDifY = difY[idx];
+           idxMaxDifY = idx;;
+        }
+    
+        if(gap_debug)
+        {
+          if (idx == 0)
+          {
+             printf("iterationCount:%d\n"
+               , iterationCount
+               );
+          }
+          printf("  difX[%d]: %3.4f difY[%d]: %3.4f  outPoint.x: %4.5f outPoint.y: %4.5f refPoint.x: %04d 
refPoint.y: %04d\n"
+               , idx
+               , (float)difX[idx]
+               , idx
+               , (float)difY[idx]
+               , (float)outPoint.x
+               , (float)outPoint.y
+               , alignCoords->refPtr[idx]->px
+               , alignCoords->refPtr[idx]->py
+               );
+          if (idx == 3)
+          {
+             printf("iterationCount:%d idxMaxDifX: %d idxMaxDifY: %d  maxDifX: %f maxDifY: %f\n"
+               , iterationCount
+               , (int)idxMaxDifX
+               , (int)idxMaxDifY
+               , (float)maxDifX
+               , (float)maxDifY
+               );
+          }
+        }
+    
+      
+      }
+      
+      iterPrecision = MAX(fabs(maxDifX), fabs(maxDifY));
+
+      if (iterPrecision < perCoords->transformPrecisionThreshold)
+      {
+        /* record potential matches (for fine tuning render attempts) */
+        p_save_perCoords_as_array_value(perCoords, iterPrecision);
+      }
+
+      
+      if (iterPrecision < perCoords->transformPrecision)
+      {
+        /* check if all 4 coordinates are sufficent matching now...
+         * .. therfore escape from iterationCount loop
+         */
+        break;
+      }
+      else
+      {
+        /* in case of insufficient match iterate by stepsize 1/4 of measured difference 
+         * at the corner nearest to the point where max diff occured and give it another try..
+         */
+        //if (fabs(maxDifX) >= fabs(maxDifY))
+        {
+          if(difX[0] == maxDifX) { perCoords->x0 += (difX[0] * 0.125); }
+          if(difX[1] == maxDifX) { perCoords->x1 += (difX[1] * 0.125); }
+          if(difX[2] == maxDifX) { perCoords->x2 += (difX[2] * 0.125); }
+          if(difX[3] == maxDifX) { perCoords->x3 += (difX[3] * 0.125); }
+        }
+        //if (fabs(maxDifY) >= fabs(maxDifX))
+        {
+          if(difY[0] == maxDifY) { perCoords->y0 += (difY[0] * 0.125); }
+          if(difY[1] == maxDifY) { perCoords->y1 += (difY[1] * 0.125); }
+          if(difY[2] == maxDifY) { perCoords->y2 += (difY[2] * 0.125); }
+          if(difY[3] == maxDifY) { perCoords->y3 += (difY[3] * 0.125); }
+        }
+        
+      }
+    
+    }  /* end for(iterationCount ...) */
+    
+    if(gap_debug)
+    {
+      printf("gap_geo_perspective_trans_coords_from_align_coords (FINAL-Result iterationCount:%d 
iterPrecision:%f {< %f}) activeDrawableId:%d width:%d height:%d\n"
+             "    p0: %f %f\n"
+             "    p1: %f %f\n"
+             "    p2: %f %f\n"
+             "    p3: %f %f\n"
+        ,(int)iterationCount
+        ,(double)iterPrecision
+        ,(double)perCoords->transformPrecision
+        ,(int)activeDrawableId
+        ,(int)perCoords->width
+        ,(int)perCoords->height
+        ,(float)perCoords->x0
+        ,(float)perCoords->y0
+        ,(float)perCoords->x1
+        ,(float)perCoords->y1
+        ,(float)perCoords->x2
+        ,(float)perCoords->y2
+        ,(float)perCoords->x3
+        ,(float)perCoords->y3
+        );
+    }
+    
+    if (iterPrecision > 5)
+    {
+      printf("gap_geo_perspective_trans_coords_from_align_coords ITERATION FAILED iterPrecision:%f failure 
is more than 5 pixels\n"
+            ,(double)iterPrecision
+            );
+            
+      /* Reset per koordiantes to The initial new x/y coordinates of upper-left corner */
+      perCoords->x0 = 0;    
+      perCoords->y0 = 0;
+      /* The initial new x/y coordinates of upper-right corner */
+      perCoords->x1 = width; 
+      perCoords->y1 = 0;
+      /* The initial new x/y coordinate of lower-left corner */
+      perCoords->x2 = 0;
+      perCoords->y2 = height;
+      /* The initial new x/y coordinate of lower-right corner */
+      perCoords->x3 = width;
+      perCoords->y3 = height;      
+    }
+
+  }
+  
+  
+  return (TRUE);  
+  
+}  /* end gap_geo_perspective_trans_coords_from_align_coords */
+
diff --git a/gap/gap_geo.h b/gap/gap_geo.h
new file mode 100644
index 0000000..8e67b3c
--- /dev/null
+++ b/gap/gap_geo.h
@@ -0,0 +1,193 @@
+/*  gap_geo.h
+ *    This module provides structures and procedures to handle 2d geometric stuff
+ *    such as perspective transformation.
+ *
+ *  2016/04/18
+ */
+/* 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, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+/* Revision history
+ *  (2016/04/18)  2.8.x       hof: created
+ */
+
+#ifndef _GAP_GEO_H
+#define _GAP_GEO_H
+
+
+#include "config.h"
+
+
+
+typedef struct GapLineDescriptionConsts
+{
+  gdouble  a;
+  gdouble  b;
+  gdouble  c;
+} GapLineDescriptionConsts;
+
+typedef struct GapDoubleCoords
+{
+  gboolean  valid;
+  gdouble  x;
+  gdouble  y;
+} GapDoubleCoords;
+
+typedef struct GapPixelCoords
+{
+  gboolean  valid;
+  gint32    px;
+  gint32    py;
+  gdouble   avgColorDiff;    // 0 = best quality, 1 = worst quality
+  gint      status;
+} GapPixelCoords;
+
+#define GAP_ALIGN_COORDS_MAX 4
+
+typedef struct GapAlignCoords
+{
+  GapPixelCoords  currCoords[GAP_ALIGN_COORDS_MAX];      /* upto 4 coords in current frame */
+  GapPixelCoords  startCoords[GAP_ALIGN_COORDS_MAX];     /* upto 4 coords of first processed (reference) 
frame */
+  GapPixelCoords  untunedCoords[GAP_ALIGN_COORDS_MAX];   /* upto 4 untuned coords in current frame */
+
+  /* pointer sets to pick 4 coordinates correspoinding to coordinates near the 4 corners */
+  GapPixelCoords  *trkPtr[4];   /* upto 4 coords in current frame  currCoords[4]; */
+  GapPixelCoords  *refPtr[4];   /* upto 4 coords of first processed (reference) frame  startCoords[4]; */
+} GapAlignCoords;
+
+#define MAX_ATTEMPTS_PERSPECTIVE          309  //109
+#define MAX_ITER_ATTEMPTS_PERSPECTIVE     300  //100
+
+/*
+ * GAP_GEO_TRANSFORM_PRECISION_THRSHOLD is used for fine tuning purpose 
+ *   (save perspective coords of iterations that are better than this threshold for finetuning)
+ */
+//#define GAP_GEO_TRANSFORM_PRECISION_THRSHOLD      0.25
+//#define GAP_GEO_TRANSFORM_PRECISION               0.1
+
+//#define GAP_GEO_TRANSFORM_PRECISION_THRSHOLD      0.12
+//#define GAP_GEO_TRANSFORM_PRECISION               0.005
+
+//#define GAP_GEO_TRANSFORM_PRECISION_THRSHOLD      0.08
+//#define GAP_GEO_TRANSFORM_PRECISION               0.001
+
+//#define GAP_GEO_TRANSFORM_PRECISION_THRSHOLD      0.001
+//#define GAP_GEO_TRANSFORM_PRECISION               0.00025
+
+
+//#define GAP_GEO_TRANSFORM_PRECISION_THRSHOLD      0.25
+//#define GAP_GEO_TRANSFORM_PRECISION               0.001
+
+#define GAP_GEO_TRANSFORM_PRECISION_THRSHOLD      1.4
+#define GAP_GEO_TRANSFORM_PRECISION               0.2
+
+
+
+typedef struct GapPerspectiveTransCoords
+{
+  gdouble  width;
+  gdouble  height;
+  
+  /* The new x/y coordinates of upper-left corner */
+  gdouble  x0;    
+  gdouble  y0;
+    
+  /* The new x/y coordinates of upper-right corner */
+  gdouble  x1; 
+  gdouble  y1;
+    
+  /* The new x/y coordinate of lower-left corner */
+  gdouble  x2;
+  gdouble  y2;
+  
+  /* The new x/y coordinate of lower-right corner */
+  gdouble  x3;
+  gdouble  y3;
+
+
+
+  /* array for some more potential perspective transformation attempts
+   * typically with nearly the same values slightly different in precision.
+   * (used for fine tuning via probe rendering in the exact aligner)
+   */
+  gint     numberOfArrayValues;
+  gdouble  aprecision[MAX_ATTEMPTS_PERSPECTIVE];
+  gdouble  ax0[MAX_ATTEMPTS_PERSPECTIVE];    
+  gdouble  ay0[MAX_ATTEMPTS_PERSPECTIVE];
+  gdouble  ax1[MAX_ATTEMPTS_PERSPECTIVE]; 
+  gdouble  ay1[MAX_ATTEMPTS_PERSPECTIVE];
+  gdouble  ax2[MAX_ATTEMPTS_PERSPECTIVE];
+  gdouble  ay2[MAX_ATTEMPTS_PERSPECTIVE];
+  gdouble  ax3[MAX_ATTEMPTS_PERSPECTIVE];
+  gdouble  ay3[MAX_ATTEMPTS_PERSPECTIVE];
+
+  /* precision settings for iterative calculations */
+  gdouble    transformPrecision;           /* precision in pixels for calculating the perspective 
transformation matrix (0.0 to 1.0) */
+  gdouble    transformPrecisionThreshold;  /* for fine tuning purpose (use perespective coords with 
precision lower than threshold) */
+  
+} GapPerspectiveTransCoords;
+
+
+
+void                gap_geo_copy_src_to_dst_coords(GapPixelCoords *srcCoords, GapPixelCoords *dstCoords);
+gdouble             gap_geo_calculateSqrDistX2Y2(GapPixelCoords *coordA, gdouble px, gdouble py);
+gdouble             gap_geo_calculateSqrDist(GapPixelCoords *coordA, GapPixelCoords *coordB);
+gdouble             gap_geo_calculateDist(GapPixelCoords *coordA, GapPixelCoords *coordB);
+
+
+void                gap_geo_line_description_from_2Points(gdouble x1, gdouble y1, gdouble x2, gdouble y2, 
GapLineDescriptionConsts *lineConst);
+void                gap_geo_line_description_from_2GapPixelCoords(GapPixelCoords *p1, GapPixelCoords *p2, 
GapLineDescriptionConsts *lineConst);
+gdouble             gap_geo_line_getX(GapLineDescriptionConsts *lineConst, gdouble y);
+gdouble             gap_geo_line_getY(GapLineDescriptionConsts *lineConst, gdouble x);
+void                gap_geo_line_intersection(GapLineDescriptionConsts *line1, GapLineDescriptionConsts 
*line2, GapDoubleCoords *interPt);
+gboolean            gap_geo_pick_corners_from_align_coords(GapAlignCoords *alignCoords, gdouble width, 
gdouble height);
+gboolean            gap_geo_perspective_trans_coords_from_align_coords(gint32 activeDrawableId, 
GapAlignCoords *alignCoords, GapPerspectiveTransCoords *perCoords);
+void                gap_geo_assemble_perspective_transformation_matrix(GimpMatrix3  *matrix, 
GapPerspectiveTransCoords *perCoords, gint32 activeDrawableId);
+void                gap_geo_DuplicateAlignCoords(GapAlignCoords *alignCoords, GapAlignCoords 
*alignCoordsDup);
+
+/* GIMP core internal matrix and perspective handling procedures 
+ *     gimp_transform_matrix_perspective
+ *     gimp_matrix3_identity
+ * are not available for public use.
+ * As workaround this module uses its own copies of those procedures (renamed with prefix gap_)
+ */
+void          gap_matrix3_mult            (const GimpMatrix3 *matrix1,
+                                            GimpMatrix3       *matrix2);
+void          gap_matrix3_translate       (GimpMatrix3       *matrix,
+                                            gdouble            x,
+                                            gdouble            y);
+void          gap_matrix3_scale           (GimpMatrix3       *matrix,
+                                            gdouble            x,
+                                            gdouble            y);
+void       gap_matrix3_identity               (GimpMatrix3       *matrix);
+void       gap_transform_matrix_perspective   (GimpMatrix3         *matrix,
+                                                gint                 x,
+                                                gint                 y,
+                                                gint                 width,
+                                                gint                 height,
+                                                gdouble              t_x1,
+                                                gdouble              t_y1,
+                                                gdouble              t_x2,
+                                                gdouble              t_y2,
+                                                gdouble              t_x3,
+                                                gdouble              t_y3,
+                                                gdouble              t_x4,
+                                                gdouble              t_y4);
+                                                
+
+#endif  /* _GAP_GEO_H */
diff --git a/gap/gap_layer_copy.c b/gap/gap_layer_copy.c
index c9ca53a..fbd0180 100644
--- a/gap/gap_layer_copy.c
+++ b/gap/gap_layer_copy.c
@@ -1030,3 +1030,45 @@ gap_layer_new_same_size_and_offsets(gint32 image_id, gint32 origLayerId, gboolea
   return (l_new_layer_id);
           
 }  /* end gap_layer_new_same_size_and_offsets */
+
+
+
+/* --------------------------------
+ * gap_layer_resize_to_selection
+ * --------------------------------
+ * resize the layer to seletion bounds of the specified selImageId.
+ * but keep original layer size in case the selImageId has empty selection.
+ */
+void
+gap_layer_resize_to_selection(gint32 selImageId, gint32 layerId)
+{
+  gboolean has_selection;
+  gboolean non_empty;
+  gint     x1, y1, x2, y2;
+  
+  has_selection  = gimp_selection_bounds(selImageId, &non_empty, &x1, &y1, &x2, &y2);
+  if ((has_selection) && (non_empty))
+  {
+     gint32 newWidth;
+     gint32 newHeight;  /*   New layer width (1 <= new-width <= 524288) */
+     gint32 offx;       /* x offset between upper left corner of old and new layers: (old - new)  */
+     gint32 offy;       /* y offset between upper left corner of old and new layers: (old - new)  */
+
+    if (!gimp_drawable_has_alpha(layerId))
+    {
+      gimp_layer_add_alpha(layerId);
+    }
+    /* first resize to image size */
+    gimp_layer_resize_to_image_size (layerId);
+    
+    offx = 0 - x1;
+    offy = 0 - y1;
+    newWidth = x2 - x1;
+    newHeight = y2 - y1;
+    
+    gimp_layer_resize(layerId, newWidth, newHeight, offx, offy);
+
+
+  }
+
+}  /* end gap_layer_resize_to_selection */
diff --git a/gap/gap_layer_copy.h b/gap/gap_layer_copy.h
index 4862d8c..bfc41d2 100644
--- a/gap/gap_layer_copy.h
+++ b/gap/gap_layer_copy.h
@@ -97,4 +97,6 @@ gint32  gap_layer_find_by_name(gint32 image_id, const char *name);
 
 gint32  gap_layer_new_same_size_and_offsets(gint32 image_id, gint32 origLayerId, gboolean imageSize);
 
+void    gap_layer_resize_to_selection(gint32 selImageId, gint32 layerId);
+
 #endif
diff --git a/gap/gap_locate2.c b/gap/gap_locate2.c
index 5072932..a3a66a6 100644
--- a/gap/gap_locate2.c
+++ b/gap/gap_locate2.c
@@ -41,6 +41,8 @@
 #define MAX_DIFF_VALUE_PER_PIXEL  (255.0 + 255.0 + 255.0)
 #define OPACITY_LEVEL_UCHAR 50
 
+#define GOOD_MATCH_FACTOR 1.05
+
 extern int gap_debug;
 
 typedef struct Context {
@@ -63,12 +65,77 @@ typedef struct Context {
   gint32  bestMatchingPixelCount;   /* number of pixels involved in best matching attempt */
   gdouble bestMatchingDistance;     /* square distance from reference coords to the offset of the best 
matching attempt */
   gdouble bestMatchingSumDiffValue; /* summ of the RGB channel differences of the best matching attempt */
+  gdouble bestMatchingAvgColordiff;
+  gdouble goodMatchingSumDiffValue; /* summ of the RGB channel differences of the best matching attempt plus 
10 percent */
   gdouble veryNearDistance;         /* square of near radius to stop evaluation when exactly matching area 
is detected */
   GimpDrawable *refDrawable;
   GimpDrawable *targetDrawable;
-  
 } Context;
 
+  /* size of the color relation aerea 13 x 13 pixels
+   * for tuning located coordinates
+   * (effective checked  area size is 9x9 pixels
+   * with tuning offsets to the center shifted by upto +-2 pixels in any direction)
+   *
+   * 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12
+   *                   6,
+   */
+//#define GAP_COLOR_REL_ARRAY_SIZE   13
+//#define GAP_COLOR_REL_CENTER_INDEX 6
+//#define GAP_COLOR_REL_BORDER       2
+       
+  /* size of the color relation aerea 23 x 23 pixels
+   * for tuning located coordinates
+   * (effective checked  area size is 15x15 pixels
+   * with tuning offsets to the center shifted by upto +-4 pixels in any direction)
+   *
+   * 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22
+   *                .  .  .  .  .  .   11  .   .   .   .   .   .   .
+   */
+//#define GAP_COLOR_REL_ARRAY_SIZE   23
+//#define GAP_COLOR_REL_CENTER_INDEX 11
+//#define GAP_COLOR_REL_BORDER        4
+
+  /* size of the color relation aerea 31 x 31 pixels
+   * for tuning located coordinates
+   * (effective checked  area size is 23x23 pixels
+   * with tuning offsets to the center shifted by upto +-4 pixels in any direction)
+   *
+   * 0, 1, 2, 3,   4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,   
27, 28, 29, 30
+   *               .  .  .  .  .  .  .   .   .   .   .   15  .   .   .   .   .   .   .   .   .   .   . 
+   */
+//#define GAP_COLOR_REL_ARRAY_SIZE   31
+//#define GAP_COLOR_REL_CENTER_INDEX 15
+//#define GAP_COLOR_REL_BORDER        4
+
+  /* size of the color relation aerea 37 x 37 pixels
+   * for tuning located coordinates
+   * (effective checked  area size is 29x29 pixels
+   * with tuning offsets to the center shifted by upto +-4 pixels in any direction)
+   *
+   * 0, 1, 2, 3,   4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 
28, 29, 30, 31, 32, 33, 34, 35, 36
+   *               .  .  .  .  .  .  .   .   .   .   .   .   .   .   18  .   .   .   .   .   .   .   .   .   
.   .   .   .   .
+   */
+#define GAP_COLOR_REL_ARRAY_SIZE   37
+#define GAP_COLOR_REL_CENTER_INDEX 18
+#define GAP_COLOR_REL_BORDER        4
+
+
+#define GAP_COLOR_REL_REQ          (GAP_COLOR_REL_ARRAY_SIZE - (1 + (2* GAP_COLOR_REL_BORDER)))
+#define GAP_COLOR_REL_REQUIRED_PIXELS (GAP_COLOR_REL_REQ * GAP_COLOR_REL_REQ)
+
+
+
+typedef struct ColorRelation
+{
+  guchar rgba   [GAP_COLOR_REL_ARRAY_SIZE][GAP_COLOR_REL_ARRAY_SIZE][4];
+  gboolean isEmpty [GAP_COLOR_REL_ARRAY_SIZE][GAP_COLOR_REL_ARRAY_SIZE];
+  gint32 crRed   [GAP_COLOR_REL_ARRAY_SIZE][GAP_COLOR_REL_ARRAY_SIZE];
+  gint32 crGreen [GAP_COLOR_REL_ARRAY_SIZE][GAP_COLOR_REL_ARRAY_SIZE];
+  gint32 crBlue  [GAP_COLOR_REL_ARRAY_SIZE][GAP_COLOR_REL_ARRAY_SIZE];
+  gint32  pixelCount;
+} ColorRelation;
+
 static gdouble     p_calculate_average_colordiff(gdouble sumDiffValue, gint32 pixelCount);
 static void        p_compare_regions (const GimpPixelRgn *refPR
                     ,const GimpPixelRgn *targetPR
@@ -76,6 +143,54 @@ static void        p_compare_regions (const GimpPixelRgn *refPR
 static gdouble     p_calculate_distance_to_ref_coord(Context *context, gint32 px, gint32 py);
 static void        p_attempt_locate_at_current_offset(Context *context, gint32 px, gint32 py);
 
+static void        p_tuneLocatedCoordinates(Context *context, gint32  *targetX, gint32  *targetY);
+static void        p_fetchColorArray(ColorRelation *colRelPtr, GimpPixelFetcher *pft, gint width, gint 
height, gint centerX, gint centerY, gboolean hasAlpha, const char *info);
+static void        p_calculateColorRelationArrays(ColorRelation *colRelPtr, gint cx, gint cy);
+static gdouble     p_calculateColorRelationArraysDifference(ColorRelation *refColRelPtr, ColorRelation 
*targetColRelPtr, gint dx, gint dy, gint32 *pixelCount);
+static GapLocateTuneOffsElem * p_findTuneOffsShortList(Context *context, gint32  targetX, gint32  targetY);
+
+/* ----------------------------------------
+ * gap_locate_newGapLocateTuneOffsElem
+ * ----------------------------------------
+ */
+GapLocateTuneOffsElem *
+gap_locate_newGapLocateTuneOffsElem(gint idx, gint idy, gdouble relDiff)
+{
+   GapLocateTuneOffsElem *tuneOffsElem;
+   
+   
+   tuneOffsElem = g_new(GapLocateTuneOffsElem, 1);
+
+   tuneOffsElem->tuneOffsetX = idx;
+   tuneOffsElem->tuneOffsetY = idy;
+   tuneOffsElem->relDiff = relDiff;
+   tuneOffsElem->valid = TRUE;
+   tuneOffsElem->next = NULL;
+   
+   return(tuneOffsElem);
+}  /* end gap_locate_newGapLocateTuneOffsElem */
+
+/* ----------------------------------------
+ * gap_locate_freeTuneOffsList
+ * ----------------------------------------
+ */
+void
+gap_locate_freeTuneOffsList(GapLocateTuneOffsElem *rootElem)
+{
+  GapLocateTuneOffsElem *tuneOffsElem;
+  
+  tuneOffsElem = rootElem;
+  while(tuneOffsElem != NULL)
+  {
+    GapLocateTuneOffsElem *nextTuneOffsElem;
+    
+    nextTuneOffsElem = tuneOffsElem->next;
+    g_free(tuneOffsElem);
+    tuneOffsElem = nextTuneOffsElem;
+  }
+}  /* end gap_locate_freeTuneOffsList */
+
+
 
 /* ---------------------------------
  * p_calculate_average_colordiff
@@ -192,7 +307,7 @@ p_compare_regions (const GimpPixelRgn *refPR
      *  possibilities a little earlier, but i guess
      *  overall performance might be better when cecks are done only once per row.
      */
-    if (context->sumDiffValue > context->bestMatchingSumDiffValue)
+    if (context->sumDiffValue > context->goodMatchingSumDiffValue)
     {
       if (context->bestMatchingPixelCount >= context->almostFullAreaPixelCount)
       {
@@ -414,34 +529,58 @@ p_attempt_locate_at_current_offset(Context *context, gint32 px, gint32 py)
 
   
   if ((context->involvedPixelCount >= context->requiredPixelCount)
-  &&  (context->sumDiffValue <= context->bestMatchingSumDiffValue))
+  &&  (context->sumDiffValue <= context->goodMatchingSumDiffValue))
   {
-    if((context->sumDiffValue < context->bestMatchingSumDiffValue)
-    || ( context->currentDistance < context->bestMatchingDistance))
+    gdouble avgDiff;
+    
+    avgDiff = p_calculate_average_colordiff(context->sumDiffValue
+                                           , context->involvedPixelCount
+                                           );
+    //if(gap_debug)
+    //{
+    //  printf("Match Candidate: pixlCount:%d sumDiffValue: %f AVG(%f) currentDistance: %f sqrt(%f) \n"
+    //            "   goodMatchingSumDiffValue:%f bestMatching bMpixlCount:%d bMSumDiffValue: %f AVG(%f) 
bMDistance: %f sqr(%f)\n"
+    //        ,(int)context->involvedPixelCount
+    //        ,(float)context->sumDiffValue
+    //        ,(float)avgDiff
+    //        ,(float)context->currentDistance
+    //        ,(float)sqrt(context->currentDistance)
+    //        , (float) context->goodMatchingSumDiffValue
+    //        ,(int)context->bestMatchingPixelCount
+    //        ,(float)context->bestMatchingSumDiffValue
+    //        ,(float)context->bestMatchingAvgColordiff
+    //        ,(float)context->bestMatchingDistance
+    //        ,(float)sqrt(context->bestMatchingDistance)
+    //        );
+    //        
+    //}
+       
+    if((avgDiff  < context->bestMatchingAvgColordiff)
+    || ((avgDiff <= context->bestMatchingAvgColordiff) && ( context->currentDistance < 
context->bestMatchingDistance)))
     {
       context->bestMatchingSumDiffValue = context->sumDiffValue;
-      context->bestMatchingDistance = context->currentDistance;
       context->bestMatchingPixelCount = context->involvedPixelCount;
       context->bestX = px;
       context->bestY = py;
+      context->goodMatchingSumDiffValue  = context->bestMatchingSumDiffValue * GOOD_MATCH_FACTOR;  /* 5 
percent more than best */
+      context->bestMatchingAvgColordiff = avgDiff;
  
       if(gap_debug)
       {
-        gdouble bestMatchingAvgColordiff;
-        
-        bestMatchingAvgColordiff = p_calculate_average_colordiff(context->bestMatchingSumDiffValue
-                                    , context->bestMatchingPixelCount
-                                    );
-        printf("FOUND: bestX:%d bestY:%d squareDist:%d\n"
+        printf("FOUND: bestX:%d bestY:%d squareDist:%d Dist:%d bestSquareDist:%d bestDist:%d\n"
                "             sumDiffValues:%d pixelCount:%d bestMatchingAvgColordiff:%.5f\n"
           , (int)context->bestX
           , (int)context->bestY
+          , (int)context->currentDistance
+          , (int)sqrt(context->currentDistance)
           , (int)context->bestMatchingDistance
+          , (int)sqrt(context->bestMatchingDistance)
           , (int)context->bestMatchingSumDiffValue
           , (int)context->bestMatchingPixelCount
-          , (float)bestMatchingAvgColordiff
+          , (float)context->bestMatchingAvgColordiff
           );
       }
+      context->bestMatchingDistance = context->currentDistance;
        
       if ((context->currentDistance <= context->veryNearDistance)
       &&  (context->sumDiffValue == 0))
@@ -458,6 +597,630 @@ p_attempt_locate_at_current_offset(Context *context, gint32 px, gint32 py)
 
 
 /* --------------------------------------------
+ * p_tuneLocatedCoordinates                     DEPRECATED
+ * --------------------------------------------
+ * check color relations to nearest neighbour pixels
+ * and compare them ref against target layer
+ * where traget is shifted by 1 or 2 pixel offsets
+ * to find best matching color relations.
+ */
+static void
+p_tuneLocatedCoordinates(Context *context, gint32  *targetX, gint32  *targetY)
+{
+  GimpPixelFetcher *pftRef;
+  GimpPixelFetcher *pftTarget;
+  ColorRelation refColorRelation;
+  ColorRelation targetColorRelation;
+  gint    idx;
+  gint    idy;
+  gint32  requiredPixelCount;
+  gint32  tuneOffsetX;
+  gint32  tuneOffsetY;
+  gdouble minRelDiff;
+
+  gint32  tarX;
+  gint32  tarY;
+
+  tarX = *targetX;
+  tarY = *targetY;
+  
+  /* init pixel fetchers */
+  pftRef = gimp_pixel_fetcher_new (context->refDrawable, FALSE /* shadow */);
+  pftTarget = gimp_pixel_fetcher_new (context->targetDrawable, FALSE /* shadow */);
+  gimp_pixel_fetcher_set_edge_mode (pftRef, GIMP_PIXEL_FETCHER_EDGE_NONE);
+  gimp_pixel_fetcher_set_edge_mode (pftTarget, GIMP_PIXEL_FETCHER_EDGE_NONE);
+
+
+  /* copy small areas near the relevant coordinates
+   * into arrays of the ColorRelation structures for further tuning processing
+   */
+  p_fetchColorArray(&refColorRelation, pftRef
+                 , context->refDrawable->width, context->refDrawable->height
+                 , context->refX, context->refY
+                 , gimp_drawable_has_alpha(context->refDrawable->drawable_id)
+                 , "REF"
+                 );
+  p_fetchColorArray(&targetColorRelation, pftTarget
+                 , context->targetDrawable->width, context->targetDrawable->height
+                 , tarX, tarY
+                 , gimp_drawable_has_alpha(context->targetDrawable->drawable_id)
+                 , "TAR"
+                 );
+
+  p_calculateColorRelationArrays(&refColorRelation, GAP_COLOR_REL_CENTER_INDEX, GAP_COLOR_REL_CENTER_INDEX);
+       
+  requiredPixelCount = GAP_COLOR_REL_REQUIRED_PIXELS;
+  tuneOffsetX = 0;
+  tuneOffsetY = 0;
+  minRelDiff = (GAP_COLOR_REL_ARRAY_SIZE * GAP_COLOR_REL_ARRAY_SIZE * 3 * 256);
+  for(idx = 0 - GAP_COLOR_REL_BORDER; idx <= GAP_COLOR_REL_BORDER; idx++)
+  {
+    for(idy = 0 - GAP_COLOR_REL_BORDER; idy <= GAP_COLOR_REL_BORDER; idy++)
+    {
+      gdouble relDiff;
+      gint32     pixelCount;
+
+      p_calculateColorRelationArrays(&targetColorRelation, GAP_COLOR_REL_CENTER_INDEX +idx, 
GAP_COLOR_REL_CENTER_INDEX +idy);
+
+      relDiff = p_calculateColorRelationArraysDifference(&refColorRelation, &targetColorRelation, idx, idy, 
&pixelCount);
+      
+
+      
+      if ((pixelCount >= requiredPixelCount) && (relDiff < minRelDiff))
+      {
+        minRelDiff = relDiff;
+        tuneOffsetX = idx;
+        tuneOffsetY = idy;
+        
+        if(gap_debug)
+       {
+         printf("tuneLocatedCoordinates idx:%d, idy:%d, pixelCount:%d, requiredPixelCount:%d, relDiff:%f\n"
+                  ,(int)idx
+                  ,(int)idy
+                  ,(int)pixelCount
+                  ,(int)requiredPixelCount
+                  ,(float)relDiff
+                  );
+        }
+        
+        
+      }
+    }
+  }
+
+
+  gimp_pixel_fetcher_destroy (pftRef);
+  gimp_pixel_fetcher_destroy (pftTarget);
+
+       
+   *targetX = tarX + tuneOffsetX;
+   *targetY = tarY + tuneOffsetY;
+       
+}  /* end p_tuneLocatedCoordinates */       
+       
+
+
+/* ---------------------------------------
+ * p_fetchColorArray
+ * ---------------------------------------
+ * copy a small pixel environment area at centerX/Y of rgba pixels via pixelfetcher
+ * to the ColorRelation structure where transparent pixels and coordinates 
+ * outside the layer width/height are marked as empty.
+ */
+static void
+p_fetchColorArray(ColorRelation *colRelPtr, GimpPixelFetcher *pft, gint width, gint height, gint centerX, 
gint centerY, gboolean hasAlpha, const char *info)
+{
+   gint idx;
+   gint idy;
+      
+   for(idy = 0; idy < GAP_COLOR_REL_ARRAY_SIZE; idy ++)
+   
+   {
+     guchar debugLine[GAP_COLOR_REL_ARRAY_SIZE +1];
+     for(idx = 0; idx < GAP_COLOR_REL_ARRAY_SIZE; idx ++)
+     {
+         gint px;
+         gint py;
+         
+         px = (centerX + idx) - GAP_COLOR_REL_CENTER_INDEX; 
+         py = (centerY + idy) - GAP_COLOR_REL_CENTER_INDEX;
+         
+         if(gap_debug)
+         {
+           if ((idx == 0) && (idy ==0))
+           {
+             printf("\np_fetchColorArray px:%d, py:%d %s\n", (int)px, (int)py, info);
+           }
+         }
+         
+         if ((px >= 0) && (px < width) && (py >= 0) && (py < height))
+         {
+           guchar *environmentPixelRgbaPtr;
+         
+           environmentPixelRgbaPtr = &colRelPtr->rgba[idx][idy][0];
+           gimp_pixel_fetcher_get_pixel (pft, px, py, environmentPixelRgbaPtr);
+           if ((hasAlpha) && (environmentPixelRgbaPtr[3] == 0))  /* full transparent alpha channel */
+           {
+             colRelPtr->isEmpty[idx][idy] = TRUE;
+             debugLine[idx] = '.';
+           }
+           else
+           {
+             colRelPtr->isEmpty[idx][idy] = FALSE;
+             debugLine[idx] = '#';
+           }
+         }
+         else
+         {
+           /* set alpha channel fully transparent when outside layer */
+           colRelPtr->isEmpty[idx][idy] = TRUE;
+           debugLine[idx] = 'o';
+         }
+
+     }
+     if(gap_debug)
+     {
+       debugLine[GAP_COLOR_REL_ARRAY_SIZE] = '\0';
+       printf ("%s\n", &debugLine[0]);
+     }
+   }
+
+}  /* end p_fetchColorArray */
+
+
+/* ---------------------------------------
+ * p_calculateColorRelationArrays
+ * ---------------------------------------
+ * calculate relation of all pixels in the ColorRelation arrays
+ * as R,G,B differences to the pixel at position cx/cy.
+ */
+static void
+p_calculateColorRelationArrays(ColorRelation *colRelPtr, gint cx, gint cy)
+{
+   gint centerRed;
+   gint centerGreen;
+   gint centerBlue;
+   gint idx;
+   gint idy;
+      
+   centerRed   = (gint)colRelPtr->rgba[cx][cy][0];
+   centerGreen = (gint)colRelPtr->rgba[cx][cy][1];
+   centerBlue  = (gint)colRelPtr->rgba[cx][cy][2];
+   
+   colRelPtr->pixelCount = 0;
+   
+   for(idx = 0; idx < GAP_COLOR_REL_ARRAY_SIZE; idx ++)
+   {
+     for(idy = 0; idy < GAP_COLOR_REL_ARRAY_SIZE; idy ++)
+     {
+         if (colRelPtr->isEmpty[idx][idy] != TRUE)
+         {
+           gint red;
+           gint green;
+           gint blue;
+           
+           red   = (gint)colRelPtr->rgba[idx][idy][0];
+           green = (gint)colRelPtr->rgba[idx][idy][1];
+           blue  = (gint)colRelPtr->rgba[idx][idy][2];
+           
+           colRelPtr->crRed[idx][idy]   = centerRed   - red;
+           colRelPtr->crGreen[idx][idy] = centerGreen - green;
+           colRelPtr->crBlue[idx][idy]  = centerBlue  - blue;
+           
+           colRelPtr->pixelCount++;
+         }
+     }
+   }
+
+}  /* end p_calculateColorRelationArrays */
+
+
+/* ----------------------------------------
+ * p_calculateColorRelationArraysDifference
+ * ----------------------------------------
+ * calculate relative difference by comparing 2 ColorRelation Arrays.
+ * as average of all relation differences in R,G,B channels in all "NOT EMPTY" pixels.
+ * EMPTY pixels are either fully transparent or outside the layer boundaries.
+ * Note that the effective compared area size is smaller than the color relation Arrays
+ * because the comparison is done with the center of the target ColorRelation shifted by dx, dy
+ * (where dx and dy are in the range from -2 to +2 pixels)
+ * For a full size GAP_COLOR_REL_ARRAY_SIZE of 13x13 the effective compared area is 9x9 pixels.
+ */
+static gdouble
+p_calculateColorRelationArraysDifference(ColorRelation *refColRelPtr, ColorRelation *targetColRelPtr, gint 
dx, gint dy, gint32 *pixelCount)
+{
+   gint rdx;
+   gint rdy;
+   gint tdx;
+   gint tdy;
+   gint32 count;
+   
+   gdouble absDiff;
+   gdouble relDiff;
+
+   relDiff = 0;
+   absDiff = 0;
+   count = 0;
+   for(rdx = GAP_COLOR_REL_BORDER; rdx < GAP_COLOR_REL_ARRAY_SIZE - GAP_COLOR_REL_BORDER; rdx ++)
+   {
+     tdx = rdx + dx;
+     for(rdy = GAP_COLOR_REL_BORDER; rdy < GAP_COLOR_REL_ARRAY_SIZE - GAP_COLOR_REL_BORDER; rdy ++)
+     {
+       tdy = rdy + dy;
+       if ((refColRelPtr->isEmpty[rdx][rdy] != TRUE)
+       &&  (targetColRelPtr->isEmpty[tdx][tdy] != TRUE))
+       {
+         count++;
+         absDiff += abs(refColRelPtr->crRed[rdx][rdy]   - targetColRelPtr->crRed[tdx][tdy]);
+         absDiff += abs(refColRelPtr->crGreen[rdx][rdy] - targetColRelPtr->crGreen[tdx][tdy]);
+         absDiff += abs(refColRelPtr->crBlue[rdx][rdy]  - targetColRelPtr->crBlue[tdx][tdy]);
+       }
+     }
+   }
+   
+   if (count > 0)
+   {
+     relDiff = absDiff / (count * 255 * 3);
+   }
+   *pixelCount = count;
+   return (relDiff);
+   
+}  /* end p_calculateColorRelationArraysDifference */
+
+
+/* --------------------------------------------
+ * p_findTuneOffsShortList
+ * --------------------------------------------
+ * check color relations to nearest neighbour pixels
+ * and compare them ref against target layer
+ * where traget is shifted by 1,2,3 or 4 pixel offsets
+ * to find a list of the offset variants sorted by best matching color relations at begin of the returned 
list.
+ *
+ * (for tuning purpose)
+ * Note the caller is responsible to free the returned list
+ */
+static GapLocateTuneOffsElem *
+p_findTuneOffsShortList(Context *context, gint32  targetX, gint32  targetY)
+{
+  GapLocateTuneOffsElem    *tuneOffsShortList; 
+  GimpPixelFetcher *pftRef;
+  GimpPixelFetcher *pftTarget;
+  ColorRelation refColorRelation;
+  ColorRelation targetColorRelation;
+  gint    offsets[] = { 0, -1, 1, -2, 2, -3, 3, -4, 4};
+  gint    iidx;
+  gint    iidy;
+  gint    idx;
+  gint    idy;
+  gint32  requiredPixelCount;
+
+  gint32  tarX;
+  gint32  tarY;
+
+  tarX = targetX;
+  tarY = targetY;
+  
+  /* init pixel fetchers */
+  pftRef = gimp_pixel_fetcher_new (context->refDrawable, FALSE /* shadow */);
+  pftTarget = gimp_pixel_fetcher_new (context->targetDrawable, FALSE /* shadow */);
+  gimp_pixel_fetcher_set_edge_mode (pftRef, GIMP_PIXEL_FETCHER_EDGE_NONE);
+  gimp_pixel_fetcher_set_edge_mode (pftTarget, GIMP_PIXEL_FETCHER_EDGE_NONE);
+
+
+  /* copy small areas near the relevant coordinates
+   * into arrays of the ColorRelation structures for further tuning processing
+   */
+  p_fetchColorArray(&refColorRelation, pftRef
+                 , context->refDrawable->width, context->refDrawable->height
+                 , context->refX, context->refY
+                 , gimp_drawable_has_alpha(context->refDrawable->drawable_id)
+                 , "REF"
+                 );
+  p_fetchColorArray(&targetColorRelation, pftTarget
+                 , context->targetDrawable->width, context->targetDrawable->height
+                 , tarX, tarY
+                 , gimp_drawable_has_alpha(context->targetDrawable->drawable_id)
+                 , "TAR"
+                 );
+
+  p_calculateColorRelationArrays(&refColorRelation, GAP_COLOR_REL_CENTER_INDEX, GAP_COLOR_REL_CENTER_INDEX);
+       
+  requiredPixelCount = GAP_COLOR_REL_REQUIRED_PIXELS;
+  tuneOffsShortList = NULL;
+  
+  for(iidy = 0; iidy < 9; iidy++)
+  {
+    idy = offsets[iidy];
+    for(iidx = 0; iidx < 9; iidx++)
+    {
+      gdouble relDiff;
+      gint32     pixelCount;
+      
+      idx = offsets[iidx];
+
+      p_calculateColorRelationArrays(&targetColorRelation, GAP_COLOR_REL_CENTER_INDEX +idx, 
GAP_COLOR_REL_CENTER_INDEX +idy);
+
+      relDiff = p_calculateColorRelationArraysDifference(&refColorRelation, &targetColorRelation, idx, idy, 
&pixelCount);
+      
+      if(gap_debug)
+      {
+        printf("p_findTuneOffsShortList: idx:%d idy:%d relDiff:%f pixelCount:%d requiredPixelCount:%d\n"
+          ,(int)idx
+          ,(int)idy
+          ,(float)relDiff
+          ,(int)pixelCount
+          ,(int)requiredPixelCount
+          );
+      }
+
+      
+      if (pixelCount >= requiredPixelCount)
+      {
+        if (tuneOffsShortList == NULL)
+        {
+          /* on empty list create the list root element */
+          tuneOffsShortList = gap_locate_newGapLocateTuneOffsElem(idx, idy, relDiff);
+        }
+        else
+        {
+          GapLocateTuneOffsElem    *tuneOffsElem;
+
+          for(tuneOffsElem = tuneOffsShortList; tuneOffsElem != NULL; tuneOffsElem = tuneOffsElem->next)
+          {
+            if(relDiff < tuneOffsElem->relDiff)
+            {
+              GapLocateTuneOffsElem    *newGapLocateTuneOffsElem;
+              
+              /* copy existing element to a new element */
+              newGapLocateTuneOffsElem = gap_locate_newGapLocateTuneOffsElem(tuneOffsElem->tuneOffsetX, 
tuneOffsElem->tuneOffsetY, tuneOffsElem->relDiff);
+              /* and insert the new element after the current element into the list */
+              newGapLocateTuneOffsElem->next = tuneOffsElem->next;
+              tuneOffsElem->next = newGapLocateTuneOffsElem;
+              
+              /* replace the current element content with the "better" relDiff value and offsets */
+              tuneOffsElem->relDiff = relDiff;
+              tuneOffsElem->tuneOffsetX = idx;
+              tuneOffsElem->tuneOffsetY = idy;
+
+              break;
+            }
+            if (tuneOffsElem->next == NULL)
+            {
+              tuneOffsElem->next = gap_locate_newGapLocateTuneOffsElem(idx, idy, relDiff);
+              break;
+            }
+          }
+        }
+        
+      }
+    }
+  }
+
+
+  gimp_pixel_fetcher_destroy (pftRef);
+  gimp_pixel_fetcher_destroy (pftTarget);
+
+  return (tuneOffsShortList);
+       
+}  /* end p_findTuneOffsShortList */    
+
+
+/* ----------------------------------------
+ * gap_locate_FindTuneOffsShortList
+ * ----------------------------------------
+ * This procedure provides a list of tuning offsets in the range of +- 2 pixels around
+ * coordinates that were typically located via
+ *  gap_locateAreaWithinRadiusWithOffset
+ * It computes color relation arrays for the areas around refCoord (in the referenceLayerId)
+ * and currCoord (target coordinate in the activeDrawableId) and compares the 
+ * color relation arrays reference versus target.
+ * This computation and comparison is done with varying tuning offests where only the the best matching
+ * offsets are recorded in the short list that is returned to the caller
+ * (for tuning purpose while aligning video frames for video stabilisation).
+ *
+ * Note that the list may contain elements marked as invalid in case there are less than 5
+ * very good matching variants.
+ * The qFactor shall eliminate weak matchers (by setting them invalid)
+ * in case there is a clear favorite matching offset available,
+ * but keep more elements (== tune attempts) in case there are more very similar matching candidates.
+ *
+ * returns a short list of potential tuning offsets
+ * Note the caller is responsible to free the returned list (by calling p_freeTuneOffsList)
+ */
+GapLocateTuneOffsElem *
+gap_locate_FindTuneOffsShortList(gint32 activeDrawableId, gint32 referenceLayerId, GapPixelCoords *refCoord, 
GapPixelCoords *currCoord, gdouble qFactor)
+{
+  GapLocateTuneOffsElem *tuneOffsShortList;
+  GapLocateTuneOffsElem *tuneOffsElem;
+  gint     idx;
+  gboolean firstIvalidElemLogged;
+
+  Context contextData;
+  Context *context;
+
+  
+  if(gap_debug)
+  {
+    printf("\ngap_locate_FindTuneOffsShortList START ref.px:%d ref.py:%d cur.px:%d cur.py:%d qFactor:%f\n"
+      ,(int)refCoord->px
+      ,(int)refCoord->py
+      ,(int)currCoord->px
+      ,(int)currCoord->py
+      ,(float)qFactor
+      );
+  }
+
+  /* partly init Context (for color relation processing) */
+  context = &contextData;
+  context->refX = refCoord->px;
+  context->refY = refCoord->py;
+  context->bestX = refCoord->px;
+  context->bestY = refCoord->py;
+  context->refDrawable = gimp_drawable_get(referenceLayerId);
+  context->targetDrawable = gimp_drawable_get(activeDrawableId);
+
+
+
+  /* get the candidates for tunining offsets as sorted list (best matchers first) */
+  tuneOffsShortList = p_findTuneOffsShortList(context, currCoord->px, currCoord->py);
+  
+  idx = 0;
+  firstIvalidElemLogged = FALSE;
+  for(tuneOffsElem = tuneOffsShortList; tuneOffsElem != NULL; tuneOffsElem = tuneOffsElem->next)
+  {
+    if (tuneOffsElem->relDiff > tuneOffsShortList->relDiff * qFactor)
+    {
+      /* mark elements with too big difference as invalid */
+      tuneOffsElem->valid = FALSE;
+    }
+    
+    if(gap_debug)
+    {
+      if ((tuneOffsElem->valid) || (firstIvalidElemLogged == FALSE))
+      {
+        printf("  %d) ref.px:%d ref.py:%d cur.px:%d cur.py:%d tuneOffsetX:%d tuneOffsetY:%d relDiff:%f 
requiredRelDiff <= :%f"
+          ,(int)idx
+          ,(int)refCoord->px
+          ,(int)refCoord->py
+          ,(int)currCoord->px
+          ,(int)currCoord->py
+          ,(int)tuneOffsElem->tuneOffsetX
+          ,(int)tuneOffsElem->tuneOffsetY
+          ,(float)tuneOffsElem->relDiff
+          ,(float)(tuneOffsShortList->relDiff * qFactor)
+          );
+        if(tuneOffsElem->valid)
+        {
+          printf (" [VALID]\n");
+        }
+        else
+        {
+          printf (" [invalid]\n");
+          firstIvalidElemLogged = TRUE;  /* do not log further invalid elements in the list */
+        }
+      }
+    }
+    
+    idx++;
+
+  }
+
+  if(context->refDrawable != NULL)
+  {
+    gimp_drawable_detach(context->refDrawable);
+  }
+  if(context->targetDrawable != NULL)
+  {
+    gimp_drawable_detach(context->targetDrawable);
+  }
+  
+  return(tuneOffsShortList);
+
+}  /* end gap_locate_FindTuneOffsShortList */
+
+/* -------------------------------------------------------
+ * gap_locatePickNearestToPredictedCoordinateFromShortlist
+ * -------------------------------------------------------
+ * IN/OUT: trkCoord  is adjusted (tuned) with the tuning offest that leads to the
+ * nearest coordinate compared to the predictedCoord.
+ */
+void 
+gap_locatePickNearestToPredictedCoordinateFromShortlist(GapPixelCoords *trkCoord, GapPixelCoords 
*predictedCoord, 
+   GapLocateTuneOffsElem *shortListP1, gint width, gint height)
+{
+  GapLocateTuneOffsElem *elemP1;
+  
+  GapPixelCoords untunedCoord;
+  untunedCoord.px = trkCoord->px;
+  untunedCoord.py = trkCoord->py;
+  gdouble   minSqDist;
+  
+  minSqDist = (width + height) * (width + height);
+
+  for(elemP1 = shortListP1; elemP1 != NULL; elemP1 = elemP1->next)
+  {
+    GapPixelCoords tunedCoord;
+    gdouble sqrDistance;
+    if (elemP1->valid != TRUE) 
+    { 
+      continue;  /* skip low quality list entries */
+    }
+    tunedCoord.px =  untunedCoord.px + elemP1->tuneOffsetX;
+    tunedCoord.py =  untunedCoord.py + elemP1->tuneOffsetY;
+    
+    sqrDistance = gap_geo_calculateSqrDist(&tunedCoord, predictedCoord);
+    if (sqrDistance < minSqDist)
+    {
+      minSqDist = sqrDistance;
+      trkCoord->px = tunedCoord.px;
+      trkCoord->py = tunedCoord.py;
+    }
+  }
+  
+  if(gap_debug)
+  {
+    printf("gap_locatePickNearestToPredicted predictedCoord: %d %d, pickedCoord: %d %d\n"
+      , (int)predictedCoord->px
+      , (int)predictedCoord->py
+      , (int)trkCoord->px
+      , (int)trkCoord->py
+      );
+  }
+
+} /* end gap_locatePickNearestToPredictedCoordinateFromShortlist */
+
+
+/* ------------------------------------
+ * gap_locate_check_strong_shortlist
+ * ------------------------------------
+ * Checks if the short list contains only one good matching element.
+ * In case there are more elements with neaerly the same matching quality (relDiff value)
+ * it is considered as weak matching.
+ * Note that the short list has to be already sorted by ascending relDiff values.
+ * therefore the best matching element is always the 1st in the list.
+ */
+gboolean
+gap_locate_check_strong_shortlist(GapLocateTuneOffsElem *shortListP1, gdouble nearlySameFactor, gdouble 
strongRelDiff)
+{
+  GapLocateTuneOffsElem *elemP1;
+  gint                   idx;
+  gboolean               isStrong;
+
+  idx = 0;
+  isStrong = FALSE;
+  for(elemP1 = shortListP1; elemP1 != NULL; elemP1 = elemP1->next)
+  {
+    if (idx == 0)
+    {
+      if (elemP1->relDiff > strongRelDiff)
+      {
+        /* the 1st element is already poor matching, stop further checks and return FALSE */ 
+        isStrong = FALSE;
+        break;
+      }
+      else 
+      {
+        isStrong = TRUE; /* assume TRUE because the 1st element is matching good enough */
+      }
+    }
+    else
+    {
+      if (elemP1->relDiff <= shortListP1->relDiff * nearlySameFactor)
+      {
+        /* the next (2nd) element has nearly same relDiff value
+         * Therfore the 1st element is considered as WEAK because it is not the only one
+         */
+        isStrong = FALSE; 
+      }
+      break;
+    }
+    idx++;
+  }
+  return (isStrong);
+  
+}  /* end gap_locate_check_strong_shortlist */
+
+/* --------------------------------------------
  * gap_locateAreaWithinRadiusWithOffset
  * --------------------------------------------
  * processing starts at reference coords + offest
@@ -483,7 +1246,6 @@ gap_locateAreaWithinRadiusWithOffset(gint32  refDrawableId
   Context contextData;
   Context *context;
   gdouble averageColorDiff;
-  gboolean isFinishedFlag;
   gdouble maxPixelCount;
   gint32  shapeDiameter;
   gint32  fullAreaPixelCount;
@@ -521,8 +1283,10 @@ gap_locateAreaWithinRadiusWithOffset(gint32  refDrawableId
   
   maxPixelCount = MAX(context->refDrawable->width, context->targetDrawable->width)
                 * MAX(context->refDrawable->height, context->targetDrawable->height);
-                
+
+  context->bestMatchingAvgColordiff = 1.0; /* worst case */               
   context->bestMatchingSumDiffValue = maxPixelCount * MAX_DIFF_VALUE_PER_PIXEL;
+  context->goodMatchingSumDiffValue = context->bestMatchingSumDiffValue;
   context->bestMatchingDistance = maxPixelCount;
   
   averageColorDiff = 1.0;
@@ -555,7 +1319,7 @@ gap_locateAreaWithinRadiusWithOffset(gint32  refDrawableId
     {
  
       p_attempt_locate_at_current_offset(context, (offsetX + refX) + dx, (offsetY + refY) + dy);
-      if (isFinishedFlag)
+      if (context->isFinishedFlag)
       { 
         break;
       }
@@ -598,16 +1362,28 @@ gap_locateAreaWithinRadiusWithOffset(gint32  refDrawableId
                                     , context->bestMatchingPixelCount
                                     );
 
+    /// tuning did not lead to better results in most cases, 
+    /// therfore disabled for now... (and for performance reasons)
+    /* try to improve the located target coordinates 
+     * by checking color relations to nearest neighbour pixels
+     * (this may shift the result by 1 or 2 pixels)
+     */
+    //// p_tuneLocatedCoordinates(context, targetX, targetY);
     
     if(gap_debug)
     {
-      printf("gap_locateAreaWithinRadiusWithOffset Result: bestX:%d bestY:%d averageColorDiff:%.5f\n"
+      printf("gap_locateAreaWithinRadiusWithOffset Result: targetX:%d targetY:%d averageColorDiff:%.5f\n"
+             "                           bestX:%d bestY:%d tuneX:%d tuneY:%d\n"
              "                           sumDiffValues:%d pixelCount:%d\n"
              "                           refX:%d refY:%d  cancelAttemptCount:%d rowsProcessedCount:%d\n"
              "                           requiredPixelCount:%d almostFullAreaPixelCount:%d\n"
+        , (int)(*targetX)
+        , (int)(*targetY)
+        , (float)averageColorDiff
         , (int)context->bestX
         , (int)context->bestY
-        , (float)averageColorDiff
+        , (int)(*targetX) - context->bestX
+        , (int)(*targetY) - context->bestY
         , (int)context->bestMatchingSumDiffValue
         , (int)context->bestMatchingPixelCount
         , (int)context->refX
@@ -680,3 +1456,202 @@ gap_locateAreaWithinRadius(gint32  refDrawableId
   return (avgColordiff);
   
 }  /* end gap_locateAreaWithinRadius */
+
+
+/* --------------------------------------------
+ * gap_locateColordiffOpaquePixels
+ * --------------------------------------------
+ * compares opaque pixels of the specified refDrawableId and targetDrawableId.
+ * Both drawables shall have same size 
+ * Note that offsets of the layers within the image are relevant for processing.
+ * (pixels are compared at corresponding coordinates which means having the same
+ * same position in the image)
+ * in case there are less pixels involved in the comparison than the specified requiredPixelCount
+ * than value 1.0 is returned (to indicate "worst case" 
+ * for situations where not enough pixels could be compared because there are too few opaque pixels
+ * at corresponding coordinates, or there is no intersection at all)
+ * 
+ * returns average color difference (0.0 upto 1.0)
+ *    where 0.0 indicates exact matching area
+ *      and 1.0 indicates all pixel have maximum color diff (when comparing full white against full black 
area)
+ */
+gdouble
+gap_locateColordiffOpaquePixels(gint32  refDrawableId
+  , gint32  targetDrawableId
+  , gint32  requiredPixelCount
+  , gint32 *comparedPixelCount
+  )
+{
+  Context contextData;
+  Context *context;
+  gdouble averageColorDiff;
+  gdouble maxPixelCount;
+
+  GimpPixelRgn refPR;
+  GimpPixelRgn targetPR;
+  gpointer  pr;
+  gint      offsetRef_x;
+  gint      offsetRef_y;
+  gint      offsetTarget_x;
+  gint      offsetTarget_y;
+  gint      deltaOrigin_x, deltaOrigin_y;
+  gint      tmpOrigin_x, tmpOrigin_y;
+  gint      commonAreaWidth, commonAreaHeight;
+  gint      rx1, ry1;    /* pixel region origin in the reference drawable */
+  gint      tx1, ty1;    /* pixel region origin in the target drawable */
+  gboolean  isIntersect;
+  
+  *comparedPixelCount = 0;
+  
+  /* init Context for full drawable area compare processing
+   * note that context is designed for the more complex locating procedures
+   * therefore most context elements are not used this time...
+   */
+  context = &contextData;
+  context->refShapeRadius = 1;         /* not relevant for full area compare */
+  context->refX = 0;                   /* not relevant for full area compare */
+  context->refY = 0;                   /* not relevant for full area compare */
+  context->bestX = 0;                  /* not relevant for full area compare */
+  context->bestY = 0;                  /* not relevant for full area compare */
+  context->cancelAttemptCount = 0;     /* not relevant for full area compare */
+  context->cancelAttemptFlag = FALSE;  /* IGNORED for full area compare */
+  context->isFinishedFlag = FALSE;     /* IGNORED for full area compare */
+  context->requiredPixelCount = requiredPixelCount;
+  context->involvedPixelCount = 0;
+  context->sumDiffValue = 0;
+  context->currentDistance = 0;            /* not relevant for full area compare */
+  context->bestMatchingPixelCount = 0;     /* not relevant for full area compare */
+  context->veryNearDistance = (2 * 2);     /* not relevant for full area compare */
+
+  context->refDrawable = gimp_drawable_get(refDrawableId);
+  context->targetDrawable = gimp_drawable_get(targetDrawableId);
+  
+  maxPixelCount = MAX(context->refDrawable->width, context->targetDrawable->width)
+                * MAX(context->refDrawable->height, context->targetDrawable->height);
+                
+  context->bestMatchingAvgColordiff = 1.0; /* worst case */               
+  context->bestMatchingSumDiffValue = maxPixelCount * MAX_DIFF_VALUE_PER_PIXEL;
+  context->goodMatchingSumDiffValue = context->bestMatchingSumDiffValue;
+  context->bestMatchingDistance = maxPixelCount;
+  
+  averageColorDiff = 1.0;
+
+
+  /* get offsets within the image */
+  gimp_drawable_offsets (refDrawableId, &offsetRef_x, &offsetRef_y);
+  gimp_drawable_offsets (targetDrawableId, &offsetTarget_x, &offsetTarget_y);
+
+  
+  /* delta origin (is target origin relative to reference origin) */
+  deltaOrigin_x = offsetTarget_x - offsetRef_x;
+  deltaOrigin_y = offsetTarget_y - offsetRef_y;
+
+  if(gap_debug)
+  {
+    printf("gap_locateColordiffOpaquePixels sizeTarget: (%d x %d) offsetTarget x:%d y:%d  sizeRef: (%d x 
%d)offsetRef x:%d y:%d\n"
+      , (int)context->targetDrawable->width
+      , (int)context->refDrawable->height
+      , (int)offsetTarget_x
+      , (int)offsetTarget_y
+      , (int)context->refDrawable->width
+      , (int)context->refDrawable->height
+      , (int)offsetRef_x
+      , (int)offsetRef_y
+      );
+  }
+
+  isIntersect =
+   gimp_rectangle_intersect(0, 0                            /* origin1 x, y */
+                          , context->refDrawable->width     /*  width1 */
+                          , context->refDrawable->height    /* height1 */
+                          , deltaOrigin_x, deltaOrigin_y
+                          , context->targetDrawable->width
+                          , context->targetDrawable->height
+                          ,&tmpOrigin_x
+                          ,&tmpOrigin_y
+                          ,&commonAreaWidth
+                          ,&commonAreaHeight
+                          );
+  if (!isIntersect)
+  {
+    /* rectangeles do not intersect, deliver "worst case" value 1.0 */
+    return (1.0);
+  }  
+
+  rx1 = deltaOrigin_x;
+  tx1 = 0;
+  if (deltaOrigin_x < 0)
+  {
+    rx1 = 0;
+    tx1 = abs(deltaOrigin_x);
+  }
+
+  ry1 = deltaOrigin_y;
+  ty1 = 0;
+  if (deltaOrigin_y < 0)
+  {
+    ry1 = 0;
+    ty1 = abs(deltaOrigin_y);
+  }
+
+  
+  gimp_pixel_rgn_init (&refPR, context->refDrawable
+                      , rx1, ry1      /* origin x, y top left corner*/
+                      , commonAreaWidth, commonAreaHeight
+                      , FALSE     /* dirty */
+                      , FALSE     /* shadow */
+                       );
+
+  gimp_pixel_rgn_init (&targetPR, context->targetDrawable
+                      , tx1, ty1      /* origin x, y top left corner*/
+                      , commonAreaWidth, commonAreaHeight
+                      , FALSE     /* dirty */
+                      , FALSE     /* shadow */
+                       );
+
+  /* compare pixel areas in tiled portions via pixel region processing loops.
+   */
+  for (pr = gimp_pixel_rgns_register (2, &refPR, &targetPR);
+       pr != NULL;
+       pr = gimp_pixel_rgns_process (pr))
+  {
+    p_compare_regions(&refPR, &targetPR, context);
+  }
+  
+  /* deliver the number of opaque pixels that actually were involved
+   * in the comparison (having opaque alpha channel at corresponding coordinates
+   * in both layers
+   */
+  *comparedPixelCount = context->involvedPixelCount;
+  
+  if (context->involvedPixelCount >= context->requiredPixelCount)
+  {
+    averageColorDiff = p_calculate_average_colordiff( context->sumDiffValue
+                                                    , context->involvedPixelCount
+                                                    );
+  }
+  
+  if(gap_debug)
+  {
+    printf("gap_locateColordiffOpaquePixels Result: averageColorDiff:%.15f\n"
+           "                           involvedPixelCount:%d (requiredPixelCount:%d)\n"
+      , (float)averageColorDiff
+      , (int)context->involvedPixelCount
+      , (int)context->requiredPixelCount
+      );
+  }
+
+
+
+  if(context->refDrawable != NULL)
+  {
+    gimp_drawable_detach(context->refDrawable);
+  }
+  if(context->targetDrawable != NULL)
+  {
+    gimp_drawable_detach(context->targetDrawable);
+  }
+
+  return (averageColorDiff);
+
+}  /* end gap_locateColordiffOpaquePixels */
diff --git a/gap/gap_locate2.h b/gap/gap_locate2.h
index b1dceb8..312f37d 100644
--- a/gap/gap_locate2.h
+++ b/gap/gap_locate2.h
@@ -1,4 +1,4 @@
-/* gap_locate.h
+/* gap_locate2.h
  *    alternative implementation for locating corresponding pattern in another layer.
  *    by hof (Wolfgang Hofer)
  *  2011/12/03
@@ -38,6 +38,84 @@
 #include "libgimp/gimp.h"
 
 
+/* GIMP-GAP includes */
+#include "gap_geo.h"
+
+
+typedef struct GapLocateTuneOffsElem {
+  gint32   tuneOffsetX;
+  gint32   tuneOffsetY;
+  gdouble  relDiff;
+  gboolean valid;
+  struct   GapLocateTuneOffsElem *next;
+} GapLocateTuneOffsElem;
+
+
+/* ----------------------------------------
+ * gap_locate_newGapLocateTuneOffsElem
+ * ----------------------------------------
+ * allocates a new list elment and initializes it as valid
+ * and with the specified constructor parameters.
+ */
+GapLocateTuneOffsElem *
+gap_locate_newGapLocateTuneOffsElem(gint idx, gint idy, gdouble relDiff);
+
+/* ----------------------------------------
+ * gap_locate_freeTuneOffsList
+ * ----------------------------------------
+ * free all elements in the the specified list.
+ */
+void
+gap_locate_freeTuneOffsList(GapLocateTuneOffsElem *rootElem);
+
+/* ----------------------------------------
+ * gap_locate_FindTuneOffsShortList
+ * ----------------------------------------
+ * This procedure provides a list of tuning offsets in the range of +- 2 pixels around
+ * coordinates that were typically located via
+ *  gap_locateAreaWithinRadiusWithOffset
+ * It computes color relation arrays for the areas around refCoord (in the referenceLayerId)
+ * and currCoord (target coordinate in the activeDrawableId) and compares the 
+ * color relation arrays reference versus target.
+ * This computation and comparison is done with varying tuning offests where only the the best matching
+ * offsets are recorded in the short list (max 5 elements) that is returned to the caller
+ * (for tuning purpose while aligning video frames for video stabilisation).
+ *
+ * Note that the list may contain elements marked as invalid in case there are less than 5
+ * very good matching variants.
+ * The qFactor shall eliminate weak matchers (by setting them invalid)
+ * in case there is a clear favorite matching offset available,
+ * but keep more elements (== tune attempts) in case there are more very similar matching candidates.
+ *
+ * returns a short list of potential tuning offsets
+ * Note the caller is responsible to free the returned list (by calling p_freeTuneOffsList)
+ */
+GapLocateTuneOffsElem *
+gap_locate_FindTuneOffsShortList(gint32 activeDrawableId, gint32 referenceLayerId, GapPixelCoords *refCoord, 
GapPixelCoords *currCoord, gdouble qFactor);
+
+/* -------------------------------------------------------
+ * gap_locatePickNearestToPredictedCoordinateFromShortlist
+ * -------------------------------------------------------
+ * IN/OUT: trkCoord  is adjusted (tuned) with the tuning offest that leads to the
+ * nearest coordinate compared to the predictedCoord.
+ */
+void 
+gap_locatePickNearestToPredictedCoordinateFromShortlist(GapPixelCoords *trkCoord, GapPixelCoords 
*predictedCoord, 
+   GapLocateTuneOffsElem *shortListP1, gint width, gint height);
+
+/* ------------------------------------
+ * gap_locate_check_strong_shortlist
+ * ------------------------------------
+ * Checks if the short list contains only one good matching element.
+ * In case there are more elements with neaerly the same matching quality (relDiff value)
+ * it is considered as weak matching.
+ * Note that the short list has to be already sorted by ascending relDiff values.
+ * therefore the best matching element is always the 1st in the list.
+ */
+gboolean
+gap_locate_check_strong_shortlist(GapLocateTuneOffsElem *shortListP1, gdouble nearlySameFactor, gdouble 
strongRelDiff);
+
+
 /* ----------------------------------------
  * gap_locateAreaWithinRadius
  * ----------------------------------------
@@ -93,4 +171,30 @@ gap_locateAreaWithinRadiusWithOffset(gint32  refDrawableId
   );
 
 
+/* --------------------------------------------
+ * gap_locateColordiffOpaquePixels
+ * --------------------------------------------
+ * compares opaque pixels of the specified refDrawableId and targetDrawableId.
+ * Both drawables shall have same size 
+ * Note that offsets of the layers within the image are relevant for processing.
+ * (pixels are compared at corresponding coordinates which means having the same
+ * same position in the image)
+ * in case there are less pixels involved in the comarison than the specified requiredPixelCount
+ * than value 1.0 is returned (to indicate "worst case" 
+ * for situations where not enough pixels could be compared because there are too few opaque pixels
+ * at corresponding coordinates, or there is no intersection at all)
+ * 
+ * returns average color difference (0.0 upto 1.0)
+ *    where 0.0 indicates exact matching area
+ *      and 1.0 indicates all pixel have maximum color diff (when comparing full white agains full black 
area)
+ */
+gdouble
+gap_locateColordiffOpaquePixels(gint32  refDrawableId
+  , gint32  targetDrawableId
+  , gint32  requiredPixelCount
+  , gint32 *comparedPixelCount
+  );
+
+
+
 #endif
diff --git a/gap/gap_main.c b/gap/gap_main.c
index eb954ef..3e8ba37 100644
--- a/gap/gap_main.c
+++ b/gap/gap_main.c
@@ -157,6 +157,7 @@ int gap_debug = 0;
 #define PLUGIN_NAME_GAP_SHIFT                "plug_in_gap_shift"
 #define PLUGIN_NAME_GAP_REVERSE              "plug_in_gap_reverse"
 #define PLUGIN_NAME_GAP_RENUMBER             "plug_in_gap_renumber"
+#define PLUGIN_NAME_GAP_RENAME               "plug_in_gap_rename"
 #define PLUGIN_NAME_GAP_MODIFY               "plug_in_gap_modify"
 #define PLUGIN_NAME_GAP_VIDEO_EDIT_COPY      "plug_in_gap_video_edit_copy"
 #define PLUGIN_NAME_GAP_VIDEO_EDIT_PASTE     "plug_in_gap_video_edit_paste"
@@ -427,6 +428,16 @@ GimpPlugInInfo PLUG_IN_INFO =
   };
   static int nargs_renumber = G_N_ELEMENTS (args_renumber);
 
+  static GimpParamDef args_rename[] =
+  {
+    {GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive"},
+    {GIMP_PDB_IMAGE, "image", "Input image (current one of the video frames)"},
+    {GIMP_PDB_DRAWABLE, "drawable", "Input drawable (unused)"},
+    {GIMP_PDB_STRING, "newname", "new filename part (without directory part and without number, extension 
parts)"},
+  };
+  static int nargs_rename = G_N_ELEMENTS (args_rename);
+
+
   static GimpParamDef args_modify[] =
   {
     {GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive"},
@@ -504,6 +515,11 @@ GimpPlugInInfo PLUG_IN_INFO =
                                     ", 67:merge down expand"
                                     ", 68:merge down clip to image"
                                     ", 69:merge down clip to bg"
+                                    ", 70:resize to selection (use selection the current frame image)"
+                                    ", 71:resize to selection (use individual selction as it is in each 
handled frame) "
+                                    ", 72:set layer as active"
+                                    ", 73:set layermask as active"
+                                    ", 74:record layer offests to XML file"
                                     },
     {GIMP_PDB_INT32, "select_mode", "Mode how to identify a layer: 0-3 by layername 0=equal, 1=prefix, 
2=suffix, 3=contains, 4=layerstack_numberslist, 5=inv_layerstack, 6=all_visible"},
     {GIMP_PDB_INT32, "select_case", "0: ignore case 1: select_string is case sensitive"},
@@ -835,6 +851,19 @@ query ()
                          nargs_renumber, nreturn_std,
                          args_renumber, return_std);
 
+
+  gimp_install_procedure(PLUGIN_NAME_GAP_RENAME,
+                         "This plugin renames all frames (discfiles) to the specified new filename part)",
+                         "",
+                         "Wolfgang Hofer (hof gimp org)",
+                         "Wolfgang Hofer",
+                         GAP_VERSION_WITH_DATE,
+                         N_("Frames Rename..."),
+                         "RGB*, INDEXED*, GRAY*",
+                         GIMP_PLUGIN,
+                         nargs_rename, nreturn_std,
+                         args_rename, return_std);
+
   gimp_install_procedure(PLUGIN_NAME_GAP_MODIFY,
                          "This plugin performs a modifying action on each selected layer in each selected 
framerange",
                          "",
@@ -960,6 +989,7 @@ query ()
      gimp_plugin_menu_register (PLUGIN_NAME_GAP_SHIFT, menupath_image_video);
      gimp_plugin_menu_register (PLUGIN_NAME_GAP_REVERSE, menupath_image_video);
      gimp_plugin_menu_register (PLUGIN_NAME_GAP_RENUMBER, menupath_image_video);
+     gimp_plugin_menu_register (PLUGIN_NAME_GAP_RENAME, menupath_image_video);
      gimp_plugin_menu_register (PLUGIN_NAME_GAP_MODIFY, menupath_image_video);
   }
 }       /* end query */
@@ -1655,6 +1685,39 @@ run (const gchar *name
 
       }
   }
+  else if (strcmp (name, PLUGIN_NAME_GAP_RENAME) == 0)
+  {
+      gint len_newFrameName;
+      char newFrameName[256];
+
+      if(gap_debug)
+      {
+        printf("START %s\n", name);
+      }
+      newFrameName[0] = '\0';
+      len_newFrameName = sizeof(newFrameName);
+      if (run_mode == GIMP_RUN_NONINTERACTIVE)
+      {
+        if (n_params != nargs_rename)
+        {
+          status = GIMP_PDB_CALLING_ERROR;
+        }
+        else
+        {
+          strncpy(newFrameName, param[3].data.d_string, sizeof(newFrameName) -1);
+          newFrameName[len_newFrameName -1] = '\0';
+        }
+      }
+
+      if (status == GIMP_PDB_SUCCESS)
+      {
+        if(gap_debug)
+        {
+          printf("calling: gap_base_rename\n");
+        }
+        l_rc_image = gap_base_rename(run_mode, image_id, &newFrameName[0], len_newFrameName);
+      }
+  }
   else if (strcmp (name, PLUGIN_NAME_GAP_VIDEO_EDIT_COPY) == 0)
   {
       *nreturn_vals = nreturn_nothing +1;
diff --git a/gap/gap_mod_layer.c b/gap/gap_mod_layer.c
index 4ef0fab..8cbf165 100644
--- a/gap/gap_mod_layer.c
+++ b/gap/gap_mod_layer.c
@@ -378,6 +378,154 @@ gap_mod_alloc_layli(gint32 image_id, gint32 *l_sel_cnt, gint *nlayers,
 }               /* end gap_mod_alloc_layli */
 
 
+/* -----------------------------------------
+ * p_write_xml_header
+ * -----------------------------------------
+ * write header for a MovePath XML file
+ */
+static void
+p_write_xml_header(FILE *l_fp, gint imageWidth, gint imageHeight, gint layerWidth, gint layerHeight, gint 
numFrames)
+{
+  fprintf(l_fp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+  
+  fprintf(l_fp, "<gimp_gap_move_path_parameters version=\"2\" >\n");
+
+  fprintf(l_fp, "  <frame_description ");
+  fprintf(l_fp, "width=\"%d\" ", (int) imageWidth);         
+  fprintf(l_fp, "height=\"%d\" ", (int) imageHeight);         
+  fprintf(l_fp, "range_from=\"%d\" ", (int) 1);         
+  fprintf(l_fp, "range_to=\"%d\" ", (int) numFrames);         
+  fprintf(l_fp, "total_frames=\"%d\" ", (int) numFrames);         
+  fprintf(l_fp, " />\n");
+          
+          
+  fprintf(l_fp, "  <tween tween_steps=\"0\" />\n");
+  fprintf(l_fp, "  <trace tracelayer_enable=\"FALSE\" />\n");
+  fprintf(l_fp, "  <moving_object src_layer_id=\"0\" src_layerstack=\"0\" width=\"%d\" height=\"%d\"\n"
+          , (int)layerWidth
+          , (int)layerHeight
+          );
+          
+  fprintf(l_fp, "    src_handle=\"GAP_HANDLE_LEFT_TOP\"");
+  fprintf(l_fp, " handle_dx=\"0\" handle_dy=\"0\"\n"); 
+          
+  fprintf(l_fp, "    src_stepmode=\"GAP_STEP_FRAME_ONCE\" step_speed_factor=\"1.00000\"\n");
+  fprintf(l_fp, "    src_selmode=\"GAP_MOV_SEL_IGNORE\"\n");
+  fprintf(l_fp, "    src_paintmode=\"GIMP_NORMAL_MODE\"\n");
+  fprintf(l_fp, "    dst_layerstack=\"0\" src_force_visible=\"TRUE\" clip_to_img=\"FALSE\" 
src_apply_bluebox=\"FALSE\"\n");
+  fprintf(l_fp, "    >\n");
+  fprintf(l_fp, "  </moving_object>\n");
+  fprintf(l_fp, "\n");
+
+  fprintf(l_fp, "  <controlpoints current_point=\"0\" number_of_points=\"%d\"  >\n"
+          , (int)numFrames
+          );
+
+}  /* end p_write_xml_header */
+
+
+/* -----------------------------------------
+ * p_append_xml_footer
+ * -----------------------------------------
+ */
+static void
+p_append_xml_footer(gchar *filename)
+{
+  FILE *l_fp;
+  
+  if(filename == NULL)
+  {
+    return;
+  }
+  if (g_file_test (filename, G_FILE_TEST_EXISTS))
+  {
+    /* append xml footer */
+    l_fp = g_fopen(filename, "ab");
+    if(l_fp != NULL)
+    {
+      fprintf(l_fp, "  </controlpoints>\n");
+      fprintf(l_fp, "</gimp_gap_move_path_parameters>");
+      fclose(l_fp);
+    }
+  }
+}  /* end p_append_xml_footer */
+
+
+/* --------------------------------
+ * p_record_layer_offsets
+ * --------------------------------
+ * Record the layeroffests in XML controlpoint syntax
+ * (usable with the GAP MovePath feature)
+ *
+ */
+static void
+p_record_layer_offsets(gint numFrames, long frameNr, gint32 imageId, gint32 layerId, char *filename)
+{
+  FILE *l_fp;
+  gint src_offset_x;
+  gint src_offset_y;
+  gchar *logline;
+  gboolean logToStdout;
+
+ 
+  gimp_drawable_offsets(layerId, &src_offset_x, &src_offset_y);
+  
+  /* the logline to be recorded shall look like this example:
+   *   <controlpoint px="     0" py="     0" keyframe_abs="000001" /> 
+   */
+  
+  logline = g_strdup_printf("    <controlpoint px=\"%6d\" py=\"%6d\" keyframe_abs=\"%06d\" />"
+       , (int)src_offset_x
+       , (int)src_offset_y
+       , (int)frameNr
+       );
+
+  logToStdout = TRUE;
+  if (filename != NULL)
+  {
+    if (*filename != '\0')
+    {
+      logToStdout = FALSE;
+      
+      
+      if (!g_file_test (filename, G_FILE_TEST_EXISTS))
+      {
+        l_fp = g_fopen(filename, "w+");
+        p_write_xml_header(l_fp
+                          , gimp_image_width(imageId)
+                          , gimp_image_height(imageId)
+                          , gimp_drawable_width(layerId)
+                          , gimp_drawable_height(layerId)
+                          , numFrames
+                          );
+      }
+      else
+      {
+        /* append controlpoints (use binary mode to prevent additional line feeds in Windows environment)  */
+        l_fp = g_fopen(filename, "ab");
+      }
+      
+      if(l_fp != NULL)
+      {
+         fprintf(l_fp, "%s\n", logline);
+         fclose(l_fp);
+      }
+    }
+  }
+  
+  if(logToStdout == TRUE)
+  {
+    printf("%s\n", logline);
+  }
+  
+  g_free(logline);
+
+
+}  /* end p_record_layer_offsets */
+
+
+
+
 /* ============================================================================
  * p_raise_layer
  *   raise layer (check if possible before)
@@ -466,7 +614,7 @@ p_selection_combine(gint32 image_id
                             , 0
                             , 0
                             );
-  gimp_drawable_delete(l_new_channel_id);
+  gimp_item_delete(l_new_channel_id);
 
 }  /* end p_selection_combine */
 
@@ -946,6 +1094,30 @@ p_apply_action2(gint32 image_id,
         case GAP_MOD_ACM_SET_UNLINKED:
           gimp_item_set_linked(l_layer_id, FALSE);
           break;
+        case GAP_MOD_ACM_SET_ACTIVE_LAYER:
+          gimp_image_set_active_layer(image_id, l_layer_id);
+          if(gimp_layer_get_mask(l_layer_id) >= 0)
+          {
+            /* the layer has a layer mask, therefore set edit_mask
+             * FALSE (edit the layer itself is desired)
+             */
+            gimp_layer_set_edit_mask(l_layer_id, FALSE);
+          }
+          break;
+        case GAP_MOD_ACM_SET_ACTIVE_LAYERMASK:
+          gimp_image_set_active_layer(image_id, l_layer_id);
+          if(gimp_layer_get_mask(l_layer_id) >= 0)
+          {
+            /* the layer has a layer mask, therefore set edit_mask
+             * TRUE (edit the layermask is desired)
+             */
+            gimp_layer_set_edit_mask(l_layer_id, TRUE);
+          }
+          break;
+        case GAP_MOD_ACM_RECORD_LAYER_OFFSETS:
+          /* note that new_layername is used as filename for the XML output */
+          p_record_layer_offsets(1 + (MAX(from, to) - MIN(from, to)) /* numFrames*/, curr, image_id, 
l_layer_id, new_layername);
+          break;
         case GAP_MOD_ACM_RAISE:
           p_raise_layer(image_id, l_layer_id, layli_ptr, nlayers, FALSE);
           break;
@@ -1012,7 +1184,7 @@ p_apply_action2(gint32 image_id,
         case GAP_MOD_ACM_SEL_ALPHA:
           if(gimp_drawable_has_alpha(l_layer_id))
           {
-            gimp_selection_layer_alpha(l_layer_id);
+            gimp_image_select_item(image_id, GIMP_CHANNEL_OP_REPLACE, l_layer_id);
           }
           else
           {
@@ -1357,6 +1529,12 @@ p_apply_action2(gint32 image_id,
         case GAP_MOD_ACM_RESIZE_TO_IMG:
           gimp_layer_resize_to_image_size (l_layer_id);
           break;
+        case GAP_MOD_ACM_RESIZE_TO_SELECTION_1:
+          gap_layer_resize_to_selection(master_image_id, l_layer_id);
+          break;
+        case GAP_MOD_ACM_RESIZE_TO_SELECTION_N:
+          gap_layer_resize_to_selection(image_id, l_layer_id);
+          break;
         default:
           break;
       }
@@ -1796,6 +1974,7 @@ gap_mod_frames_modify(GapAnimInfo *ainfo_ptr,
         (int)action_mode, (int)sel_mode, (int)sel_case, (int)sel_invert, sel_pattern);
   }
 
+  groupFilterHandlingMode = 0;
   l_operate_on_layermask = FALSE;
   l_percentage = 0.0;
   if(ainfo_ptr->run_mode == GIMP_RUN_INTERACTIVE)
@@ -2245,6 +2424,14 @@ modify_advance_to_next_frame:
        gimp_image_remove_channel(ainfo_ptr->image_id, master_channel_id);
     }
   }
+  
+  if (action_mode == GAP_MOD_ACM_RECORD_LAYER_OFFSETS)
+  {
+     /* Write XML closing tags when recording to XML file
+      * note that new_layername holds the XML filename in this action_mode 
+      */
+     p_append_xml_footer(new_layername);
+  }
 
   if(gap_debug)
   {
diff --git a/gap/gap_mod_layer.h b/gap/gap_mod_layer.h
index 0fc44ed..c5821a3 100644
--- a/gap/gap_mod_layer.h
+++ b/gap/gap_mod_layer.h
@@ -122,6 +122,15 @@
 #define  GAP_MOD_ACM_MERGE_DOWN_IMG                68
 #define  GAP_MOD_ACM_MERGE_DOWN_BG                 69
 
+
+#define  GAP_MOD_ACM_RESIZE_TO_SELECTION_1         70
+#define  GAP_MOD_ACM_RESIZE_TO_SELECTION_N         71
+#define  GAP_MOD_ACM_SET_ACTIVE_LAYER              72
+#define  GAP_MOD_ACM_SET_ACTIVE_LAYERMASK          73
+#define  GAP_MOD_ACM_RECORD_LAYER_OFFSETS          74
+
+
+
 typedef struct
 {
   gint32 layer_id;
diff --git a/gap/gap_mod_layer_dialog.c b/gap/gap_mod_layer_dialog.c
index 896f3f2..7bab761 100644
--- a/gap/gap_mod_layer_dialog.c
+++ b/gap/gap_mod_layer_dialog.c
@@ -410,6 +410,10 @@ p_upd_sensitivity(GapModFramesGlobalParams *gmop)
     case GAP_MOD_ACM_SEL_INVERT:
       l_sensitive_frame = FALSE;
       break;
+    case GAP_MOD_ACM_RECORD_LAYER_OFFSETS:
+      l_label_name = _("XML Filename");
+      l_sensitive = TRUE;
+      break;
     default:
       break;
   }
@@ -581,6 +585,26 @@ p_make_layer_attrinutes_submenu(GtkWidget *master_menu, GapModFramesGlobalParams
                        ,gmop
                        );
 
+
+  p_make_func_menu_item(_("Set layer active")
+                       ,_("set all selected layers unlinked")
+                       ,GAP_MOD_ACM_SET_ACTIVE_LAYER
+                       ,sub_menu
+                       ,gmop
+                       );
+  p_make_func_menu_item(_("Set layermask active")
+                       ,_("set all selected layers unlinked")
+                       ,GAP_MOD_ACM_SET_ACTIVE_LAYERMASK
+                       ,sub_menu
+                       ,gmop
+                       );
+  p_make_func_menu_item(_("Record layer offsets (to xml file)")
+                       ,_("set all selected layers unlinked")
+                       ,GAP_MOD_ACM_RECORD_LAYER_OFFSETS
+                       ,sub_menu
+                       ,gmop
+                       );
+
 }  /* end p_make_layer_attrinutes_submenu */
 
 
@@ -1094,6 +1118,22 @@ p_make_toplevel_menu_items(GtkWidget *master_menu, GapModFramesGlobalParams *gmo
                       ,gmop
                       );
 
+
+  p_make_func_menu_item(_("Resize layer(s) to selection (active frame)")
+                       ,_("Resize selected layer(s) to selection bounds of the active frame")
+                      ,GAP_MOD_ACM_RESIZE_TO_SELECTION_1
+                      ,master_menu
+                      ,gmop
+                      );
+
+  p_make_func_menu_item(_("Resize layer(s) to selection (individual per frame)")
+                       ,_("Resize selected layer(s) to selection bounds using individual selection per 
frame")
+                      ,GAP_MOD_ACM_RESIZE_TO_SELECTION_N
+                      ,master_menu
+                      ,gmop
+                      );
+
+
   p_make_func_menu_item(_("Add alpha channel")
                        ,NULL
                        ,GAP_MOD_ACM_ADD_ALPHA
diff --git a/gap/gap_mov_dialog.c b/gap/gap_mov_dialog.c
index e43844f..ad935a3 100644
--- a/gap/gap_mov_dialog.c
+++ b/gap/gap_mov_dialog.c
@@ -26,6 +26,7 @@
  */
 
 /* revision history:
+ * gimp    2.8.20;  2017/02/21  hof: support handle offsets
  * gimp    2.8.14;  2015/08/24  hof: support merge down postprocessing
  * gimp    2.8.10;  2014/05/07  hof: support unlimited number of controlpoints.
  * gimp    2.1.0b;  2004/11/04  hof: replaced deprecated option_menu by combo box
@@ -118,6 +119,7 @@
 #include "gap_libgapbase.h"
 #include "gap_layer_copy.h"
 #include "gap_lib.h"
+#include "gap_base.h"
 #include "gap_image.h"
 #include "gap_mov_exec.h"
 #include "gap_mov_xml_par.h"
@@ -346,6 +348,15 @@ typedef struct
   GtkWidget     *tracemerge_mode_combo;
   GtkWidget     *merge_target_combo;
 
+  GtkAdjustment *handleDx_adj;
+  GtkAdjustment *handleDy_adj;
+  GtkAdjustment *current_point_adj;
+  gint           current_point;
+  guint          current_point_spinbutton_bevent_state;
+  
+  gboolean       haveOverwritablePointfilename;
+  GtkWidget     *point_filename_frame;
+
 } t_mov_gui_stuff;
 
 
@@ -360,7 +371,9 @@ static void        p_gap_mov_dlg_move_dialog(t_mov_gui_stuff *mgp
                        , gint32 nframes
                        );
 static void        p_free_mgp_resources(t_mov_gui_stuff *mgp);
+static void        p_update_pointfilename_text(t_mov_gui_stuff *mgp);
 static void        p_update_point_index_text (t_mov_gui_stuff *mgp);
+static void        p_update_current_point_adj(t_mov_gui_stuff *mgp);
 static void        p_set_sensitivity_by_adjustment(GtkAdjustment *adj, gboolean sensitive);
 static void        p_accel_widget_sensitivity(t_mov_gui_stuff *mgp);
 static void        p_points_from_tab         (t_mov_gui_stuff *mgp);
@@ -369,10 +382,12 @@ static void        p_point_refresh           (t_mov_gui_stuff *mgp);
 static void        p_pick_nearest_point      (gint px, gint py);
 static void        p_reset_points            ();
 static void        p_clear_one_point         (gint idx);
-static void        p_mix_one_point(gint idx, gint ref1, gint ref2, gdouble mix_factor);
+static void        p_clear_one_point_protect_keyframe(gint idx, gboolean keyframeProtect);
+static void        p_mix_one_point(gint idx, gint ref1, gint ref2, gdouble mix_factor, gboolean 
keyframeProtect);
+static void        p_mix_one_point_coord(gint idx, gint ref1, gint ref2, gdouble mix_factor, gboolean 
xCoord);
 static void        p_refresh_widgets_after_load(t_mov_gui_stuff *mgp);
 static void        p_load_points             (char *filename, t_mov_gui_stuff *mgp);
-static void        p_save_points             (char *filename, t_mov_gui_stuff *mgp);
+static void        p_save_points             (char *filename, t_mov_gui_stuff *mgp, gboolean 
showOverwriteDialog);
 
 static GimpDrawable * p_get_flattened_drawable (gint32 image_id);
 static GimpDrawable * p_get_prevw_drawable (t_mov_gui_stuff *mgp);
@@ -403,6 +418,8 @@ static void        mov_path_colorbutton_update ( GimpColorButton *widget, t_mov_
 static void        mov_path_keycolorbutton_clicked ( GimpColorButton *widget, t_mov_gui_stuff *mgp);
 static void        mov_path_keycolorbutton_changed ( GimpColorButton *widget, t_mov_gui_stuff *mgp);
 static void        mov_path_keyframe_update ( GtkWidget *widget, t_mov_gui_stuff *mgp );
+static gboolean    mov_path_current_point_spinbutton_callback ( GtkWidget *widget, GdkEventButton *bevent, 
t_mov_gui_stuff *mgp);
+static void        mov_path_current_point_update ( GtkWidget *widget, t_mov_gui_stuff *mgp );
 static void        mov_path_x_adjustment_update ( GtkWidget *widget, gpointer data );
 static void        mov_path_y_adjustment_update ( GtkWidget *widget, gpointer data );
 static void        mov_path_tfactor_adjustment_update( GtkWidget *widget, gdouble *val);
@@ -444,10 +461,17 @@ static void     mov_pclr_callback        (GtkWidget *widget,gpointer data);
 static void     mov_pclr_all_callback    (GtkWidget *widget,GdkEventButton *bevent,gpointer data);
 static void     mov_prot_follow_callback (GtkWidget *widget,GdkEventButton *bevent,gpointer data);
 static void     mov_pload_callback       (GtkWidget *widget,gpointer data);
-static void     mov_psave_callback       (GtkWidget *widget,gpointer data);
+static void     mov_psave_callback       (GtkWidget *widget,GdkEventButton *bevent,gpointer data);
 static void     p_points_load_from_file  (GtkWidget *widget,t_mov_gui_stuff *mgp);
 static void     p_points_save_to_file    (GtkWidget *widget,t_mov_gui_stuff *mgp);
+static void     p_points_save_to_known_filename(t_mov_gui_stuff *mgp, gboolean showOverwriteDialog);
 
+static void     mov_x_button_callback    (GtkWidget *widget,GdkEventButton *bevent,gpointer data);
+static void     mov_y_button_callback    (GtkWidget *widget,GdkEventButton *bevent,gpointer data);
+static void     p_xy_coordinate_button_cb (GtkWidget *widget,
+                      GdkEventButton *bevent,
+                      t_mov_gui_stuff  *mgp,
+                      gboolean xCoord);
 static gboolean mov_check_valid_src_layer(t_mov_gui_stuff   *mgp);
 static void     mov_help_callback        (GtkWidget *widget, t_mov_gui_stuff *mgp);
 static void     mov_close_callback       (GtkWidget *widget, t_mov_gui_stuff *mgp);
@@ -671,6 +695,9 @@ gap_mov_dlg_edit_movepath_dialog (gint32 frame_image_id, gint32 drawable_id
     pvals->tween_opacity_initial = 80.0;
     pvals->tween_opacity_desc = 80.0;
 
+    pvals->handleDx = 0.0;
+    pvals->handleDy = 0.0;
+
     p_reset_points();
   }
 
@@ -785,6 +812,12 @@ p_gap_mov_dlg_move_dialog(t_mov_gui_stuff *mgp
   mgp->tracemerge_mode_combo = NULL;
   mgp->merge_target_combo = NULL;
 
+  mgp->handleDx_adj = NULL;
+  mgp->handleDy_adj = NULL;
+  mgp->current_point_adj = NULL;
+  mgp->current_point_spinbutton_bevent_state = 0;
+  mgp->current_point = 0;
+
   pvals = mov_ptr->val_ptr;
 
 
@@ -855,6 +888,8 @@ p_gap_mov_dlg_move_dialog(t_mov_gui_stuff *mgp
   if(mgp->isRecordOnlyMode != TRUE)
   {
     pvals->src_handle = GAP_HANDLE_LEFT_TOP;
+    pvals->handleDx = 0.0;
+    pvals->handleDy = 0.0;
     pvals->src_selmode = GAP_MOV_SEL_IGNORE;
     pvals->src_paintmode = GIMP_NORMAL_MODE;
     pvals->src_force_visible  = 1;
@@ -880,11 +915,13 @@ p_gap_mov_dlg_move_dialog(t_mov_gui_stuff *mgp
   pvals->dst_range_end   = l_last;
   pvals->dst_layerstack = 0;   /* 0 ... insert layer on top of stack */
 
+  mgp->haveOverwritablePointfilename = FALSE;   /* the 1st Save button click displays the fileselector */
   mgp->filesel = NULL;   /* fileselector is not open */
   mgp->ainfo_ptr            = mov_ptr->dst_ainfo_ptr;
   mgp->preview_frame_nr     = l_curr;
   mgp->old_preview_frame_nr = mgp->preview_frame_nr;
   mgp->point_index_frame = NULL;
+  mgp->point_filename_frame = NULL;
 
   p_points_from_tab(mgp);
   p_update_point_index_text(mgp);
@@ -2320,7 +2357,7 @@ mov_pclr_callback (GtkWidget *widget,
   t_mov_gui_stuff *mgp = data;
 
   if(gap_debug) printf("mov_pclr_callback\n");
-  p_clear_one_point(pvals->point_idx);         /* clear the current point */
+  p_clear_one_point_protect_keyframe(pvals->point_idx, TRUE);         /* clear the current point but keep 
keyframe information */
   p_point_refresh(mgp);
   mov_set_instant_apply_request(mgp);
 }
@@ -2345,11 +2382,19 @@ mov_pclr_all_callback (GtkWidget *widget,
   gint l_idx;
   gint l_ref_idx1;
   gint l_ref_idx2;
+  gboolean keyframeProtect;
   t_mov_gui_stuff *mgp = data;
   gdouble          mix_factor;
 
   if(gap_debug) printf("mov_pclr_all_callback\n");
 
+  keyframeProtect = TRUE;
+  if(bevent->state & GDK_MOD1_MASK)  /* ALT */
+  {
+    keyframeProtect = FALSE;
+  }
+
+
   if(bevent->state & GDK_SHIFT_MASK)
   {
     for(l_idx = 1; l_idx <= pvals->point_idx_max; l_idx++)
@@ -2358,7 +2403,7 @@ mov_pclr_all_callback (GtkWidget *widget,
       l_ref_idx1 = 0;
       l_ref_idx2 = 0;
 
-      p_mix_one_point(l_idx, l_ref_idx1, l_ref_idx2, mix_factor);
+      p_mix_one_point(l_idx, l_ref_idx1, l_ref_idx2, mix_factor, keyframeProtect);
     }
   }
   else
@@ -2371,14 +2416,14 @@ mov_pclr_all_callback (GtkWidget *widget,
         l_ref_idx1 = 0;
         l_ref_idx2 = pvals->point_idx_max;
 
-        p_mix_one_point(l_idx, l_ref_idx1, l_ref_idx2, mix_factor);
+        p_mix_one_point(l_idx, l_ref_idx1, l_ref_idx2, mix_factor, keyframeProtect);
       }
     }
     else
     {
       for(l_idx = 0; l_idx <= pvals->point_idx_max; l_idx++)
       {
-        p_clear_one_point(l_idx);
+        p_clear_one_point_protect_keyframe(l_idx, keyframeProtect);
       }
     }
   }
@@ -2484,11 +2529,31 @@ mov_pload_callback (GtkWidget *widget,
 
 
 static void
-mov_psave_callback (GtkWidget *widget,
-                      gpointer   data)
+mov_psave_callback (GtkWidget *widget
+                   , GdkEventButton *bevent
+                   , gpointer   data)
 {
   GtkWidget *filesel;
+  gboolean   showSaveDialog;
   t_mov_gui_stuff *mgp = data;
+  
+  showSaveDialog = TRUE;
+  if((bevent->state & GDK_CONTROL_MASK)
+  || (bevent->state & GDK_SHIFT_MASK))
+  {
+    showSaveDialog = TRUE;
+  }
+  else
+  {
+    if (mgp->haveOverwritablePointfilename == TRUE)
+    {
+      /* Save overwrite without fileselection dialog
+       * depends on previous successful load or save in this session 
+       */
+      showSaveDialog = FALSE;
+    }
+  }
+  
 
   if(mgp->filesel != NULL)
   {
@@ -2496,6 +2561,13 @@ mov_psave_callback (GtkWidget *widget,
     return;  /* filesel is already open */
   }
 
+  if(showSaveDialog != TRUE)
+  {
+    p_points_save_to_known_filename(mgp, FALSE /* Do NOT showOverwriteDialog */  );
+    return;
+  }
+
+
   filesel = gtk_file_selection_new ( _("Save Path Points to File"));
   mgp->filesel = filesel;
 
@@ -2523,6 +2595,105 @@ mov_psave_callback (GtkWidget *widget,
                     mgp);
 }
 
+
+/* ------------------------
+ * mov_x_button_callback
+ * ------------------------
+ */
+static void
+mov_x_button_callback (GtkWidget *widget,
+                      GdkEventButton *bevent,
+                      gpointer   data)
+{
+  t_mov_gui_stuff *mgp = data;
+  gboolean         xCoord = TRUE; /* operate on the X coordinate */
+
+  if(gap_debug) printf("mov_x_button_callback\n");
+
+  p_xy_coordinate_button_cb(widget, bevent, mgp, xCoord);
+
+}
+
+/* ------------------------
+ * mov_y_button_callback
+ * ------------------------
+ */
+static void
+mov_y_button_callback (GtkWidget *widget,
+                      GdkEventButton *bevent,
+                      gpointer   data)
+{
+  t_mov_gui_stuff *mgp = data;
+  gboolean         xCoord = FALSE; /* operate on the Y coordinate */
+
+  if(gap_debug) printf("mov_y_button_callback\n");
+
+  p_xy_coordinate_button_cb(widget, bevent, mgp, xCoord);
+
+}
+
+
+/* -------------------------
+ * p_xy_coordinate_button_cb
+ * -------------------------
+ * Copy or mix x (or y) coordinate from previous or next controlpoint.
+ * The operation Depends on Modifier Key that was hold down while the X or Y Button was pressed.
+ * SHIFT: Copy from next
+ * CTRL:  Mix previos next
+ * <none>: copy from previous
+ */
+static void
+p_xy_coordinate_button_cb (GtkWidget *widget,
+                      GdkEventButton *bevent,
+                      t_mov_gui_stuff  *mgp,
+                      gboolean xCoord)
+{
+  gint l_idx;
+  gint l_ref_idx1;
+  gint l_ref_idx2;
+  gdouble          mix_factor;
+  if (mgp == NULL)
+  {
+    return;
+  }
+
+
+  l_idx = pvals->point_idx; /* index of current point */
+  l_ref_idx1 = l_idx;
+  l_ref_idx2 = l_idx;
+  mix_factor = 0.0;
+  if(bevent->state & GDK_SHIFT_MASK)
+  {
+    /* copy coordinate from next point */
+    l_ref_idx1 = l_idx + 1;
+    l_ref_idx2 = l_idx + 1;
+  }
+  else
+  {
+    if(bevent->state & GDK_CONTROL_MASK)
+    {
+      /* mix x coord from previous and next point */
+      mix_factor = 0.5;
+      l_ref_idx1 = l_idx - 1;
+      l_ref_idx2 = l_idx + 1;
+    }
+    else
+    {
+      /* copy coordinate from previos point */
+      l_ref_idx1 = l_idx - 1;
+      l_ref_idx2 = l_idx - 1;
+
+    }
+  }
+
+  p_mix_one_point_coord(l_idx, l_ref_idx1, l_ref_idx2, mix_factor, xCoord);
+
+  p_point_refresh(mgp);
+  mov_set_instant_apply_request(mgp);
+
+} /* end p_xy_coordinate_button_cb */
+
+
 /* --------------------------------
  * p_refresh_widgets_after_load
  * --------------------------------
@@ -2589,6 +2760,16 @@ p_refresh_widgets_after_load(t_mov_gui_stuff *mgp)
   }
 
 
+  if(mgp->handleDx_adj != NULL)
+  {
+    gtk_adjustment_set_value(mgp->handleDx_adj,  pvals->handleDx);
+  }
+  if(mgp->handleDy_adj != NULL)
+  {
+    gtk_adjustment_set_value(mgp->handleDy_adj,  pvals->handleDy);
+  }
+
+
   if(mgp->dst_range_start_adj != NULL)
   {
     gtk_adjustment_set_value(mgp->dst_range_start_adj,  pvals->dst_range_start);
@@ -2711,7 +2892,7 @@ p_points_load_from_file (GtkWidget *widget,
 /* ---------------------------------
  * p_points_save_to_file
  * ---------------------------------
- *
+ * Callback procedure for filesel "Save" dialog, OK Button
  */
 static void
 p_points_save_to_file (GtkWidget *widget,
@@ -2740,15 +2921,23 @@ p_points_save_to_file (GtkWidget *widget,
   gtk_widget_destroy(GTK_WIDGET(mgp->filesel));
   mgp->filesel = NULL;
 
+  p_points_save_to_known_filename(mgp, TRUE /* showOverwriteDialog */  );
+
+}  /* end p_points_save_to_file */
+
+
+static void
+p_points_save_to_known_filename(t_mov_gui_stuff *mgp, gboolean showOverwriteDialog)
+{
   p_points_to_tab(mgp);
-  p_save_points(mgp->pointfile_name, mgp);
+  p_save_points(mgp->pointfile_name, mgp, showOverwriteDialog);
 
   /* quit if MovePath Mainwindow was closed */
   if(mgp->shell == NULL) { gtk_main_quit(); return; }
 
   p_point_refresh(mgp);
 
-}  /* end p_points_save_to_file */
+}
 
 
 static void
@@ -3530,6 +3719,39 @@ p_points_to_tab(t_mov_gui_stuff *mgp)
   }
 }
 
+static void
+p_update_pointfilename_text(t_mov_gui_stuff *mgp)
+{
+  char *lblTxt;
+  
+  if(mgp == NULL)
+  {
+    return;
+  }
+  
+  if ((mgp->pointfile_name == NULL) || (mgp->haveOverwritablePointfilename != TRUE))
+  {
+    lblTxt = g_strdup_printf(_("Edit Controlpoints"));
+  }
+  else
+  {
+    lblTxt = gap_base_shorten_filename(_("Edit Controlpoints ")  /* const char *prefix */
+                            ,mgp->pointfile_name         /* const char *filename */
+                            ,NULL                        /* const char *suffix */
+                            ,47                          /* gint32 max_chars */
+                        );
+  }
+  
+  if (mgp->point_filename_frame)
+  {
+    gtk_frame_set_label (GTK_FRAME (mgp->point_filename_frame), lblTxt);
+  }
+  
+  g_free(lblTxt);
+  
+}
+
+
 void
 p_update_point_index_text(t_mov_gui_stuff *mgp)
 {
@@ -3538,10 +3760,37 @@ p_update_point_index_text(t_mov_gui_stuff *mgp)
               pvals->point_idx + 1, pvals->point_idx_max +1);
 
   if (mgp->point_index_frame)
-    {
-      gtk_frame_set_label (GTK_FRAME (mgp->point_index_frame),
+  {
+    gtk_frame_set_label (GTK_FRAME (mgp->point_index_frame),
                           &mgp->point_index_text[0]);
+  }
+  
+  p_update_current_point_adj(mgp);
+}
+
+static void
+p_update_current_point_adj(t_mov_gui_stuff *mgp)
+{
+  if (mgp->current_point_adj)
+  {
+    gdouble l_val;
+    gdouble l_current_point;
+    gdouble l_upper;
+    
+    l_upper = pvals->point_idx_max +1;
+    l_val = gtk_adjustment_get_upper(GTK_ADJUSTMENT(mgp->current_point_adj));
+    if (l_val != l_upper)
+    {
+        gtk_adjustment_set_upper(GTK_ADJUSTMENT(mgp->current_point_adj), l_upper);
     }
+    
+    l_current_point = pvals->point_idx + 1;
+    l_val = gtk_adjustment_get_value(GTK_ADJUSTMENT(mgp->current_point_adj));
+    if(l_val != l_current_point)
+    {
+      gtk_adjustment_set_value (GTK_ADJUSTMENT(mgp->current_point_adj), l_current_point);
+    }
+  }
 }
 
 
@@ -3551,7 +3800,13 @@ p_update_point_index_text(t_mov_gui_stuff *mgp)
  * ============================================================================
  */
 void
-p_clear_one_point(gint idx)
+p_clear_one_point(gint idx) 
+{
+  p_clear_one_point_protect_keyframe(idx, FALSE /* keyframeProtect */);
+}
+
+void
+p_clear_one_point_protect_keyframe(gint idx, gboolean keyframeProtect)
 {
   if((idx >= 0) && (idx <= pvals->point_idx_max))
   {
@@ -3569,9 +3824,11 @@ p_clear_one_point(gint idx)
     pvals->point[idx].tbrx      = 1.0;
     pvals->point[idx].tbry      = 1.0;
     pvals->point[idx].sel_feather_radius = 0.0;
-    pvals->point[idx].keyframe = 0;   /* 0: controlpoint is not fixed to keyframe */
-    pvals->point[idx].keyframe_abs = 0;   /* 0: controlpoint is not fixed to keyframe */
-
+    if (keyframeProtect != TRUE)
+    {
+      pvals->point[idx].keyframe = 0;   /* 0: controlpoint is not fixed to keyframe */
+      pvals->point[idx].keyframe_abs = 0;   /* 0: controlpoint is not fixed to keyframe */
+    }
     pvals->point[idx].accPosition = 0;           /* 0: linear (NO acceleration) is default */
     pvals->point[idx].accOpacity = 0;            /* 0: linear (NO acceleration) is default */
     pvals->point[idx].accSize = 0;               /* 0: linear (NO acceleration) is default */
@@ -3591,7 +3848,7 @@ p_clear_one_point(gint idx)
  * All settings EXCEPT the position are affected
  */
 void
-p_mix_one_point(gint idx, gint ref1, gint ref2, gdouble mix_factor)
+p_mix_one_point(gint idx, gint ref1, gint ref2, gdouble mix_factor, gboolean keyframeProtect)
 {
 
   if((idx >= 0)
@@ -3627,12 +3884,46 @@ p_mix_one_point(gint idx, gint ref1, gint ref2, gdouble mix_factor)
     pvals->point[idx].accSelFeatherRadius = GAP_BASE_MIX_VALUE(mix_factor, 
pvals->point[ref1].accSelFeatherRadius,  pvals->point[ref2].accSelFeatherRadius);
 
 
-
-    pvals->point[idx].keyframe = 0;   /* 0: controlpoint is not fixed to keyframe */
-    pvals->point[idx].keyframe_abs = 0;   /* 0: controlpoint is not fixed to keyframe */
+    if (keyframeProtect != TRUE)
+    {
+      pvals->point[idx].keyframe = 0;   /* 0: controlpoint is not fixed to keyframe */
+      pvals->point[idx].keyframe_abs = 0;   /* 0: controlpoint is not fixed to keyframe */
+    }
   }
 }       /* end p_mix_one_point */
 
+/* --------------------------
+ * p_mix_one_point_coord
+ * --------------------------
+ * calculate x or y coordinate by mixing
+ * the settings of 2 reference points.
+ * no other settings that the coordinate are affected
+ */
+void
+p_mix_one_point_coord(gint idx, gint ref1, gint ref2, gdouble mix_factor, gboolean xCoord)
+{
+  gdouble value;
+  if((idx >= 0)
+  && (idx <= pvals->point_idx_max)
+  && (ref1 >= 0)
+  && (ref1 <= pvals->point_idx_max)
+  && (ref2 >= 0)
+  && (ref2 <= pvals->point_idx_max)
+  )
+  {
+    if (xCoord == TRUE)
+    {
+      value = GAP_BASE_MIX_VALUE(mix_factor, (gdouble)pvals->point[ref1].p_x,  
(gdouble)pvals->point[ref2].p_x); 
+      pvals->point[idx].p_x  = rint(value);
+    }
+    else
+    {
+      value = GAP_BASE_MIX_VALUE(mix_factor, (gdouble)pvals->point[ref1].p_y,  
(gdouble)pvals->point[ref2].p_y); 
+      pvals->point[idx].p_y  = rint(value);
+    }
+  }
+}       /* end p_mix_one_point_coord */
+
 
 /* ============================================================================
  * p_reset_points
@@ -3737,9 +4028,11 @@ p_load_points(char *filename, t_mov_gui_stuff *mgp)
           pvals->point_idx = 0;
         }
       }
+      mgp->haveOverwritablePointfilename = TRUE;
     }
     else
     {
+      mgp->haveOverwritablePointfilename = FALSE;
       if(l_errno != 0)
       {
         g_message(_("ERROR: Could not open xml parameterfile\n"
@@ -3754,6 +4047,7 @@ p_load_points(char *filename, t_mov_gui_stuff *mgp)
       }
 
     }
+    p_update_pointfilename_text(mgp);
 
     return;
   }
@@ -3765,8 +4059,13 @@ p_load_points(char *filename, t_mov_gui_stuff *mgp)
   {
     p_reset_points();
   }
-  if (l_rc != 0)
+  if (l_rc == 0)
   {
+    mgp->haveOverwritablePointfilename = TRUE;
+  }
+  else
+  {
+    mgp->haveOverwritablePointfilename = FALSE;
     if(l_errno != 0)
     {
       g_message(_("ERROR: Could not open controlpoints\n"
@@ -3780,6 +4079,7 @@ p_load_points(char *filename, t_mov_gui_stuff *mgp)
                ,filename);
     }
   }
+  p_update_pointfilename_text(mgp);
 }  /* end p_load_points */
 
 
@@ -3789,16 +4089,25 @@ p_load_points(char *filename, t_mov_gui_stuff *mgp)
  * ----------------------------
  * depending on the filename extension (.xml)
  *   save point table (from global pvals into named file)
- *
+ * showOverwriteDialog 
+ *   TRUE  (Use a popup dialog to warn user and ask for overwrite permission)
+ *   FALSE (overwrite allowed without futher dialog)
  */
 static void
-p_save_points(char *filename, t_mov_gui_stuff *mgp)
+p_save_points(char *filename, t_mov_gui_stuff *mgp, gboolean showOverwriteDialog)
 {
   gint l_rc;
   gint l_errno;
   gboolean l_wr_permission;
 
-  l_wr_permission = gap_arr_overwrite_file_dialog(filename);
+  if (showOverwriteDialog == TRUE)
+  {
+    l_wr_permission = gap_arr_overwrite_file_dialog(filename);
+  }
+  else 
+  {
+    l_wr_permission = TRUE;
+  }
 
   /* quit if MovePath Mainwindow was closed */
   if(mgp->shell == NULL) { gtk_main_quit (); return; }
@@ -3815,12 +4124,18 @@ p_save_points(char *filename, t_mov_gui_stuff *mgp)
     }
     l_errno = errno;
 
-    if(l_rc != 0)
+    if(l_rc == 0)
     {
+        mgp->haveOverwritablePointfilename = TRUE;
+    }
+    else
+    {
+        mgp->haveOverwritablePointfilename = FALSE;
         g_message (_("Failed to write controlpointfile\n"
                       "filename: '%s':\n%s"),
                    filename, g_strerror (l_errno));
     }
+    p_update_pointfilename_text(mgp);
   }
 }       /* end p_save_points */
 
@@ -3851,6 +4166,7 @@ mov_src_sel_create(t_mov_gui_stuff *mgp)
 {
   GtkWidget *table;
   GtkWidget *sub_table;
+  GtkWidget *sub_table2;  /* for handle offsets */
   GtkWidget *combo;
   GtkWidget *label;
   GtkObject      *adj;
@@ -3863,7 +4179,7 @@ mov_src_sel_create(t_mov_gui_stuff *mgp)
   gtk_table_set_col_spacings (GTK_TABLE (table), 4);
 
   /* Source Layer menu */
-  label = gtk_label_new( _("Source Image/Layer:"));
+  label = gtk_label_new( _("Image/Layer:"));
   gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
   gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 4, 0);
   gtk_widget_show(label);
@@ -4047,6 +4363,7 @@ mov_src_sel_create(t_mov_gui_stuff *mgp)
   mgp->stepmode_combo = combo;
 
 
+
   /* Source Image Handle menu */
 
   label = gtk_label_new( _("Handle:"));
@@ -4054,6 +4371,20 @@ mov_src_sel_create(t_mov_gui_stuff *mgp)
   gtk_table_attach(GTK_TABLE(table), label, 2, 3, 1, 2, GTK_FILL, 0, 4, 0);
   gtk_widget_show(label);
 
+
+  /* the sub_table (1 row) */
+  sub_table2 = gtk_table_new (1, 5, FALSE);
+  gtk_widget_show(sub_table2);
+  gtk_container_set_border_width (GTK_CONTAINER (sub_table2), 2);
+  gtk_table_set_row_spacings (GTK_TABLE (sub_table2), 0);
+  gtk_table_set_col_spacings (GTK_TABLE (sub_table2), 2);
+
+  gtk_table_attach(GTK_TABLE(table), sub_table2, 3, 4, 1, 2,
+                   GTK_EXPAND | GTK_FILL, 0, 0, 0);
+
+
+
+
   combo = gimp_int_combo_box_new (_("Left  Top"),     GAP_HANDLE_LEFT_TOP,
                                   _("Left  Bottom"),  GAP_HANDLE_LEFT_BOT,
                                   _("Right Top"),     GAP_HANDLE_RIGHT_TOP,
@@ -4086,13 +4417,54 @@ mov_src_sel_create(t_mov_gui_stuff *mgp)
                               mgp);
   }
 
-  gtk_table_attach(GTK_TABLE(table), combo, 3, 4, 1, 2,
+  gtk_table_attach(GTK_TABLE(sub_table2), combo, 0, 1, 0, 1,
                    GTK_EXPAND | GTK_FILL, 0, 0, 0);
   gimp_help_set_help_data(combo,
                        _("How to place the Source layer at controlpoint coordinates")
                        , NULL);
   gtk_widget_show(combo);
   mgp->handlemode_combo = combo;
+  
+  
+  /* Handle Offset X */
+  adj = p_mov_spinbutton_new( GTK_TABLE (sub_table2), 1, 0,    /* table col, row */
+                          _("dX:"),                           /* label text */
+                          SCALE_WIDTH, ENTRY_WIDTH,           /* scalesize spinsize */
+                          (gdouble)pvals->handleDx,           /* initial value */
+                          (gdouble)-9999.0, (gdouble)9999.0,   /* lower, upper */
+                          1.0, 10.0,                          /* step, page */
+                          0,                                  /* digits */
+                          FALSE,                              /* constrain */
+                          (gdouble)-9999.0, (gdouble)9999.0,        /* lower, upper (unconstrained) */
+                          _("Handle Offest X is added to x coordinate in all points"),
+                          NULL);    /* tooltip privatetip */
+  g_object_set_data(G_OBJECT(adj), "mgp", mgp);
+  g_signal_connect (G_OBJECT (adj), "value_changed",
+                    G_CALLBACK (mov_instant_double_adjustment_update),
+                    &pvals->handleDx);
+  mgp->handleDx_adj = GTK_ADJUSTMENT(adj);
+
+
+
+  /* Handle Offset Y */
+  adj = p_mov_spinbutton_new( GTK_TABLE (sub_table2), 3, 0,    /* table col, row */
+                          _("dY:"),                           /* label text */
+                          SCALE_WIDTH, ENTRY_WIDTH,           /* scalesize spinsize */
+                          (gdouble)pvals->handleDy,           /* initial value */
+                          (gdouble)-9999.0, (gdouble)9999.0,   /* lower, upper */
+                          1.0, 10.0,                          /* step, page */
+                          0,                                  /* digits */
+                          FALSE,                              /* constrain */
+                          (gdouble)-9999.0, (gdouble)9999.0,        /* lower, upper (unconstrained) */
+                          _("Handle Offest Y is added to y coordinate in all points"),
+                          NULL);    /* tooltip privatetip */
+  g_object_set_data(G_OBJECT(adj), "mgp", mgp);
+  g_signal_connect (G_OBJECT (adj), "value_changed",
+                    G_CALLBACK (mov_instant_double_adjustment_update),
+                    &pvals->handleDy);
+  mgp->handleDy_adj = GTK_ADJUSTMENT(adj);
+  
+  
 
   gtk_widget_show( table );
 
@@ -4549,7 +4921,10 @@ mov_edit_button_box_create (t_mov_gui_stuff *mgp)
 
   /* the frame */
   frame = gimp_frame_new (_("Edit Controlpoints"));
+  mgp->point_filename_frame = frame;
   gtk_container_set_border_width( GTK_CONTAINER( frame ), 2 );
+  mgp->point_filename_frame = frame;
+
 
 
   /* button_table 7 rows */
@@ -4696,7 +5071,8 @@ mov_edit_button_box_create (t_mov_gui_stuff *mgp)
                     GTK_FILL, 0, 0, 0 );
   gimp_help_set_help_data(button,
                        _("Reset all controlpoints to default values "
-                         "but dont change the path (X/Y values). "
+                         "but dont change the path (X/Y values and keyframes). "
+                         "Hold down the alt key removes the keyframe information from all controlpoints."
                          "Hold down the shift key to copy settings "
                          "of point1 into all other points. "
                          "Holding down the ctrl key spreads a mix of "
@@ -4760,10 +5136,10 @@ mov_edit_button_box_create (t_mov_gui_stuff *mgp)
   gtk_table_attach( GTK_TABLE(button_table), button, 1, 2, row, row+1,
                     GTK_FILL, 0, 0, 0 );
   gimp_help_set_help_data(button,
-                       _("Save controlpoints to file")
+                       _("Save controlpoints to file. Hold down the ctrl or shift key for filename selection 
dialog.")
                        , NULL);
   gtk_widget_show (button);
-  g_signal_connect (G_OBJECT (button), "clicked",
+  g_signal_connect (G_OBJECT (button), "button_press_event",
                     G_CALLBACK (mov_psave_callback),
                     mgp);
 
@@ -5601,6 +5977,9 @@ mov_path_prevw_create ( GimpDrawable *drawable, t_mov_gui_stuff *mgp, gboolean v
   GtkObject      *adj;
   GtkWidget      *framerange_table;
   GtkWidget      *edit_buttons;
+  GtkWidget      *button;
+  guint          row;
+  guint          col;
 
   mgp->drawable = drawable;
   mgp->dwidth   = gimp_drawable_width(drawable->drawable_id );
@@ -5634,16 +6013,33 @@ mov_path_prevw_create ( GimpDrawable *drawable, t_mov_gui_stuff *mgp, gboolean v
 
 
   /* the table (3 rows) for other controlpoint specific settings */
-  table = gtk_table_new ( 3, 4, FALSE );
+  table = gtk_table_new ( 3, 5, FALSE );
   gtk_container_set_border_width (GTK_CONTAINER (table), 2 );
   gtk_table_set_row_spacings (GTK_TABLE (table), 2);
   gtk_table_set_col_spacings (GTK_TABLE (table), 4);
   gtk_container_add (GTK_CONTAINER (cpt_frame), table);
 
-
   /* X */
-  adj = gimp_scale_entry_new( GTK_TABLE (table), 0, 0,            /* table col, row */
-                          _("X:"),                                /* label text */
+  row = 0;
+  col = 0;
+  
+  button = gtk_button_new_with_label (_("X:"));
+  GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
+  gtk_table_attach( GTK_TABLE(table), button, col, col+1, row, row +1,
+                    0/*GTK_FILL*/, 0, 0, 0 );
+  gimp_help_set_help_data(button,
+                       _("Copy X coordinate from previous Controlpoint. "
+                         "Holding down the shift Key Copy X coordinate from next Controlpoint. "
+                         "Holding down the ctrl Key Calculate X coordinate as average between previous and 
next Controlpoint.")
+                       , NULL);
+  gtk_widget_show (button);
+  g_signal_connect (G_OBJECT (button), "button_press_event",
+                    G_CALLBACK (mov_x_button_callback),
+                    mgp);
+
+  col++;
+  adj = gimp_scale_entry_new( GTK_TABLE (table), col, row,        /* table col, row */
+                          /* X: */ "",                            /* label text */
                           SCALE_WIDTH, SPINBUTTON_WIDTH,          /* scalesize spinsize */
                           (gdouble)mgp->p_x,                      /* value */
                           (gdouble)0, (gdouble)mgp->dwidth,       /* lower, upper */
@@ -5660,8 +6056,26 @@ mov_path_prevw_create ( GimpDrawable *drawable, t_mov_gui_stuff *mgp, gboolean v
   mgp->x_adj = GTK_ADJUSTMENT(adj);
 
   /* Y */
-  adj = gimp_scale_entry_new( GTK_TABLE (table), 0, 1,            /* table col, row */
-                          _("Y:"),                                /* label text */
+  row = 1;
+  col = 0;
+  
+  button = gtk_button_new_with_label (_("Y:"));
+  GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
+  gtk_table_attach( GTK_TABLE(table), button, col, col+1, row, row +1,
+                    0/*GTK_FILL*/, 0, 0, 0 );
+  gimp_help_set_help_data(button,
+                       _("Copy Y coordinate from previous Controlpoint. "
+                         "Holding down the shift Key Copy Y coordinate from next Controlpoint. "
+                         "Holding down the ctrl Key Calculate Y coordinate as average between previous and 
next Controlpoint.")
+                       , NULL);
+  gtk_widget_show (button);
+  g_signal_connect (G_OBJECT (button), "button_press_event",
+                    G_CALLBACK (mov_y_button_callback),
+                    mgp);
+
+  col++;
+  adj = gimp_scale_entry_new( GTK_TABLE (table), col, row,        /* table col, row */
+                          /* Y: */ "",                                     /* label text */
                           SCALE_WIDTH, ENTRY_WIDTH,               /* scalesize spinsize */
                           (gdouble)mgp->p_y,                      /* value */
                           (gdouble)0, (gdouble)mgp->dheight,      /* lower, upper */
@@ -5678,7 +6092,9 @@ mov_path_prevw_create ( GimpDrawable *drawable, t_mov_gui_stuff *mgp, gboolean v
   mgp->y_adj = GTK_ADJUSTMENT(adj);
 
   /* Keyframe */
-  adj = p_mov_spinbutton_new( GTK_TABLE (table), 1, 2,          /* table col, row */
+  row = 2;
+  col = 2;
+  adj = p_mov_spinbutton_new( GTK_TABLE (table), col, row,      /* table col, row */
                           _("Keyframe:"),                       /* label text */
                           SCALE_WIDTH, ENTRY_WIDTH,             /* scalesize spinsize */
                           (gdouble)mgp->keyframe_abs,           /* value */
@@ -5742,8 +6158,10 @@ mov_path_prevw_create ( GimpDrawable *drawable, t_mov_gui_stuff *mgp, gboolean v
                              );
 
   }
-  gtk_table_attach(GTK_TABLE(table), notebook, 3, 4          /* column */
-                                             , 0, 3          /* all rows */
+  row = 0;
+  col = 4;
+  gtk_table_attach(GTK_TABLE(table), notebook, col, col+1    /* column */
+                                             , row, row+3    /* all rows */
                                              , 0, 0, 0, 0);
 
   gtk_widget_show (notebook);
@@ -6001,7 +6419,7 @@ mov_path_prevw_create ( GimpDrawable *drawable, t_mov_gui_stuff *mgp, gboolean v
   }
 
   /* the preview sub table (1 row) */
-  pv_sub_table = gtk_table_new ( 1, 3, FALSE );
+  pv_sub_table = gtk_table_new ( 1, 5, FALSE );
 
   /* the Preview Frame Number  */
   adj = gimp_scale_entry_new( GTK_TABLE (pv_sub_table), 0, 1,   /* table col, row */
@@ -6044,6 +6462,56 @@ mov_path_prevw_create ( GimpDrawable *drawable, t_mov_gui_stuff *mgp, gboolean v
     }
 
   }
+  
+  /* the Point Spinbutton */
+  mgp->current_point = pvals->point_idx + 1;
+  adj = p_mov_spinbutton_new( GTK_TABLE (pv_sub_table), 4, 1,          /* table col, row */
+                          _("Point:"),                          /* label text */
+                          SCALE_WIDTH, ENTRY_WIDTH,             /* scalesize spinsize */
+                          (gdouble)mgp->current_point,                   /* value */
+                          (gdouble)1, (gdouble)pvals->point_idx_max +1,  /* lower, upper */
+                          1, 10,                                         /* step, page */
+                          0,                                             /* digits */
+                          TRUE,                                          /* constrain */
+                          (gdouble)1, (gdouble)pvals->point_idx_max +1,  /* lower, upper (unconstrained) */
+                          _("Current controlpoint"),
+                          NULL);    /* tooltip privatetip */
+  g_signal_connect (G_OBJECT (adj), "value_changed",
+                    G_CALLBACK (mov_path_current_point_update),
+                    mgp);
+  mgp->current_point_adj = GTK_ADJUSTMENT(adj);
+  if (FALSE)
+  {
+    // TODO 
+    /* this code does not yet work as expected, 
+     *  when mov_path_current_point_spinbutton_callback is connected to the spinbutton
+     *  it seems that the default handler is not called anymore and the value can not be
+     *  adjusted via mouseclicks on the small arrow up/down buttons of the spinbutton widget.
+     *
+     * when mov_path_current_point_spinbutton_callback is connected via g_signal_connect_after
+     * ist is not called (because the default handler comes first)
+     * (For proper operation mov_path_current_point_spinbutton_callback should connect BEFORE the default 
handler
+     * or explicite call the default handler... 
+     *
+     * The wanted behavios should add the optional follow_keyframe feature in case
+     * the SHIFT modifyer is hold down while ajusting the spinbutton value via moseclick
+     */
+    GtkWidget *spinbutton;
+    
+    spinbutton = g_object_get_data( G_OBJECT(adj), "spinbutton" );
+    if (spinbutton != NULL)
+    {
+      // g_signal_connect_after   "button_press_event"  "key_press_event"
+      g_signal_connect (G_OBJECT (spinbutton), "button_press_event",
+                    G_CALLBACK (mov_path_current_point_spinbutton_callback),
+                    mgp);
+      g_signal_connect (G_OBJECT (spinbutton), "button_press_event",
+                    G_CALLBACK (mov_path_current_point_spinbutton_callback),
+                    mgp);
+    }
+  
+  }
+  
 
 
   gtk_table_attach( GTK_TABLE(pv_table), pv_sub_table, 0, 1, 3, 4,
@@ -6416,6 +6884,56 @@ mov_path_keyframe_update ( GtkWidget *widget, t_mov_gui_stuff *mgp)
 }
 
 
+static gboolean
+mov_path_current_point_spinbutton_callback ( GtkWidget *widget,
+                    GdkEventButton *bevent,
+                    t_mov_gui_stuff *mgp)
+{
+  if(gap_debug)
+  {
+     printf("mov_path_current_point_spinbutton_callback bevent->state:%d", bevent->state);
+     if (mgp->current_point_spinbutton_bevent_state & GDK_SHIFT_MASK)
+     {
+       printf(" GDK_SHIFT_MASK set");
+     }
+     printf("\n");
+  }
+
+  if(mgp != NULL)
+  {
+    mgp->current_point_spinbutton_bevent_state = bevent->state;
+  }
+  
+  return (TRUE);
+}
+
+static void
+mov_path_current_point_update ( GtkWidget *widget, t_mov_gui_stuff *mgp)
+{
+  if(gap_debug)
+  {
+     printf("mov_path_current_point_update START\n");
+  }
+  gimp_int_adjustment_update(GTK_ADJUSTMENT(widget), &mgp->current_point);
+  if (mgp->current_point != pvals->point_idx +1)
+  {
+    gint new_point_idx;
+    
+    new_point_idx = CLAMP(mgp->current_point -1, 0, pvals->point_idx_max);  /* the value entered in the 
widget starts at 1, point_idx starts at 0 */
+    p_points_to_tab(mgp);
+    pvals->point_idx = new_point_idx;
+    p_point_refresh(mgp);
+    
+    if (mgp->current_point_spinbutton_bevent_state & GDK_SHIFT_MASK)
+    {
+      mov_follow_keyframe(mgp);
+    }
+    mov_set_instant_apply_request(mgp);
+  }
+
+}
+
+
 /*
  *  mov_path_xy_adjustment_update
  */
diff --git a/gap/gap_mov_dialog.h b/gap/gap_mov_dialog.h
index 44426a7..4bc437d 100644
--- a/gap/gap_mov_dialog.h
+++ b/gap/gap_mov_dialog.h
@@ -298,6 +298,10 @@ typedef struct {
         GapMovMergePostProcessingMode  mergeModeRenderedTweenLayer;
         GapMovMergePostProcessingMode  mergeModeRenderedTraceLayer;
         GapMovMergePostProcessingTargetMode  mergeTarget;
+        
+        
+        gdouble handleDx;
+        gdouble handleDy;
 
 
 } GapMovValues;
diff --git a/gap/gap_mov_exec.c b/gap/gap_mov_exec.c
index 9013dbc..a9f39bc 100644
--- a/gap/gap_mov_exec.c
+++ b/gap/gap_mov_exec.c
@@ -2061,8 +2061,8 @@ gap_mov_exec_set_handle_offsets_singleframe(GapMovValues *val_ptr, GapMovCurrent
    l_src_width  = gimp_image_width(cur_ptr->singleMovObjImageId);
    l_src_height = gimp_image_height(cur_ptr->singleMovObjImageId);
 
-   cur_ptr->l_handleX = 0.0;
-   cur_ptr->l_handleY = 0.0;
+   cur_ptr->l_handleX = 0 + val_ptr->handleDx;
+   cur_ptr->l_handleY = 0 + val_ptr->handleDy;
    switch(val_ptr->src_handle)
    {
       case GAP_HANDLE_LEFT_BOT:
@@ -4553,8 +4553,8 @@ void gap_mov_exec_set_handle_offsets(GapMovValues *val_ptr, GapMovCurrent *cur_p
      l_src_height = gimp_image_height(val_ptr->cache_tmp_image_id);
    }
 
-   cur_ptr->l_handleX = 0.0;
-   cur_ptr->l_handleY = 0.0;
+   cur_ptr->l_handleX = 0 + val_ptr->handleDx;
+   cur_ptr->l_handleY = 0 + val_ptr->handleDy;
    switch(val_ptr->src_handle)
    {
       case GAP_HANDLE_LEFT_BOT:
@@ -4693,6 +4693,9 @@ GapMovValues *gap_mov_exec_new_GapMovValues()
   pvals->mergeModeRenderedTweenLayer = GAP_MPP_MODE_KEEP;
   pvals->mergeModeRenderedTraceLayer = GAP_MPP_MODE_KEEP;
   pvals->mergeTarget = GAP_MPP_TARGET_NEW_LAYER;
+  
+  pvals->handleDx = 0.0;
+  pvals->handleDy = 0.0;
 
 
   return(pvals);
@@ -4775,6 +4778,8 @@ void gap_mov_exec_copy_xml_GapMovValues(GapMovValues *dstValues, GapMovValues *s
   dstValues->total_frames = srcValues->total_frames;
   dstValues->src_layerstack = srcValues->src_layerstack;
   dstValues->src_handle = srcValues->src_handle;
+  dstValues->handleDx = srcValues->handleDx;
+  dstValues->handleDy = srcValues->handleDy;
   dstValues->src_stepmode = srcValues->src_stepmode;
   dstValues->src_selmode = srcValues->src_selmode;
   dstValues->src_paintmode = srcValues->src_paintmode;
diff --git a/gap/gap_mov_render.c b/gap/gap_mov_render.c
index 79048cc..b7246cd 100644
--- a/gap/gap_mov_render.c
+++ b/gap/gap_mov_render.c
@@ -301,7 +301,7 @@ p_mov_transform_perspective(gint32 layer_id
   if(gap_debug)
   {
     printf("** p_mov_transform_perspective:\n");
-    printf("  Factors: [0] %.3f %.3f  [1] %.3f %.3f  [2] %.3f %.3f  [3] %.3f %.3f\n"
+    printf("  Factors: [0] %.8f %.8f  [1] %.8f %.8f  [2] %.8f %.8f  [3] %.8f %.8f\n"
           ,(float)cur_ptr->currTTLX
           ,(float)cur_ptr->currTTLY
           ,(float)cur_ptr->currTTRX
@@ -315,13 +315,13 @@ p_mov_transform_perspective(gint32 layer_id
           ,(float)width
           ,(float)height
           );
-    printf("  x0: %4.3f y0: %4.3f     x1: %4.3f y1: %4.3f\n"
+    printf("  x0: %4.8f y0: %4.8f     x1: %4.8f y1: %4.8f\n"
           ,(float)x0
           ,(float)y0
           ,(float)x1
           ,(float)y1
           );
-    printf("  x2: %4.3f y2: %4.3f     x3: %4.3f y3: %4.3f\n\n"
+    printf("  x2: %4.8f y2: %4.8f     x3: %4.8f y3: %4.8f\n\n"
           ,(float)x2
           ,(float)y2
           ,(float)x3
@@ -942,14 +942,25 @@ gap_mov_render_render(gint32 image_id, GapMovValues *val_ptr, GapMovCurrent *cur
                           , &scaleHeightPercent
                           );
 
-
+  if (gap_debug)
+  {
+    printf("After: p_mov_calculate_scale_factors cur_ptr->currWidth: %f  currHeight:%f "
+           " scaleWidthPercent:%.20f scaleHeightPercent:%.20f  l_orig_width:%d l_orig_height:%d\n"
+          , (float)cur_ptr->currWidth
+          , (float)cur_ptr->currHeight
+          , (float)scaleWidthPercent
+          , (float)scaleHeightPercent
+          , (int)l_orig_width
+          , (int)l_orig_height
+          );
+  }
 
 
 
   if((scaleWidthPercent * scaleHeightPercent) > (100.0 * 100.0))
   {
-    l_potential_new_width  = (l_orig_width  * scaleWidthPercent) / 100;
-    l_potential_new_height = (l_orig_height * scaleHeightPercent) / 100;
+    l_potential_new_width  = rint(((gdouble)l_orig_width  * scaleWidthPercent) / 100.0);
+    l_potential_new_height = rint(((gdouble)l_orig_height * scaleHeightPercent) / 100.0);
 
     if((l_potential_new_width != l_new_width)
     || (l_potential_new_height != l_new_height))
@@ -979,6 +990,13 @@ gap_mov_render_render(gint32 image_id, GapMovValues *val_ptr, GapMovCurrent *cur
                        , &l_new_width
                        , &l_new_height
                        );
+    if(gap_debug)
+    {
+      printf("PER_TRANS done after upscale l_new_width:%d l_new_height:%d \n"
+        , (int)l_new_width
+        , (int)l_new_height
+        );
+    }
   }
   else
   {
@@ -991,8 +1009,16 @@ gap_mov_render_render(gint32 image_id, GapMovValues *val_ptr, GapMovCurrent *cur
                        , &l_new_height
                        );
 
-    l_potential_new_width  = (l_new_width  * scaleWidthPercent) / 100;
-    l_potential_new_height = (l_new_height * scaleHeightPercent) / 100;
+    if(gap_debug)
+    {
+      printf("PER_TRANS done l_new_width:%d l_new_height:%d \n"
+        , (int)l_new_width
+        , (int)l_new_height
+        );
+    }
+
+    l_potential_new_width  = rint(((gdouble)l_new_width  * scaleWidthPercent) / 100.0);
+    l_potential_new_height = rint(((gdouble)l_new_height * scaleHeightPercent) / 100.0);
 
     if((l_potential_new_width != l_new_width)
     || (l_potential_new_height != l_new_height))
@@ -1029,13 +1055,21 @@ gap_mov_render_render(gint32 image_id, GapMovValues *val_ptr, GapMovCurrent *cur
 
     l_new_width  = gimp_drawable_width(l_cp_layer_id);
     l_new_height = gimp_drawable_height(l_cp_layer_id);
+
+    if(gap_debug)
+    {
+      printf("ROTATE done l_new_width:%d l_new_height:%d \n"
+        , (int)l_new_width
+        , (int)l_new_height
+        );
+    }
   }
 
   if(l_resized_flag == 1)
   {
-     /* adjust offsets according to handle and change of size */
-     switch(val_ptr->src_handle)
-     {
+    /* adjust offsets according to handle and change of size */
+    switch(val_ptr->src_handle)
+    {
         case GAP_HANDLE_LEFT_BOT:
            l_src_offset_y += ((gint)l_orig_height - (gint)l_new_height);
            break;
@@ -1053,7 +1087,20 @@ gap_mov_render_render(gint32 image_id, GapMovValues *val_ptr, GapMovCurrent *cur
         case GAP_HANDLE_LEFT_TOP:
         default:
            break;
-     }
+    }
+    if(gap_debug)
+    {
+      printf("After RESIZED_FLAG l_src_offset_x:%d l_src_offset_y:%d \n"
+             " l_orig_width:%d l_orig_height:%d\n"
+             " l_new_width:%d  l_new_height:%d\n"
+        , (int)l_src_offset_x
+        , (int)l_src_offset_y
+        , (int)l_orig_width
+        , (int)l_orig_height
+        , (int)l_new_width
+        , (int)l_new_height
+        );
+    }
   }
 
   /* calculate offsets in destination image */
@@ -1063,6 +1110,24 @@ gap_mov_render_render(gint32 image_id, GapMovValues *val_ptr, GapMovCurrent *cur
   /* modify coordinate offsets of the copied layer within dest. image */
   gimp_layer_set_offsets(l_cp_layer_id, l_offset_x, l_offset_y);
 
+  if(gap_debug)
+  {
+      printf("FINAL SET OFFSETS l_offset_x:%d l_offset_y:%d \n"
+             " currX:%d currY:%d\n"
+             " l_handleX:%d l_handleY:%d\n"
+             " l_src_offset_x:%d l_src_offset_y:%d\n"
+        , (int)l_offset_x
+        , (int)l_offset_y
+        , (int)cur_ptr->currX
+        , (int)cur_ptr->currY
+        , (int)cur_ptr->l_handleX
+        , (int)cur_ptr->l_handleY
+        , (int)l_src_offset_x 
+        , (int)l_src_offset_y
+        );
+  }
+
+
   /* clip the handled layer to image size if desired */
   if(val_ptr->clip_to_img != 0)
   {
@@ -1306,8 +1371,8 @@ render_fetch_wanted_src_frame:
                  , (float)pvals->apv_scalex, (float)pvals->apv_scaley );
        }
 
-       l_size_x = (gimp_image_width(pvals->cache_tmp_image_id) * pvals->apv_scalex) / 100;
-       l_size_y = (gimp_image_height(pvals->cache_tmp_image_id) * pvals->apv_scaley) / 100;
+       l_size_x = rint(((gdouble)gimp_image_width(pvals->cache_tmp_image_id) * pvals->apv_scalex) / 100.0);
+       l_size_y = rint(((gdouble)gimp_image_height(pvals->cache_tmp_image_id) * pvals->apv_scaley) / 100.0);
        gimp_image_scale(pvals->cache_tmp_image_id, l_size_x, l_size_y);
      }
 
diff --git a/gap/gap_mov_xml_par.c b/gap/gap_mov_xml_par.c
index dedb627..8acec52 100644
--- a/gap/gap_mov_xml_par.c
+++ b/gap/gap_mov_xml_par.c
@@ -95,6 +95,8 @@
 #define GAP_MOVPATH_XML_TOKEN_DST_GROUP_DELIM        "dst_group_name_delimiter"
 
 #define GAP_MOVPATH_XML_TOKEN_STEP_SPEED_FACTOR      "step_speed_factor"
+#define GAP_MOVPATH_XML_TOKEN_HANDLE_DX              "handle_dx"
+#define GAP_MOVPATH_XML_TOKEN_HANDLE_DY              "handle_dy"
 #define GAP_MOVPATH_XML_TOKEN_SRC_FORCE_VISIBLE      "src_force_visible"
 #define GAP_MOVPATH_XML_TOKEN_CLIP_TO_IMG            "clip_to_img"
 #define GAP_MOVPATH_XML_TOKEN_SRC_APPLY_BLUEBOX      "src_apply_bluebox"
@@ -726,6 +728,14 @@ p_xml_parse_element_moving_object(const gchar         *element_name,
     {
       userDataPtr->isParseOk = gap_xml_parse_value_gdouble(*value_cursor, 
&userDataPtr->pvals->step_speed_factor);
     }
+    else if (strcmp (*name_cursor, GAP_MOVPATH_XML_TOKEN_HANDLE_DX) == 0)
+    {
+      userDataPtr->isParseOk = gap_xml_parse_value_gdouble(*value_cursor, &userDataPtr->pvals->handleDx);
+    }
+    else if (strcmp (*name_cursor, GAP_MOVPATH_XML_TOKEN_HANDLE_DY) == 0)
+    {
+      userDataPtr->isParseOk = gap_xml_parse_value_gdouble(*value_cursor, &userDataPtr->pvals->handleDy);
+    }
     else if (strcmp (*name_cursor, GAP_MOVPATH_XML_TOKEN_SRC_FORCE_VISIBLE) == 0)
     {
       userDataPtr->isParseOk = gap_xml_parse_value_gboolean_as_gint(*value_cursor, 
&userDataPtr->pvals->src_force_visible);
@@ -1592,6 +1602,8 @@ gap_mov_xml_par_save(char *filename, GapMovValues *pvals)
       }
       fprintf(l_fp, "\n    ");
       gap_xml_write_EnumValue(l_fp, GAP_MOVPATH_XML_TOKEN_SRC_HANDLE, pvals->src_handle, 
&valuesGapMovHandle[0]);
+      gap_xml_write_gdouble_value(l_fp, GAP_MOVPATH_XML_TOKEN_HANDLE_DX, pvals->handleDx, 4, 0);
+      gap_xml_write_gdouble_value(l_fp, GAP_MOVPATH_XML_TOKEN_HANDLE_DY, pvals->handleDy, 4, 0);
       fprintf(l_fp, "\n    ");
       gap_xml_write_EnumValue(l_fp, GAP_MOVPATH_XML_TOKEN_SRC_STEPMODE, pvals->src_stepmode, 
&valuesGapMovStepMode[0]);
       gap_xml_write_gdouble_value(l_fp, GAP_MOVPATH_XML_TOKEN_STEP_SPEED_FACTOR, pvals->step_speed_factor, 
1, 5);
diff --git a/gap/gap_onion_base.c b/gap/gap_onion_base.c
index 3da67bc..83c141a 100644
--- a/gap/gap_onion_base.c
+++ b/gap/gap_onion_base.c
@@ -105,7 +105,7 @@ gap_onion_base_check_is_onion_layer(gint32 layer_id)
    if(gap_debug) printf("gap_onion_base_check_is_onion_layer: START layer_id %d\n", (int)layer_id);
 
    l_found = FALSE;
-   l_parasite = gimp_drawable_parasite_find(layer_id, GAP_ONION_PARASITE_NAME);
+   l_parasite = gimp_item_get_parasite(layer_id, GAP_ONION_PARASITE_NAME);
    if (l_parasite)
    {
       l_parasite_data = (GapOnionBaseParasite_data *)l_parasite->data;
@@ -281,6 +281,7 @@ gap_onion_base_onionskin_apply(gpointer gpp
   char         *l_name;
   gint32        l_layer_id;
   gint32        l_new_layer_id;
+  gint32        l_layermask_id;
   gint32     *l_layers_list;
   gint        l_nlayers;
   gdouble     l_opacity;
@@ -309,6 +310,7 @@ gap_onion_base_onionskin_apply(gpointer gpp
      printf("  ainfo_last_frame_nr: %d\n",     (int)ainfo_last_frame_nr);
      printf("  ainfo_basename: %s\n",     ainfo_basename);
      printf("  ainfo_extension: %s\n",    ainfo_extension);
+     printf("  layermask_mode:  %d\n", (int)vin_ptr->layermask_mode);
 
   }
 
@@ -572,9 +574,68 @@ gap_onion_base_onionskin_apply(gpointer gpp
       g_free(l_name);
 
 
+
+      /* (optional) create layermask */
+      l_layermask_id = -1;
+      switch(vin_ptr->layermask_mode)
+      {
+        case GAP_ONION_LAYERMASK_MODE_SELECTION:
+        case GAP_ONION_LAYERMASK_MODE_SELECTION_CLIP:
+          if(gimp_layer_get_mask(l_new_layer_id) >= 0)
+          {
+            gimp_layer_remove_mask (l_new_layer_id, GIMP_MASK_APPLY);
+          }
+          l_layermask_id = gimp_layer_create_mask(l_new_layer_id, GIMP_ADD_SELECTION_MASK);
+          gimp_layer_add_mask(l_new_layer_id, l_layermask_id);
+          if (vin_ptr->layermask_mode == GAP_ONION_LAYERMASK_MODE_SELECTION_CLIP)
+          {
+            gap_layer_resize_to_selection(image_id, l_new_layer_id);
+          }
+          break;
+        case GAP_ONION_LAYERMASK_MODE_BLACK:
+          if(gimp_layer_get_mask(l_new_layer_id) >= 0)
+          {
+            gimp_layer_remove_mask (l_new_layer_id, GIMP_MASK_DISCARD);
+          }
+          l_layermask_id = gimp_layer_create_mask(l_new_layer_id, GIMP_ADD_BLACK_MASK);
+          gimp_layer_add_mask(l_new_layer_id, l_layermask_id);
+          break;
+        case GAP_ONION_LAYERMASK_MODE_WHITE:
+          if(gimp_layer_get_mask(l_new_layer_id) >= 0)
+          {
+            gimp_layer_remove_mask (l_new_layer_id, GIMP_MASK_DISCARD);
+          }
+          l_layermask_id = gimp_layer_create_mask(l_new_layer_id, GIMP_ADD_WHITE_MASK);
+          gimp_layer_add_mask(l_new_layer_id, l_layermask_id);
+          break;
+        default:  /* GAP_ONION_LAYERMASK_MODE_NONE */
+          break;
+      }
+
+
       /* Set parasite or tattoo */
       gap_onion_base_mark_as_onionlayer(l_new_layer_id);
       /* gimp_layer_set_opacity(l_new_layer_id, l_opacity); */
+      
+      /* Handle activation of onion layer or its layermask */
+      if(vin_ptr->active_mode != GAP_ONION_ACTIVE_MODE_NONE)
+      {
+         gimp_image_set_active_layer(image_id, l_new_layer_id);
+         
+         if (l_layermask_id >= 0)
+         {
+           gboolean setMaskActive;
+
+           setMaskActive = FALSE;
+           if(vin_ptr->active_mode == GAP_ONION_ACTIVE_MODE_ONION_LAYER_MASK)
+           {
+             setMaskActive = TRUE;
+           }
+           gimp_layer_set_edit_mask(l_new_layer_id, setMaskActive);
+         
+         }
+      }
+
     }
 
 
@@ -596,9 +657,14 @@ gap_onion_base_onionskin_apply(gpointer gpp
 
   }
 
-  if(l_active_layer >= 0)
+
+  
+  if (vin_ptr->active_mode == GAP_ONION_ACTIVE_MODE_NONE)
   {
-    gimp_image_set_active_layer(image_id, l_active_layer);
+    if(l_active_layer >= 0)
+    {
+      gimp_image_set_active_layer(image_id, l_active_layer);
+    }
   }
 
   if(gap_debug) printf("gap_onion_base_onionskin_apply: END\n\n");
diff --git a/gap/gap_onion_base.h b/gap/gap_onion_base.h
index a5941a2..0005316 100644
--- a/gap/gap_onion_base.h
+++ b/gap/gap_onion_base.h
@@ -65,6 +65,17 @@
 #define GAP_ONION_REFMODE_BIDRIECTIONAL_SINGLE   1
 #define GAP_ONION_REFMODE_BIDRIECTIONAL_DOUBLE   2
 
+#define GAP_ONION_LAYERMASK_MODE_NONE            0
+#define GAP_ONION_LAYERMASK_MODE_BLACK           1
+#define GAP_ONION_LAYERMASK_MODE_WHITE           2
+#define GAP_ONION_LAYERMASK_MODE_SELECTION       3
+#define GAP_ONION_LAYERMASK_MODE_SELECTION_CLIP  4
+
+#define GAP_ONION_ACTIVE_MODE_NONE               0
+#define GAP_ONION_ACTIVE_MODE_ONION_LAYER        1
+#define GAP_ONION_ACTIVE_MODE_ONION_LAYER_MASK   2
+
+
 typedef struct GapOnionBaseParasite_data {
    time_t       timestamp;      /* UTC timecode of creation time */
    gint32       tattoo;         /* unique tattoo */
diff --git a/gap/gap_onion_dialog.c b/gap/gap_onion_dialog.c
index d959c6b..fe0484e 100644
--- a/gap/gap_onion_dialog.c
+++ b/gap/gap_onion_dialog.c
@@ -24,6 +24,7 @@
  */
 
 /* revision history:
+ * version 2.8.xx;  2017/03/15   hof: added onionskin setting layermask_mode and active_mode
  * version 2.1.0a;  2004/06/03   hof: added onionskin setting ref_mode
  * version 2.1.0a;  2004.04.10   hof: callbacks now directly use GapOnionMainGlobalParams *gpp
  *                                    rather than gpointer
@@ -57,6 +58,8 @@
 #define ENC_MENU_ITEM_INDEX_KEY "gap_enc_menu_item_index"
 #define MAX_SELECT_MODE_ARRAY_ELEMENTS 7
 #define MAX_REF_MODE_ARRAY_ELEMENTS 3
+#define MAX_LAYERMASK_MODE_ARRAY_ELEMENTS 5
+#define MAX_ACTIVE_MODE_ARRAY_ELEMENTS 3
 
 
 extern int gap_debug;
@@ -69,6 +72,21 @@ gint  gtab_ref_modes[MAX_REF_MODE_ARRAY_ELEMENTS] =
   };
 
 
+gint  gtab_layermask_modes[MAX_LAYERMASK_MODE_ARRAY_ELEMENTS] =
+  { GAP_ONION_LAYERMASK_MODE_NONE                /* 0 */
+  , GAP_ONION_LAYERMASK_MODE_BLACK  /* 1 */
+  , GAP_ONION_LAYERMASK_MODE_WHITE  /* 2 */
+  , GAP_ONION_LAYERMASK_MODE_SELECTION  /* 3 */
+  , GAP_ONION_LAYERMASK_MODE_SELECTION_CLIP  /* 4 */
+  };
+
+gint  gtab_active_modes[MAX_ACTIVE_MODE_ARRAY_ELEMENTS] =
+  { GAP_ONION_ACTIVE_MODE_NONE                /* 0 */
+  , GAP_ONION_ACTIVE_MODE_ONION_LAYER         /* 1 */
+  , GAP_ONION_ACTIVE_MODE_ONION_LAYER_MASK    /* 2 */
+  };
+
+
 static void
 p_init_main_dialog_widgets(GapOnionMainGlobalParams *gpp);
 
@@ -272,6 +290,50 @@ on_oni__combo_ref_mode (GtkWidget     *widget,
 }
 
 static void
+on_oni__combo_layermask_mode (GtkWidget     *widget,
+                        GapOnionMainGlobalParams *gpp)
+{
+  gint       l_idx;
+  gint       value;
+
+  if(gap_debug) printf("CB: on_oni__combo_layermask_mode\n");
+
+  if(gpp == NULL) return;
+
+  gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &value);
+  l_idx = value;
+
+  if(gap_debug) printf("CB: on_oni__combo_layermask_mode index: %d\n", (int)l_idx);
+  if((l_idx >= MAX_LAYERMASK_MODE_ARRAY_ELEMENTS) || (l_idx < 1))
+  {
+     l_idx = 0;
+  }
+  gpp->vin.layermask_mode = gtab_layermask_modes[l_idx];
+}
+
+static void
+on_oni__combo_active_mode (GtkWidget     *widget,
+                        GapOnionMainGlobalParams *gpp)
+{
+  gint       l_idx;
+  gint       value;
+
+  if(gap_debug) printf("CB: on_oni__combo_active_mode\n");
+
+  if(gpp == NULL) return;
+
+  gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &value);
+  l_idx = value;
+
+  if(gap_debug) printf("CB: on_oni__combo_active_mode index: %d\n", (int)l_idx);
+  if((l_idx >= MAX_ACTIVE_MODE_ARRAY_ELEMENTS) || (l_idx < 1))
+  {
+     l_idx = 0;
+  }
+  gpp->vin.active_mode = gtab_active_modes[l_idx];
+}
+
+static void
 on_oni__spinbutton_range_from_changed  (GtkEditable     *editable,
                                         GapOnionMainGlobalParams *gpp)
 {
@@ -707,6 +769,9 @@ on_oni__checkbutton_auto_delete_toggled  (GtkToggleButton *togglebutton,
  }
 }
 
+
+
+
 static void
 p_init_combo_actual_idx(GapOnionMainGlobalParams *gpp, GtkWidget *wgt, gint *gtab_ptr, gint val, gint maxidx)
 {
@@ -726,6 +791,16 @@ p_init_combo_actual_idx(GapOnionMainGlobalParams *gpp, GtkWidget *wgt, gint *gta
 static void
 p_init_combos(GapOnionMainGlobalParams *gpp)
 {
+ if(gap_debug)
+ {
+   printf("p_init_combos: select_mode:%d ref_mode:%d layermask_mode:%d active_mode:%d\n"
+      ,(int)gpp->vin.select_mode
+      ,(int)gpp->vin.ref_mode
+      ,(int)gpp->vin.layermask_mode
+      ,(int)gpp->vin.active_mode
+      );
+ }
+
  p_init_combo_actual_idx( gpp
                              , gpp->oni__combo_select_mode
                              , gtab_select_modes
@@ -738,6 +813,18 @@ p_init_combos(GapOnionMainGlobalParams *gpp)
                              , gpp->vin.ref_mode
                              , MAX_REF_MODE_ARRAY_ELEMENTS
                              );
+ p_init_combo_actual_idx( gpp
+                             , gpp->oni__combo_layermask_mode
+                             , gtab_layermask_modes
+                             , gpp->vin.layermask_mode
+                             , MAX_LAYERMASK_MODE_ARRAY_ELEMENTS
+                             );
+ p_init_combo_actual_idx( gpp
+                             , gpp->oni__combo_active_mode
+                             , gtab_active_modes
+                             , gpp->vin.active_mode
+                             , MAX_ACTIVE_MODE_ARRAY_ELEMENTS
+                             );
 }
 
 
@@ -816,6 +903,7 @@ p_init_togglebuttons(GapOnionMainGlobalParams *gpp)
 
   wgt = gpp->oni__checkbutton_auto_delete;
   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wgt), gpp->vin.auto_delete_before_save);
+  
 }
 
 static void
@@ -865,6 +953,8 @@ create_oni__dialog (GapOnionMainGlobalParams *gpp)
   GtkWidget *label9;
   GtkWidget *label10;
 
+  GtkWidget *oni__combo_active_mode;
+  GtkWidget *oni__combo_layermask_mode;
   GtkWidget *oni__combo_ref_mode;
   GtkWidget *oni__combo_select_mode;
 
@@ -886,7 +976,6 @@ create_oni__dialog (GapOnionMainGlobalParams *gpp)
   GtkWidget *oni__checkbutton_auto_replace;
   GtkWidget *oni__checkbutton_auto_delete;
 
-
   GtkWidget *oni__button_default;
   GtkWidget *dialog_action_area1;
   GtkWidget *oni__button_cancel;
@@ -1176,6 +1265,47 @@ create_oni__dialog (GapOnionMainGlobalParams *gpp)
                          , _("Descending opacity for 2nd onionskin layer")
                          , NULL);
 
+
+
+  tab1_row++;
+
+  label = gtk_label_new (_("Layermask Mode:"));
+  gtk_widget_show (label);
+  gtk_table_attach (GTK_TABLE (table1), label, 0, 1, tab1_row, tab1_row+1,
+                    (GtkAttachOptions) (GTK_FILL),
+                    (GtkAttachOptions) (0), 0, 0);
+  gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+
+
+  /* the layermask_mode  combo box */
+  oni__combo_layermask_mode
+    = gimp_int_combo_box_new (_("None"),                   0,
+                              _("Black (fully transparent)"),  1,
+                              _("White (fully opaque)"),  2,
+                              _("From Selection (in current image)"),  3,
+                              _("Clipped from Selection (in current image) "),  4,
+                              NULL);
+  gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (oni__combo_layermask_mode),
+                                 0,  /* initial gint value */
+                                 G_CALLBACK (on_oni__combo_layermask_mode),
+                                 gpp);
+
+  gtk_widget_show (oni__combo_layermask_mode);
+  gtk_table_attach (GTK_TABLE (table1), oni__combo_layermask_mode, 1, 3, tab1_row, tab1_row+1,
+                    (GtkAttachOptions) (GTK_FILL),
+                    (GtkAttachOptions) (0), 0, 0);
+  gimp_help_set_help_data(oni__combo_layermask_mode
+                         , _("Layermask creation for the onionskin layer(s):\n"
+                             " None: (create onionskin layer without layermask)\n"
+                             " Black (create onionskin layer with black layermask)\n"
+                             " White (create onionskin layer with white layermask)\n"
+                             " Selection (create layermask from selection in current image)\n"
+                             " Selection (create layermask from selection in current image) and clip layer 
to selection size")
+                         , NULL);
+
+
+
+
   tab1_row++;
 
 
@@ -1320,7 +1450,7 @@ create_oni__dialog (GapOnionMainGlobalParams *gpp)
   {
      GtkWidget *auto_table;
 
-     auto_table = gtk_table_new (1, 2, TRUE);
+     auto_table = gtk_table_new (1, 3, TRUE);
      gtk_widget_show (auto_table);
      gtk_table_attach (GTK_TABLE (table1), auto_table, 0, 3, tab1_row, tab1_row+1,
                       (GtkAttachOptions) (GTK_FILL),
@@ -1350,6 +1480,27 @@ create_oni__dialog (GapOnionMainGlobalParams *gpp)
                        1, 2, 0, 1,
                        (GtkAttachOptions) (GTK_FILL),
                        (GtkAttachOptions) (GTK_FILL), 0, 0);
+
+
+    /* the active_mode  combo box */
+    oni__combo_active_mode = gimp_int_combo_box_new (_("Keep active layer"),                   0,
+                              _("Set Onion layer active"),  1,
+                              _("Set Onion layermask active"),  2,
+                              NULL);
+    gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (oni__combo_active_mode),
+                                 0,  /* initial gint value */
+                                 G_CALLBACK (on_oni__combo_active_mode),
+                                 gpp);
+
+    gtk_widget_show (oni__combo_active_mode);
+    gtk_table_attach (GTK_TABLE (auto_table), oni__combo_active_mode, 2, 3, 0, 1,
+                    (GtkAttachOptions) (GTK_FILL),
+                    (GtkAttachOptions) (0), 0, 0);
+    gimp_help_set_help_data(oni__combo_active_mode
+                         , _("Handling of active layer after onion layer creation")
+                         , NULL);
+                         
+
   }
 
 
@@ -1465,6 +1616,7 @@ create_oni__dialog (GapOnionMainGlobalParams *gpp)
                       G_CALLBACK (on_oni__checkbutton_auto_delete_toggled),
                       gpp);
 
+
   if (oni__button_help)
     g_signal_connect (G_OBJECT (oni__button_help), "clicked",
                       G_CALLBACK (on_oni__button_help_clicked),
@@ -1489,11 +1641,12 @@ create_oni__dialog (GapOnionMainGlobalParams *gpp)
                       gpp);
 
 
-
   /* copy widget pointers to global parameter
    * (for use in callbacks outside of this procedure)
    */
   gpp->oni__entry_select_string         = oni__entry_select_string;
+  gpp->oni__combo_active_mode           = oni__combo_active_mode;
+  gpp->oni__combo_layermask_mode        = oni__combo_layermask_mode;
   gpp->oni__combo_ref_mode              = oni__combo_ref_mode;
   gpp->oni__combo_select_mode           = oni__combo_select_mode;
   gpp->oni__spinbutton_ignore_botlayers = oni__spinbutton_ignore_botlayers;
@@ -1529,6 +1682,8 @@ gap_onion_dlg_init_default_values(GapOnionMainGlobalParams *gpp)
           gpp->vin.auto_delete_before_save = FALSE;
 
           gpp->vin.num_olayers        = 2;
+          gpp->vin.layermask_mode     = gtab_layermask_modes[0]; /* none layermask mode */
+          gpp->vin.active_mode        = gtab_active_modes[0];  /* none keep active layer */
           gpp->vin.ref_mode           = gtab_ref_modes[0]; /* normal ref mode */
           gpp->vin.ref_delta          = -1;
           gpp->vin.ref_cycle          = FALSE;
@@ -1576,14 +1731,20 @@ gap_onion_dlg_onion_cfg_dialog(GapOnionMainGlobalParams *gpp)
   {
     gint32 l_ref_mode;
     gint32 l_select_mode;
+    gint32 l_layermask_mode;
+    gint32 l_active_mode;
 
     l_ref_mode = gpp->vin.ref_mode;
     l_select_mode = gpp->vin.select_mode;
+    l_layermask_mode = gpp->vin.layermask_mode;
+    l_active_mode = gpp->vin.active_mode;
 
     gpp->main_dialog = create_oni__dialog(gpp);
 
     gpp->vin.ref_mode = l_ref_mode;
     gpp->vin.select_mode = l_select_mode;
+    gpp->vin.layermask_mode = l_layermask_mode;
+    gpp->vin.active_mode = l_active_mode;
   }
 
   if(gap_debug) printf("gap_onion_dlg_onion_cfg_dialog: After create_oni__dialog\n");
diff --git a/gap/gap_onion_main.c b/gap/gap_onion_main.c
index bcf3d45..48bf41a 100644
--- a/gap/gap_onion_main.c
+++ b/gap/gap_onion_main.c
@@ -33,6 +33,7 @@
 
 
 /* revision history:
+ * version 2.8.xx;  2017/03/15   hof: added onionskin layermask_mode parameter
  * version 2.1.0a;  2004/06/03   hof: added onionskin ref_mode parameter
  * version 1.3.17a; 2003.07.29   hof: param types GimpPlugInInfo.run procedure
  * version 1.3.16c; 2003.07.12   hof: Onionsettings scope changes from gimp-session
@@ -100,6 +101,8 @@ GimpPlugInInfo PLUG_IN_INFO =
     {GIMP_PDB_INT32, "auto_create", "TRUE..automatic creation/replacing of onionskinlayers after GAP 
controlled load"},
     {GIMP_PDB_INT32, "auto_delete", "TRUE..automatic delete of onionskinlayers before GAP controlled save"},
     {GIMP_PDB_INT32, "ref_mode", "Reference Mode:  0:NORMAL, 1:BIDIRECTIONAL_SINGLE, 2:BIDIRECTIONAL_DOUBLE 
"},
+    {GIMP_PDB_INT32, "layermask_mode", "Layermask creation Mode:  0:NONE, 1:BLACK, 2:WHITE, 
3:FROM_SELECTION, 4: FROM_SELECTION_WITH_CLIPPING  "},
+    {GIMP_PDB_INT32, "active_mode", "0:do not switch the active layer, 1:set onion layer active after 
creation, 2: set onion layermask active"},
   };
   static int nargs_onion_cfg = G_N_ELEMENTS(args_onion_cfg);
 
@@ -329,6 +332,9 @@ run(const gchar *name
           gpp->vin.auto_delete_before_save = param[20].data.d_int32;
           gpp->vin.onionskin_auto_enable   = TRUE;
           gpp->vin.ref_mode          = param[21].data.d_int32;
+          gpp->vin.layermask_mode    = param[22].data.d_int32;
+          gpp->vin.active_mode = param[23].data.d_int32;
+          
         }
       }
       else if(gpp->run_mode != GIMP_RUN_INTERACTIVE)
diff --git a/gap/gap_onion_main.h b/gap/gap_onion_main.h
index bae7695..18649e2 100644
--- a/gap/gap_onion_main.h
+++ b/gap/gap_onion_main.h
@@ -120,6 +120,8 @@ typedef struct {
   GtkWidget *main_dialog;
 
   GtkWidget  *oni__entry_select_string;
+  GtkWidget  *oni__combo_active_mode;
+  GtkWidget  *oni__combo_layermask_mode;
   GtkWidget  *oni__combo_ref_mode;
   GtkWidget  *oni__combo_select_mode;
   GtkWidget  *oni__spinbutton_ignore_botlayers;
diff --git a/gap/gap_opacity_exposure_main.c b/gap/gap_opacity_exposure_main.c
new file mode 100644
index 0000000..858ef04
--- /dev/null
+++ b/gap/gap_opacity_exposure_main.c
@@ -0,0 +1,1554 @@
+/* gap_opacity_exposure_main.c
+ *  This filter sets the opacity in the specified layer
+ *  in a way that a combination in normal mode with the layer below
+ *  will give the best matching average brightness compared to a pecified target value
+ *  or a reference layer (of same size)
+ *
+ *  It is intended to adjust exposure in a series of timelapse frame images  
+ *  to compensate changing light conditions during the timelapse shooting period.
+ *  The frame images must contain 2 layers representing the same image in 
+ *  2 different brightness variants.
+ *  (typically created by 2 raw processing loops with different exposure settings)
+ *  
+ *  Note that only opaque pixels are involved in the comparison with the Reference layer.
+ *
+ *    by hof (Wolfgang Hofer)
+ *  2016/06/15
+ *
+ */
+/* 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, see
+ * <http://www.gnu.org/licenses/>.
+ */
+/* Revision history
+ *  (2011/12/01)  2.7.0       hof: created
+ */
+int gap_debug = 0;  /* 1 == print debug infos , 0 dont print debug infos */
+#define GAP_DEBUG_DECLARED 1
+
+
+#include "config.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <gtk/gtk.h>
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#include "gimplastvaldesc.h"
+#include "gap_arr_dialog.h"
+
+#include  "gap_libgapbase.h"
+
+#include "gap-intl.h"
+
+
+
+#define PLUG_IN_NAME        "gap-opacity-exposure"
+#define PLUG_IN_BINARY      "gap_opacity_exposure"
+#define PLUG_IN_PRINT_NAME  "Opacity Exposure"
+#define PLUG_IN_IMAGE_TYPES "RGB*"
+#define PLUG_IN_AUTHOR      "Wolfgang Hofer (hof gimp org)"
+#define PLUG_IN_COPYRIGHT   "Wolfgang Hofer"
+#define PLUG_IN_HELP_ID     "gap-opacity-exposure"
+
+
+#define OPACITY_LEVEL_UCHAR 50
+
+#define GAP_OECD_RESPONSE_RESET 1
+ 
+ 
+
+typedef struct Context {
+  gint32  involvedPixelCount;       /* number of (opaque) pixels involved in evaluation of summary values */
+  gdouble redSumRef;                /* summ of all Red involved pixels in the refDrawable   */
+  gdouble greenSumRef;              /* summ of all Green involved pixels in the refDrawable */
+  gdouble blueSumRef;               /* summ of all Blue involved pixels in the refDrawable  */
+  gdouble redSumUpper;
+  gdouble greenSumUpper;
+  gdouble blueSumUpper;
+  gdouble redSumLower;
+  gdouble greenSumLower;
+  gdouble blueSumLower;
+  
+  GimpDrawable *refDrawable;
+  GimpDrawable *upperDrawable;
+  GimpDrawable *lowerDrawable;
+  
+} Context; 
+
+typedef struct FilterValues {
+   gdouble    targetLum;              /* 0.0 to 100.0 percent */
+   gboolean   useRefLayerLum;
+   gboolean   useRefLayerMsk;
+   gint32     refLayerId;
+} FilterValues;
+
+typedef struct _OpacityExposureDialog OpacityExposureDialog;
+
+struct _OpacityExposureDialog
+{
+  gint          run;
+  gint          show_progress;
+  gint32        refLayerId;
+  gint32        upperLayerId;
+  gint32        lowerLayerId;
+  gint          countPotentialRefLayers;
+
+
+  GtkWidget       *shell;
+
+  GtkWidget       *okButton;
+  GtkWidget       *getLuminanceButton;
+  GtkWidget       *refLayerCombo;
+  GtkWidget       *refLayerLabel;
+
+  GtkObject       *targetLum_spinbutton_adj;
+  GtkWidget       *targetLum_spinbutton;
+  GtkWidget       *useRefLayerLumCheckbutton;
+  GtkWidget       *useRefLayerMskCheckbutton;
+
+  FilterValues   *vals;
+};
+
+
+
+
+static void  query (void);
+static void  run (const gchar *name,          /* name of plugin */
+     gint nparams,               /* number of in-paramters */
+     const GimpParam * param,    /* in-parameters */
+     gint *nreturn_vals,         /* number of out-parameters */
+     GimpParam ** return_vals);  /* out-parameters */
+
+static gdouble   p_getAverageLuminance(gint32 layerId);
+static void      p_adjustOpacityToMatchAverageReferenceBrightness(FilterValues *fiVals
+                   , gint32  upperDrawableId
+                   , gint32  lowerDrawableId
+                   );
+                   
+static void       p_color_channel_sum_regions (const GimpPixelRgn *refPR
+                    ,const GimpPixelRgn *upperPR
+                    ,const GimpPixelRgn *lowerPR
+                    ,Context *context);
+
+
+static void      p_mainDialogResonse (GtkWidget *widget,
+                    gint       response_id,
+                   OpacityExposureDialog *oecd);
+static void      p_layerMenuCallback(GtkWidget *widget, gint32 *reflayerId);
+static gint      p_refLayerConstrain(gint32 image_id, gint32 drawable_id, gpointer data);
+static void      on_gboolean_button_update (GtkWidget *widget, gpointer   data);
+static void      p_update_widget_sensitivity(OpacityExposureDialog *oecd);
+static void      p_init_widget_values (OpacityExposureDialog *oecd);
+static gboolean  p_dialog (FilterValues *fiVals, gint32 upperLayerId, gint32 lowerLayerId);
+static void      p_get_last_values(FilterValues *fiVals);
+static gint32    p_findLowerLayer(gint32 image_id, gint32 upperLayerId);
+
+static gdouble   p_calculateAvgLuminance(gint32 involvedPixelCount
+                            , gdouble redSum
+                            , gdouble greenSum
+                            , gdouble blueSum
+                            );
+static gint32    p_processing(gint32 image_id, gint32 upperLayerId, gboolean doProgress, FilterValues 
*fiVals);
+
+
+/* Global Variables */
+GimpPlugInInfo PLUG_IN_INFO =
+{
+  NULL,   /* init_proc  */
+  NULL,   /* quit_proc  */
+  query,  /* query_proc */
+  run     /* run_proc   */
+};
+
+
+FilterValues     fiVals;
+
+
+static const GimpParamDef in_args[] =
+{
+    { GIMP_PDB_INT32,    "run-mode",      "Interactive, non-interactive" },
+    { GIMP_PDB_IMAGE,    "image",         "Input image"                  },
+    { GIMP_PDB_DRAWABLE, "drawable",      "layer where opacity value shall be set in a way that combination 
in normal mode"
+                                          "with the layer below gives best matching average brightness 
compared to targetLuminance or to the refLayer"  },
+    { GIMP_PDB_FLOAT,    "targetLuminance", "desired average target luminance when merged down to the layer 
below in NORMAL mode 0.0 to 100.0 percent"},
+    { GIMP_PDB_INT32,    "useRefLum",     "TRUE (1): use average luminance of the opaque pixels in the 
reference layer instead of specified targetLuminance "},
+    { GIMP_PDB_INT32,    "useRefMsk",     "TRUE (1): use opaque pixels in the reference layer as mask for 
average lumninance calculation "},
+    { GIMP_PDB_DRAWABLE, "refLayerId",    "(optional) reference layer to be used as reference exposure 
(average brightness)"
+                                          "(note that the reference layer may be in another image)" }
+};
+
+
+
+static const GimpParamDef return_vals[] = {
+    { GIMP_PDB_DRAWABLE, "drawable",      "unused" }
+};
+
+static gint global_number_in_args = G_N_ELEMENTS (in_args);
+static gint global_number_out_args = G_N_ELEMENTS (return_vals);
+
+
+
+
+
+/* Functions */
+
+MAIN ()
+
+static void query (void)
+{
+  static GimpLastvalDef lastvals[] =
+  {
+    GIMP_LASTVALDEF_GDOUBLE     (GIMP_ITER_TRUE,   fiVals.targetLum,      "targetLum"),
+    GIMP_LASTVALDEF_GBOOLEAN    (GIMP_ITER_FALSE,  fiVals.useRefLayerLum, "useRefLayerLum"),
+    GIMP_LASTVALDEF_GBOOLEAN    (GIMP_ITER_FALSE,  fiVals.useRefLayerMsk, "useRefLayerMsk"),
+    GIMP_LASTVALDEF_DRAWABLE    (GIMP_ITER_FALSE,  fiVals.refLayerId,     "refLayerId"),
+  };
+
+  /* registration for last values buffer structure (useful for filter apply via frames_modify feature) */
+  gimp_lastval_desc_register(PLUG_IN_NAME,
+                             &fiVals,
+                             sizeof(fiVals),
+                             G_N_ELEMENTS (lastvals),
+                             lastvals);
+
+
+  gimp_plugin_domain_register (GETTEXT_PACKAGE, LOCALEDIR);
+
+
+  gimp_install_procedure (PLUG_IN_NAME,
+                          "Adjust layer opacity of a layer to match brightness with a given trget value or a 
reference layer.",
+                          "This filter operates on 2 layers in the layerstack, where "
+                          "the opacity of the upper layer is adjusted in a way that combination in normal 
mode with the layer below "
+                          "will have best possible match in average brightness with the specified 
targetLuminance or with a specified reference layer. "
+                          "For average brightness calculation only opaque pixels are taken into account."
+                          "The filter is intended to compensate changing exposure in a series of frame 
images"
+                          "for timelapse video sequences where each frame image has 2 layers showing the 
same image, "
+                          "but were converted from RAW fileformat with different exposure settings "
+                          "(that shall give darker and brighter exposure than the wanted exposure of the 
reference layer) "
+                          " ",
+                          PLUG_IN_AUTHOR,
+                          PLUG_IN_COPYRIGHT,
+                          GAP_VERSION_WITH_DATE,
+                          N_(PLUG_IN_PRINT_NAME),
+                          PLUG_IN_IMAGE_TYPES,
+                          GIMP_PLUGIN,
+                          global_number_in_args,
+                          global_number_out_args,
+                          in_args,
+                          return_vals);
+
+
+  {
+    /* Menu names */
+    const char *menupath = N_("<Image>/Video/Layer/Attributes/");
+
+    gimp_plugin_menu_register (PLUG_IN_NAME, menupath);
+  }
+
+}
+
+
+
+
+/* ---------------------------------
+ * p_color_channel_sum_regions
+ * ---------------------------------
+ * calculate summary Colorchanel values of pixels that are opaque in all 3 drawables 
+ * in the compared area region.
+ */
+static void
+p_color_channel_sum_regions (const GimpPixelRgn *refPR
+                  ,const GimpPixelRgn *upperPR
+                  ,const GimpPixelRgn *lowerPR
+                  ,Context *context)
+{
+  guint    row;
+  guchar*  ref = refPR->data;       /* the reference drawable */
+  guchar*  upper = upperPR->data;   /* the upper drawable */
+  guchar*  lower = lowerPR->data;   /* the lower drawable */
+  
+  guint    commonWidth;
+  guint    commonHeight;
+
+  commonWidth = MIN(MIN(upperPR->w, lowerPR->w), refPR->w);
+  commonHeight = MIN(MIN(upperPR->h, lowerPR->h), refPR->h);
+
+//   if(gap_debug)
+//   {
+//     printf("region REF x:%d y:%d w:%d h:%d  upper x:%d y:%d w:%d h:%d  Common w:%d h:%d px:%d py:%d\n"
+//        , (int)refPR->x
+//        , (int)refPR->y
+//        , (int)refPR->w
+//        , (int)refPR->h
+//        , (int)upperPR->x
+//        , (int)upperPR->y
+//        , (int)upperPR->w
+//        , (int)upperPR->h
+//        , (int)commonWidth
+//        , (int)commonHeight
+//        , (int)context->px
+//        , (int)context->py
+//        );
+//   }
+
+
+  for (row = 0; row < commonHeight; row++)
+  {
+    guint  col;
+    guint  idxref;
+    guint  idxupper;
+    guint  idxlower;
+    
+    idxref = 0;
+    idxupper = 0;
+    idxlower = 0;
+    for(col = 0; col < commonWidth; col++)
+    {
+      gboolean isInvolved;
+      
+      isInvolved = TRUE;
+      
+      if(refPR->bpp > 3)
+      {
+        if(ref[idxref +3] < OPACITY_LEVEL_UCHAR)
+        {
+          /* transparent reference pixel is not compared */
+          isInvolved = FALSE;
+        }
+      }
+
+      if(upperPR->bpp > 3)
+      {
+        if(upper[idxupper +3] < OPACITY_LEVEL_UCHAR)
+        {
+          /* transparent upper pixel is not compared */
+          isInvolved = FALSE;
+        }
+      }
+
+      if(lowerPR->bpp > 3)
+      {
+        if(lower[idxlower +3] < OPACITY_LEVEL_UCHAR)
+        {
+          /* transparent upper pixel is not compared */
+          isInvolved = FALSE;
+        }
+      }
+
+      if (isInvolved == TRUE)
+      {
+        context->involvedPixelCount += 1;
+        context->redSumRef   += ref[idxref];
+        context->greenSumRef += ref[idxref +1];
+        context->blueSumRef  += ref[idxref +2];
+        
+        context->redSumUpper   += upper[idxupper];
+        context->greenSumUpper += upper[idxupper +1];
+        context->blueSumUpper  += upper[idxupper +2];
+        
+        context->redSumLower   += lower[idxlower];
+        context->greenSumLower += lower[idxlower +1];
+        context->blueSumLower  += lower[idxlower +2];
+      }
+
+      idxref    += refPR->bpp;
+      idxupper += upperPR->bpp;
+      idxlower += lowerPR->bpp;
+    }
+
+
+    ref += refPR->rowstride;
+    upper += upperPR->rowstride;
+    lower += lowerPR->rowstride;
+
+  }
+
+}  /* end p_color_channel_sum_regions */
+
+
+/* ------------------------------------------------
+ * p_getAverageLuminance
+ * ------------------------------------------------
+ * get average luminance value of all opaque pixels
+ * in percent (0% for totally black image, 100% for totally white image)
+ */
+static gdouble
+p_getAverageLuminance(gint32  refDrawableId)
+{
+  Context contextData;
+  Context *context;
+
+  GimpPixelRgn refPR;
+  gpointer  pr;
+  gdouble avgLumRef;
+  
+  
+  /* init Context for full drawable area compare processing
+   * note that context is designed for the more complex locating procedures
+   * therefore most context elements are not used this time...
+   */
+  context = &contextData;
+  context->involvedPixelCount = 0;
+  context->redSumRef      = 0.0;
+  context->greenSumRef    = 0.0;   
+  context->blueSumRef     = 0.0;   
+  context->redSumUpper    = 0.0;
+  context->greenSumUpper  = 0.0;
+  context->blueSumUpper   = 0.0;
+  context->redSumLower    = 0.0;
+  context->greenSumLower  = 0.0;
+  context->blueSumLower   = 0.0;
+  
+  context->upperDrawable = NULL;
+  context->lowerDrawable = NULL;
+  context->refDrawable = gimp_drawable_get(refDrawableId);
+
+  gimp_pixel_rgn_init (&refPR, context->refDrawable
+                      , 0, 0      /* origin x, y top left corner*/
+                      , context->refDrawable->width, context->refDrawable->height
+                      , FALSE     /* dirty */
+                      , FALSE     /* shadow */
+                       );
+
+  /* calculate summary color channels of pixels that are opaque 
+   * (use use the same reference layer for all 3 drawables.
+   */
+  for (pr = gimp_pixel_rgns_register (1, &refPR);
+       pr != NULL;
+       pr = gimp_pixel_rgns_process (pr))
+  {
+    p_color_channel_sum_regions(&refPR, &refPR, &refPR, context);
+  }
+
+  avgLumRef = p_calculateAvgLuminance(context->involvedPixelCount
+                            , context->redSumRef
+                            , context->greenSumRef
+                            , context->blueSumRef
+                            );  
+
+  
+  if (context->involvedPixelCount < 0)
+  {
+    avgLumRef = 0;
+  }
+  
+  
+  if(context->refDrawable != NULL)
+  {
+    gimp_drawable_detach(context->refDrawable);
+  }
+
+  return (avgLumRef * 100.0);
+
+}  /* end p_getAverageLuminance */
+
+
+
+/* ------------------------------------------------
+ * p_adjustOpacityToMatchAverageReferenceBrightness
+ * ------------------------------------------------
+ * this procedure sets the opacity of the specified 
+ *    upperDrawableId (typically the layer on top of layerstack)
+ * in a way that a mix with the 
+ *    lowerDrawableId (typically the layer below)
+ * reults in best matching average luminance compared to the specified
+ *    targetLuminancee (in case useRefLayerLum is FALSE) value OR with the specified
+ *    refDrawableId (in case useRefLayerLum is TRUE ue the referencelayer average luminance as desired 
target)
+ * Note that only opaque pixels (opaque in all 3 drawables) are involved in the
+ * calculation of the average brightness (exposure) value.
+ *
+ * Typically all 3 drawables shall have same size (otherwise only the smallest area is processed)
+ * Further note that offsets of the layers within the image(s) are ignored for processing.
+ */
+void
+p_adjustOpacityToMatchAverageReferenceBrightness(FilterValues *fiVals
+  , gint32  upperDrawableId
+  , gint32  lowerDrawableId
+  )
+{
+  Context contextData;
+  Context *context;
+
+  GimpPixelRgn refPR;
+  GimpPixelRgn upperPR;
+  GimpPixelRgn lowerPR;
+  gpointer  pr;
+  gint      commonAreaWidth, commonAreaHeight;
+  gdouble   upperOpacity;
+  
+  gint32  refDrawableId;
+  
+  refDrawableId = -1;
+  if ((fiVals->useRefLayerLum) || (fiVals->useRefLayerMsk))
+  {
+    refDrawableId = fiVals->refLayerId;
+  }
+  
+  
+  
+  /* init Context for full drawable area compare processing
+   * note that context is designed for the more complex locating procedures
+   * therefore most context elements are not used this time...
+   */
+  context = &contextData;
+  context->involvedPixelCount = 0;
+  context->redSumRef      = 0.0;
+  context->greenSumRef    = 0.0;   
+  context->blueSumRef     = 0.0;   
+  context->redSumUpper    = 0.0;
+  context->greenSumUpper  = 0.0;
+  context->blueSumUpper   = 0.0;
+  context->redSumLower    = 0.0;
+  context->greenSumLower  = 0.0;
+  context->blueSumLower   = 0.0;
+  
+  context->upperDrawable = gimp_drawable_get(upperDrawableId);
+  context->lowerDrawable = gimp_drawable_get(lowerDrawableId);
+  context->refDrawable = NULL;
+  if ((fiVals->useRefLayerLum) || (fiVals->useRefLayerMsk))
+  {
+    context->refDrawable = gimp_drawable_get(refDrawableId);
+    commonAreaWidth = MIN(context->refDrawable->width, context->upperDrawable->width);
+    commonAreaHeight = MIN(context->refDrawable->height, context->upperDrawable->height);
+  }
+  else
+  {
+    commonAreaWidth = context->upperDrawable->width;
+    commonAreaHeight = context->upperDrawable->height;
+  }
+  
+
+
+  if(gap_debug)
+  {
+    printf ("p_adjustOpacityToMatchAverageReferenceBrightness refDrawableId:%d upperDrawableId:%d 
lowerDrawableId:%d \n"
+       ,(int)refDrawableId
+       ,(int)upperDrawableId
+       ,(int)lowerDrawableId
+      );
+  }
+
+  if ((commonAreaWidth <= 0) || (commonAreaHeight <= 0))
+  {
+    /* there is no common area size to process */
+    if(gap_debug)
+    {
+      printf ("p_adjustOpacityToMatchAverageReferenceBrightness common area size is 0 (DO NOTHING)\n");
+    }
+    return;
+  }  
+
+  if ((fiVals->useRefLayerLum) || (fiVals->useRefLayerMsk))
+  {
+    gimp_pixel_rgn_init (&refPR, context->refDrawable
+                      , 0, 0      /* origin x, y top left corner*/
+                      , commonAreaWidth, commonAreaHeight
+                      , FALSE     /* dirty */
+                      , FALSE     /* shadow */
+                       );
+  }
+  
+  gimp_pixel_rgn_init (&upperPR, context->upperDrawable
+                      , 0, 0      /* origin x, y top left corner*/
+                      , commonAreaWidth, commonAreaHeight
+                      , FALSE     /* dirty */
+                      , FALSE     /* shadow */
+                       );
+
+  gimp_pixel_rgn_init (&lowerPR, context->lowerDrawable
+                      , 0, 0      /* origin x, y top left corner*/
+                      , commonAreaWidth, commonAreaHeight
+                      , FALSE     /* dirty */
+                      , FALSE     /* shadow */
+                       );
+
+  if ((fiVals->useRefLayerLum) || (fiVals->useRefLayerMsk))
+  {
+    /* calculate summary color channels of pixels that are opaque in all 3 drawables.
+     */
+    for (pr = gimp_pixel_rgns_register (3, &refPR, &upperPR, &lowerPR);
+       pr != NULL;
+       pr = gimp_pixel_rgns_process (pr))
+    {
+      p_color_channel_sum_regions(&refPR, &upperPR, &lowerPR, context);
+    }
+  }
+  else
+  {
+    /* calculate summary color channels of pixels that are opaque in all 3 drawables.
+     */
+    for (pr = gimp_pixel_rgns_register (2, &upperPR, &lowerPR);
+       pr != NULL;
+       pr = gimp_pixel_rgns_process (pr))
+    {
+      /* use upperPR twice to fake the missing refLayer */
+      p_color_channel_sum_regions(&upperPR, &upperPR, &lowerPR, context);
+    }
+  }
+  
+  upperOpacity = 1.0;  /* assume show upper layer full opaque */
+
+  if(gap_debug)
+  {
+    printf ("p_adjustOpacityToMatchAverageReferenceBrightness involvedPixelCount:%d \n"
+            "   refsum(R:%f G:%f B:%f)\n"
+            "   uppsum(R:%f G:%f B:%f)\n"
+            "   lowsum(R:%f G:%f B:%f)\n"
+       ,(int)context->involvedPixelCount
+       ,(float)context->redSumRef
+       ,(float)context->greenSumRef
+       ,(float)context->blueSumRef
+       ,(float)context->redSumUpper
+       ,(float)context->greenSumUpper
+       ,(float)context->blueSumUpper
+       ,(float)context->redSumLower
+       ,(float)context->greenSumLower
+       ,(float)context->blueSumLower
+      );
+  }
+
+  
+  if (context->involvedPixelCount > 0)
+  {
+    gdouble avgLumRef;
+    gdouble avgLumUpper;
+    gdouble avgLumLower;
+
+    if (fiVals->useRefLayerLum)
+    {
+      avgLumRef = p_calculateAvgLuminance(context->involvedPixelCount
+                            , context->redSumRef
+                            , context->greenSumRef
+                            , context->blueSumRef
+                            );
+    }
+    else
+    {
+      /* get reference from the specified target percent value 
+       * (convert to range 0.0 - 1.0)
+       */
+      avgLumRef = fiVals->targetLum / 100.0;
+    }
+    
+    avgLumUpper = p_calculateAvgLuminance(context->involvedPixelCount
+                            , context->redSumUpper
+                            , context->greenSumUpper
+                            , context->blueSumUpper
+                            );
+    avgLumLower = p_calculateAvgLuminance(context->involvedPixelCount
+                            , context->redSumLower
+                            , context->greenSumLower
+                            , context->blueSumLower
+                            );
+    
+
+    if(gap_debug)
+    {
+      printf ("p_adjustOpacityToMatchAverageReferenceBrightness involvedPixelCount:%d \n"
+            "   refLUM: %f\n"
+            "   uppLUM: %f\n"
+            "   lowLUM: %f\n"
+       ,(int)context->involvedPixelCount
+       ,(float)avgLumRef
+       ,(float)avgLumUpper
+       ,(float)avgLumLower
+      );
+    }
+
+
+    
+    if (avgLumRef <= MIN(avgLumUpper, avgLumLower))
+    {
+      /* reference is darker than both layers */
+      
+      if (avgLumLower < avgLumUpper)
+      {
+        upperOpacity = 0.0; /* set upper transparent to show only the darker lower layer */
+      }
+    }
+    else if (avgLumRef >= MAX(avgLumUpper, avgLumLower))
+    {
+      /* reference is brighter than both layers */
+      if (avgLumLower > avgLumUpper)
+      {
+        upperOpacity = 0.0; /* set upper transparent to show only the brighter lower layer */
+      }
+    }
+    else
+    {
+      gdouble factor;
+      gdouble uppLUM;
+      gdouble lowLUM;
+      
+      uppLUM = MAX(avgLumUpper, avgLumLower);
+      lowLUM = MIN(avgLumUpper, avgLumLower);
+      
+      factor = 1.0 / (uppLUM - lowLUM);
+      upperOpacity = (avgLumRef - lowLUM) * factor;
+      
+    }
+
+  }
+  
+  
+  gimp_layer_set_opacity(upperDrawableId, CLAMP(upperOpacity * 100.0, 0.0, 100.0));
+  
+  if(context->refDrawable != NULL)
+  {
+    gimp_drawable_detach(context->refDrawable);
+  }
+  if(context->upperDrawable != NULL)
+  {
+    gimp_drawable_detach(context->upperDrawable);
+  }
+  if(context->lowerDrawable != NULL)
+  {
+    gimp_drawable_detach(context->lowerDrawable);
+  }
+
+
+}  /* end p_adjustOpacityToMatchAverageReferenceBrightness */
+
+
+
+/* ---------------------------------
+ * p_mainDialogResonse
+ * ---------------------------------
+ */
+static void
+p_mainDialogResonse (GtkWidget *widget,
+                 gint       response_id,
+                 OpacityExposureDialog *oecd)
+{
+  GtkWidget *dialog;
+
+  switch (response_id)
+  {
+    case GAP_OECD_RESPONSE_RESET:
+      if(oecd)
+      {
+        if(oecd->refLayerId >= 0)
+        {
+          gdouble targetLum;
+          targetLum = p_getAverageLuminance(oecd->refLayerId);
+          gtk_adjustment_set_value(GTK_ADJUSTMENT(oecd->targetLum_spinbutton_adj), (gfloat)targetLum);
+        }
+      }
+      break;
+
+    case GTK_RESPONSE_OK:
+      if(oecd)
+      {
+        if (GTK_WIDGET_VISIBLE (oecd->shell))
+        {
+          gtk_widget_hide (oecd->shell);
+        }
+        oecd->run = TRUE;
+      }
+
+    default:
+      dialog = NULL;
+      if(oecd)
+      {
+        dialog = oecd->shell;
+        if(dialog)
+        {
+          oecd->shell = NULL;
+          gtk_widget_destroy (dialog);
+        }
+      }
+      gtk_main_quit ();
+      break;
+  }
+}  /* end p_mainDialogResonse */
+
+
+/* ------------------------------
+ * p_layerMenuCallback
+ * ------------------------------
+ *
+ */
+static void
+p_layerMenuCallback(GtkWidget *widget, gint32 *reflayerId)
+{
+  gint value;
+
+  gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &value);
+
+  if(gap_debug)
+  {
+    printf("p_layerMenuCallback: cloudLayerAddr:%ld value:%d\n"
+      ,(long)reflayerId
+      ,(int)value
+      );
+  }
+
+  if(reflayerId != NULL)
+  {
+    *reflayerId = value;
+  }
+
+
+} /* end p_layerMenuCallback */
+
+/* ------------------------------
+ * p_refLayerConstrain
+ * ------------------------------
+ *
+ */
+static gint
+p_refLayerConstrain(gint32 image_id, gint32 drawable_id, gpointer data)
+{
+  OpacityExposureDialog *oecd;
+  
+  oecd = (OpacityExposureDialog *)data;
+
+  if(gap_debug)
+  {
+    printf("p_refLayerConstrain PROCEDURE image_id:%d drawable_id:%d oecd:%ld  countPotentialRefLayers:%d\n"
+                          ,(int)image_id
+                          ,(int)drawable_id
+                          ,(long)oecd
+                          ,(int)oecd->countPotentialRefLayers
+                          );
+  }
+
+  if(drawable_id < 0)
+  {
+     /* gimp 1.1 makes a first call of the constraint procedure
+      * with drawable_id = -1, and skips the whole image if FALSE is returned
+      */
+     return(TRUE);
+  }
+
+  if(gimp_item_is_valid(drawable_id) != TRUE)
+  {
+     return(FALSE);
+  }
+
+  /* the processed layers are not usable as reference */
+  if((drawable_id == oecd->upperLayerId)
+  || (drawable_id == oecd->lowerLayerId))
+  {
+    return (FALSE);
+  }
+
+  if(!gimp_drawable_is_rgb(drawable_id))
+  {
+    return (FALSE);
+  }
+
+  oecd->countPotentialRefLayers++;
+  if(gap_debug)
+  {
+    printf("p_refLayerConstrain PROCEDURE image_id:%d drawable_id:%d OK layer accepted  
countPotentialRefLayers:%d\n"
+                          ,(int)image_id
+                          ,(int)drawable_id
+                          ,(int)oecd->countPotentialRefLayers
+                          );
+  }
+
+  return(TRUE);
+
+} /* end p_refLayerConstrain */
+
+
+/* --------------------------------------
+ * on_gboolean_button_update
+ * --------------------------------------
+ */
+static void
+on_gboolean_button_update (GtkWidget *widget,
+                           gpointer   data)
+{
+  OpacityExposureDialog *oecd;
+  gint *toggle_val = (gint *) data;
+
+  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
+  {
+    *toggle_val = TRUE;
+  }
+  else
+  {
+    *toggle_val = FALSE;
+  }
+  gimp_toggle_button_sensitive_update (GTK_TOGGLE_BUTTON (widget));
+
+  oecd = (OpacityExposureDialog *) g_object_get_data (G_OBJECT (widget), "oecd");
+  if(oecd != NULL)
+  {
+    p_update_widget_sensitivity (oecd);
+  }
+}
+
+/* --------------------------------------
+ * p_update_widget_sensitivity
+ * --------------------------------------
+ */
+static void
+p_update_widget_sensitivity (OpacityExposureDialog *oecd)
+{
+  if(oecd == NULL)
+  {
+    return;
+  }
+  if(oecd->vals == NULL)
+  {
+    return;
+  }
+
+  if (oecd->refLayerId >= 0)
+  {
+     gtk_widget_set_sensitive(oecd->getLuminanceButton, TRUE);
+  }
+  else
+  {
+     gtk_widget_set_sensitive(oecd->getLuminanceButton, FALSE);
+  }
+  
+  if (oecd->vals->useRefLayerLum == TRUE)
+  {
+     gtk_widget_set_sensitive(oecd->targetLum_spinbutton ,  FALSE);
+  }
+  else
+  {
+     gtk_widget_set_sensitive(oecd->targetLum_spinbutton ,  TRUE);
+  }
+
+}  /* end p_update_widget_sensitivity */
+
+
+/* --------------------------------------
+ * p_init_widget_values
+ * --------------------------------------
+ */
+static void
+p_init_widget_values (OpacityExposureDialog *oecd)
+{
+  if(oecd == NULL)
+  {
+    return;
+  }
+  if(oecd->vals == NULL)
+  {
+    return;
+  }
+
+  /* init spnbuttons */
+  gtk_adjustment_set_value(GTK_ADJUSTMENT(oecd->targetLum_spinbutton_adj)
+                         , (gfloat)oecd->vals->targetLum);
+
+  /* init checkbuttons */
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (oecd->useRefLayerLumCheckbutton)
+                               , oecd->vals->useRefLayerLum);
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (oecd->useRefLayerMskCheckbutton)
+                               , oecd->vals->useRefLayerMsk);
+
+  
+}  /* end p_init_widget_values */
+
+
+
+/* ------------------------------
+ * p_dialog
+ * ------------------------------
+ * create and show the dialog window
+ * return TRUE in OK was selected.
+ */
+static gboolean
+p_dialog (FilterValues *fiVals, gint32 upperLayerId, gint32 lowerLayerId)
+{
+  OpacityExposureDialog  oecDialog;
+  OpacityExposureDialog *oecd;
+  GtkWidget  *vbox;
+
+  GtkWidget *dialog1;
+  GtkWidget *dialog_vbox1;
+  GtkWidget *frame1;
+  GtkWidget *vbox1;
+  GtkWidget *label;
+  GtkWidget *table1;
+  GtkWidget *dialog_action_area1;
+  GtkWidget *combo;
+  GtkWidget *checkbutton;
+  GtkObject *spinbutton_adj;
+  GtkWidget *spinbutton;
+  gint       row;
+  gboolean   errorCount;
+
+
+  oecd = &oecDialog;
+
+  /* Init UI  */
+  gimp_ui_init ("waterpattern", FALSE);
+
+
+  /*  The dialog1  */
+  oecd->run = FALSE;
+  oecd->vals = fiVals;
+  oecd->refLayerId = -1;
+  oecd->upperLayerId = upperLayerId;
+  oecd->lowerLayerId = lowerLayerId;
+  oecd->countPotentialRefLayers = 0;
+  errorCount = 0;
+
+  if(gap_debug)
+  {
+    printf("p_dialog START upperLayerId:%d lowerLayerId:%d\n"
+      ,(int)upperLayerId
+      ,(int)lowerLayerId
+      );
+  }
+
+
+  /*  The dialog1 and main vbox  */
+  dialog1 = gimp_dialog_new (_("Opacity Exposure"), "opacity exposure",
+                               NULL, 0,
+                               gimp_standard_help_func, PLUG_IN_HELP_ID,
+
+                               GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+                               NULL);
+
+  oecd->shell = dialog1;
+  oecd->getLuminanceButton = gimp_dialog_add_button (GIMP_DIALOG(dialog1), GIMP_STOCK_RESET, 
GAP_OECD_RESPONSE_RESET);
+  gimp_help_set_help_data(oecd->getLuminanceButton,
+                       _("Get Average Luminance From the reference layer")
+                       , NULL);
+  oecd->okButton = gimp_dialog_add_button (GIMP_DIALOG(dialog1), GTK_STOCK_OK, GTK_RESPONSE_OK);
+
+
+  g_signal_connect (G_OBJECT (dialog1), "response",
+                      G_CALLBACK (p_mainDialogResonse),
+                      oecd);
+
+  /* the vbox */
+  vbox = gtk_vbox_new (FALSE, 2);
+  gtk_container_set_border_width (GTK_CONTAINER (vbox), 2);
+  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog1)->vbox), vbox,
+                      TRUE, TRUE, 0);
+  gtk_widget_show (vbox);
+
+  dialog_vbox1 = GTK_DIALOG (dialog1)->vbox;
+  g_object_set_data (G_OBJECT (dialog1), "dialog_vbox1", dialog_vbox1);
+  gtk_widget_show (dialog_vbox1);
+
+
+  /* the frame */
+  frame1 = gimp_frame_new (_("Options"));
+
+  gtk_widget_show (frame1);
+  gtk_box_pack_start (GTK_BOX (dialog_vbox1), frame1, TRUE, TRUE, 0);
+  gtk_container_set_border_width (GTK_CONTAINER (frame1), 2);
+
+  vbox1 = gtk_vbox_new (FALSE, 0);
+  gtk_container_add (GTK_CONTAINER (frame1), vbox1);
+  gtk_widget_show (vbox1);
+
+
+  /* add label that describes how to use this filter */
+  label = gtk_label_new (_("This filter adjust opacity of a layer\n"
+                           "in a way that the combination with the layer below\n"
+                           "matches the brightness of a reference layer)"));
+  gtk_box_pack_start (GTK_BOX (vbox1), label, TRUE, TRUE, 0);
+  gtk_widget_show (label);
+
+
+  /* table1  */
+  table1 = gtk_table_new (4, 2, FALSE);
+  gtk_widget_show (table1);
+  gtk_box_pack_start (GTK_BOX (vbox1), table1, TRUE, TRUE, 0);
+  gtk_container_set_border_width (GTK_CONTAINER (table1), 4);
+
+
+  row = 0;
+
+  label = gtk_label_new (_("Target Luminance:"));
+  gtk_widget_show (label);
+  gtk_table_attach (GTK_TABLE (table1), label, 0, 1, row, row+1,
+                    (GtkAttachOptions) (GTK_FILL),
+                    (GtkAttachOptions) (0), 0, 0);
+  gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+
+  /* average luminance spinbutton  */
+  spinbutton_adj = gtk_adjustment_new (oecd->vals->targetLum, 0.0, 100.0, 1.0, 10, 0);
+  spinbutton = gtk_spin_button_new (GTK_ADJUSTMENT (spinbutton_adj), 1, 2);
+  gimp_help_set_help_data (spinbutton, _("Target Average Luminance (when merged with layer below in NORMAL 
mode)"), NULL);
+  gtk_widget_show (spinbutton);
+  gtk_table_attach (GTK_TABLE (table1), spinbutton, 1, 2, row, row+1,
+                    (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+                    (GtkAttachOptions) (0), 0, 0);
+  g_signal_connect (G_OBJECT (spinbutton_adj), "value_changed", G_CALLBACK (gimp_double_adjustment_update), 
&oecd->vals->targetLum);
+  oecd->targetLum_spinbutton_adj = spinbutton_adj;
+  oecd->targetLum_spinbutton = spinbutton;
+
+  row++;
+
+  /* use reference layer's average luminance checkbutton  */
+  label = gtk_label_new (_("Use RefLayer:"));
+  gtk_widget_show (label);
+  gtk_table_attach (GTK_TABLE (table1), label, 0, 1, row, row+1,
+                    (GtkAttachOptions) (GTK_FILL),
+                    (GtkAttachOptions) (0), 0, 0);
+  gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+
+
+  checkbutton = gtk_check_button_new_with_label (" ");
+  g_object_set_data (G_OBJECT (checkbutton), "oecd", oecd);
+  oecd->useRefLayerLumCheckbutton = checkbutton;
+  gtk_widget_show (checkbutton);
+  gimp_help_set_help_data (checkbutton, _("ON: use Average Luminance from opaque pixels in the reference 
layer (ignore Target) OFF: use specified Target Lumninance value"), NULL);
+  gtk_table_attach( GTK_TABLE(table1), checkbutton, 1, 2, row, row+1,
+                    GTK_FILL, 0, 0, 0 );
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (checkbutton), oecd->vals->useRefLayerLum);
+  g_signal_connect (checkbutton, "toggled",
+                    G_CALLBACK (on_gboolean_button_update),
+                    &oecd->vals->useRefLayerLum);
+
+  row++;
+
+  /* use reference layer as mask checkbutton  */
+  label = gtk_label_new (_("Use RefLayer as Mask:"));
+  gtk_widget_show (label);
+  gtk_table_attach (GTK_TABLE (table1), label, 0, 1, row, row+1,
+                    (GtkAttachOptions) (GTK_FILL),
+                    (GtkAttachOptions) (0), 0, 0);
+  gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+
+
+  checkbutton = gtk_check_button_new_with_label (" ");
+  g_object_set_data (G_OBJECT (checkbutton), "oecd", oecd);
+  oecd->useRefLayerMskCheckbutton = checkbutton;
+  gtk_widget_show (checkbutton);
+  gimp_help_set_help_data (checkbutton, _("ON: use the opaque pixels of reference layer as mask "), NULL);
+  gtk_table_attach( GTK_TABLE(table1), checkbutton, 1, 2, row, row+1,
+                    GTK_FILL, 0, 0, 0 );
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (checkbutton), oecd->vals->useRefLayerMsk);
+  g_signal_connect (checkbutton, "toggled",
+                    G_CALLBACK (on_gboolean_button_update),
+                    &oecd->vals->useRefLayerMsk);
+
+
+  row++;
+
+  /* the reference layer label and combo */
+  label = gtk_label_new (_("Exposure Reference Layer"));
+  oecd->refLayerLabel = label;
+  gtk_widget_show (label);
+  gtk_table_attach (GTK_TABLE (table1), label, 0, 1, row, row+1,
+                    (GtkAttachOptions) (GTK_FILL),
+                    (GtkAttachOptions) (0), 0, 0);
+  gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+
+  combo = gimp_layer_combo_box_new (p_refLayerConstrain, oecd);
+  oecd->refLayerCombo = combo;
+  gtk_widget_show(combo);
+  gtk_table_attach(GTK_TABLE(table1), combo, 1, 4, row, row+1,
+                   GTK_EXPAND | GTK_FILL, 0, 0, 0);
+
+  gimp_help_set_help_data(combo,
+                       _("Select a reference layer")
+                       , NULL);
+
+  gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo),
+                              oecd->refLayerId,                      /* initial value */
+                              G_CALLBACK (p_layerMenuCallback),
+                              &oecd->refLayerId);
+
+
+
+  dialog_action_area1 = GTK_DIALOG (dialog1)->action_area;
+  gtk_widget_show (dialog_action_area1);
+  gtk_container_set_border_width (GTK_CONTAINER (dialog_action_area1), 10);
+
+
+  /* check required conditions to run this filter 
+   * and add warning texts if not fulfilled.
+   */
+  if (oecd->countPotentialRefLayers < 1)
+  {
+     /* disable get Luminance button */
+     gtk_widget_set_sensitive(oecd->getLuminanceButton, FALSE);
+
+     label = gtk_label_new (_("Warning: no reference layer found \n"
+                              "(open a ref image in gimp session)"));
+     gtk_box_pack_start (GTK_BOX (vbox1), label, TRUE, TRUE, 0);
+     gtk_widget_show (label);
+     errorCount++;
+  }
+  if (oecd->lowerLayerId < 0)
+  {
+     label = gtk_label_new (_("Warning: no layer found below processed layer\n"
+                              "(add the lower layer with other exposure settings)"));
+     gtk_box_pack_start (GTK_BOX (vbox1), label, TRUE, TRUE, 0);
+     gtk_widget_show (label);
+     errorCount++;
+  }
+  
+  if (errorCount > 0)
+  {
+    /* disable OK button */
+    gtk_widget_set_sensitive(oecd->okButton, FALSE);
+  }
+
+
+  p_init_widget_values (oecd);
+  p_update_widget_sensitivity (oecd);
+
+
+  gtk_widget_show (dialog1);
+
+  gtk_main ();
+  gdk_flush ();
+
+  oecd->vals->refLayerId = oecd->refLayerId;
+
+  if (errorCount > 0)
+  {
+    return (FALSE);
+  }
+  return (oecd->run);
+
+}  /* end p_dialog */
+
+/* -----------------------------------
+ * p_get_last_values
+ * -----------------------------------
+ */
+static void
+p_get_last_values(FilterValues *fiVals)
+{
+  int l_len;
+
+  /* init default values */
+  fiVals->targetLum             = 50.0; /* 50 percent */
+  fiVals->useRefLayerLum        = FALSE;
+  fiVals->useRefLayerMsk        = FALSE;
+  fiVals->refLayerId            = -1;
+
+  l_len = gimp_get_data_size (PLUG_IN_NAME);
+  if (l_len == sizeof(FilterValues))
+  {
+    /* Possibly retrieve data from a previous interactive run */
+    gimp_get_data (PLUG_IN_NAME, fiVals);
+
+    if(gap_debug)
+    {
+      printf("p_get_last_values FOUND data for key:%s\n"
+        , PLUG_IN_NAME
+        );
+    }
+  }
+
+  if(gap_debug)
+  {
+    printf("p_get_last_values: refLayerId:%d \n"
+            , (int)fiVals->refLayerId
+            );
+  }
+
+}  /* end p_get_last_values */
+
+
+/* -----------------------------
+ * p_findLowerLayer
+ * -----------------------------
+ */
+static gint32
+p_findLowerLayer(gint32 image_id, gint32 upperLayerId)
+{
+  gint32  lowerLayerId;
+  gint      l_idx;
+  gint      l_nlayers;
+  gint32   *l_layers_list;
+  
+  lowerLayerId = -1;
+  
+  /* find the layer below upperLayerId 
+   * (TODO curent implementation does not support the case where 
+   *  where upperLayerId is not on top image level but placed somewhere in nested layergroups)
+   */
+  l_layers_list = gimp_image_get_layers(image_id, &l_nlayers);
+  if(l_layers_list != NULL)
+  {
+    for(l_idx = 0; l_idx < l_nlayers; l_idx++)
+    {
+      if (l_layers_list[l_idx] == upperLayerId)
+      {
+        if (l_idx < l_nlayers -1)
+        {
+          lowerLayerId = l_layers_list[l_idx +1];
+        }
+        break;
+      }
+    }
+    g_free(l_layers_list);
+  }
+
+  return (lowerLayerId);
+  
+}  /* end p_findLowerLayer */
+
+/* ---------------------------------
+ * p_calculateAvgLuminance
+ * ---------------------------------
+ * returns difference of 2 colors as gdouble value
+ * in range 0.0 (exact match) to 1.0 (maximal difference)
+ */
+static gdouble
+p_calculateAvgLuminance(gint32 involvedPixelCount
+                            , gdouble redSum
+                            , gdouble greenSum
+                            , gdouble blueSum)
+{
+  GimpRGB rgb;
+  gdouble count;
+
+  if (involvedPixelCount > 0)
+  {
+    gdouble max, min;
+    gdouble lum;
+
+    count = involvedPixelCount;
+    rgb.r = CLAMP(((redSum / count) / 255.0),   0.0, 1.0);
+    rgb.g = CLAMP(((greenSum / count) / 255.0), 0.0, 1.0);
+    rgb.b = CLAMP(((blueSum / count) / 255.0),  0.0, 1.0);
+    rgb.a = 1.0;
+ 
+
+
+    max = gimp_rgb_max (&rgb);
+    min = gimp_rgb_min (&rgb);
+
+    lum = (max + min) / 2.0;
+    
+    if(gap_debug)
+    {
+      printf("Avg R:%f G:%f B:%f   lum:%f\n"
+        ,(float)rgb.r
+        ,(float)rgb.g
+        ,(float)rgb.b
+        ,(float)lum
+        );
+    }
+    
+
+    return (lum);
+  }
+  
+  return (0.0);
+
+}  /* end p_calculateAvgLuminance */
+
+/* -----------------------------
+ * p_processing
+ * -----------------------------
+ * find and check required layers and start processing
+ * or return -1 in case checks failed.
+ * return upperLayerId in case of success.
+ */
+static gint32
+p_processing(gint32 image_id, gint32 upperLayerId, gboolean doProgress, FilterValues *fiVals)
+{
+  gint32  lowerLayerId;
+  
+  lowerLayerId = p_findLowerLayer(image_id, upperLayerId);
+  if (lowerLayerId < 0)
+  {
+    printf("Error: no layer found below layerid: %d in the layerstack\n", (int)upperLayerId );
+    return(-1);
+  }
+  
+  if ((fiVals->refLayerId < 0) && (fiVals->useRefLayerLum == TRUE))
+  {
+    printf("Error: no valid reference layer available\n");
+    return(-1);
+  }
+  if ((fiVals->refLayerId < 0) && (fiVals->useRefLayerMsk == TRUE))
+  {
+    printf("Error: no valid reference layer available\n");
+    return(-1);
+  }
+  
+  p_adjustOpacityToMatchAverageReferenceBrightness(fiVals
+                , upperLayerId
+                , lowerLayerId
+                );
+  
+  return (upperLayerId);
+  
+}  /* end p_processing */
+
+
+/* -------
+ * run
+ * -------
+ * this procedure is invoked by the GIMP to run the plug-in.
+ */
+static void
+run (const gchar *name,          /* name of plugin */
+     gint nparams,               /* number of in-paramters */
+     const GimpParam * param,    /* in-parameters */
+     gint *nreturn_vals,         /* number of out-parameters */
+     GimpParam ** return_vals)   /* out-parameters */
+{
+  const gchar *l_env;
+  gint32       image_id = -1;
+  gint32       upperLayerId = -1;
+  gboolean doProgress;
+  gboolean doFlush;
+  GapLastvalAnimatedCallInfo  animCallInfo;
+
+
+  /* Get the runmode from the in-parameters */
+  GimpRunMode run_mode = param[0].data.d_int32;
+
+  /* status variable, use it to check for errors in invocation usualy only
+     during non-interactive calling */
+  GimpPDBStatusType status = GIMP_PDB_SUCCESS;
+
+  /* always return at least the status to the caller. */
+  static GimpParam values[2];
+
+  INIT_I18N();
+
+  l_env = g_getenv("GAP_DEBUG");
+  if(l_env != NULL)
+  {
+    if((*l_env != 'n') && (*l_env != 'N')) gap_debug = 1;
+  }
+
+  if(gap_debug)
+  {
+    printf("\n\nDEBUG: run %s\n", name);
+  }
+
+
+  doProgress = FALSE;
+  doFlush = FALSE;
+
+  /* initialize the return of the status */
+  values[0].type = GIMP_PDB_STATUS;
+  values[0].data.d_status = status;
+  values[1].type = GIMP_PDB_DRAWABLE;
+  values[1].data.d_drawable = -1;
+  *nreturn_vals = 2;
+  *return_vals = values;
+
+  /* init default values and Possibly retrieve data from a previous interactive run */
+  p_get_last_values(&fiVals);   
+
+  /* get image and drawable */
+  image_id = param[1].data.d_int32;
+  upperLayerId = param[2].data.d_int32;
+
+
+  /* how are we running today? */
+  switch (run_mode)
+  {
+    case GIMP_RUN_INTERACTIVE:
+      if(strcmp(name, PLUG_IN_NAME) ==0)
+      {
+        gboolean dialogOk;
+        gint32 lowerLayerId;
+        
+        lowerLayerId = p_findLowerLayer(image_id, upperLayerId);
+
+        dialogOk = p_dialog(&fiVals, upperLayerId, lowerLayerId);
+        if( dialogOk != TRUE)
+        {
+          status = GIMP_PDB_CALLING_ERROR;
+        }
+
+      }
+      doProgress = TRUE;
+      doFlush =  TRUE;
+      break;
+
+    case GIMP_RUN_NONINTERACTIVE:
+      /* check to see if invoked with the correct number of parameters */
+      if (nparams == global_number_in_args)
+      {
+          fiVals.targetLum                = param[3].data.d_float;;
+          fiVals.useRefLayerLum           = param[4].data.d_int32;
+          fiVals.useRefLayerMsk           = param[5].data.d_int32;
+          fiVals.refLayerId               = param[6].data.d_int32;
+      }
+      else
+      {
+        status = GIMP_PDB_CALLING_ERROR;
+      }
+
+      break;
+
+    case GIMP_RUN_WITH_LAST_VALS:
+      animCallInfo.animatedCallInProgress = FALSE;
+      gimp_get_data(GAP_LASTVAL_KEY_ANIMATED_CALL_INFO, &animCallInfo);
+
+      if(animCallInfo.animatedCallInProgress != TRUE)
+      {
+        doProgress = TRUE;
+        doFlush =  TRUE;
+      }
+      break;
+
+    default:
+      break;
+  }
+
+  if (status == GIMP_PDB_SUCCESS)
+  {
+    gimp_image_undo_group_start (image_id);
+
+    /* Run the main function */
+    values[1].data.d_drawable =
+          p_processing(image_id
+                      , upperLayerId  /* the layer wher opacity is to be set */
+                      , doProgress
+                      , &fiVals
+                      );
+
+    gimp_image_undo_group_end (image_id);
+
+    if(run_mode == GIMP_RUN_INTERACTIVE)
+    {
+      gimp_set_data (PLUG_IN_NAME, &fiVals, sizeof(fiVals));
+    }
+
+
+    if (values[1].data.d_drawable < 0)
+    {
+       status = GIMP_PDB_CALLING_ERROR;
+    }
+
+    /* If run mode is interactive, flush displays, else (script) don't
+     * do it, as the screen updates would make the scripts slow
+     */
+    if (doFlush)
+    {
+      gimp_displays_flush ();
+    }
+
+
+  }
+  values[0].data.d_status = status;
+
+}       /* end run */
+
+
diff --git a/gap/gap_vex_exec.c b/gap/gap_vex_exec.c
index 843a414..089c591 100644
--- a/gap/gap_vex_exec.c
+++ b/gap/gap_vex_exec.c
@@ -107,7 +107,7 @@ p_frame_postprocessing(t_GVA_Handle   *gvahand
   if (gpp->val.generate_alpha_via_bluebox == TRUE)
   {
     if ((gpp->val.extract_with_layermask == TRUE)
-    || (gpp->val.extract_with_layermask == TRUE))
+    || (gpp->val.extract_alpha_as_gray_frames == TRUE))
     {
       l_bbox_layer_id = gimp_layer_copy(gvahand->layer_id);
       gimp_image_insert_layer(gvahand->image_id, l_bbox_layer_id, 0, -1);
@@ -134,7 +134,12 @@ p_frame_postprocessing(t_GVA_Handle   *gvahand
       {
         printf("GRAY created layermask_id:%d\n", l_layermask_id);
       }
+      gimp_layer_add_mask(l_bbox_layer_id, l_layermask_id);
       gap_layer_copy_paste_drawable(gvahand->image_id, gvahand->layer_id, l_layermask_id);
+      /* insert the graymask above gvahand->layer_id normal mode and merge_down */
+      //gimp_image_set_active_layer (gvahand->image_id, gvahand->layer_id);
+      //gimp_image_insert_layer(gvahand->image_id, l_layermask_id, 0, -1);  /* 0, -1 is insert above the 
active layer */
+      //gvahand->layer_id = gimp_image_merge_down(gvahand->image_id, l_layermask_id, 
GIMP_EXPAND_AS_NECESSARY);
     }
     else if (gpp->val.extract_with_layermask == TRUE)
     {
diff --git a/gap/gap_vin.c b/gap/gap_vin.c
index 985a77f..4b088c9 100644
--- a/gap/gap_vin.c
+++ b/gap/gap_vin.c
@@ -23,6 +23,7 @@
  */
 
 /* revision history:
+ * version 2.8.xx;  2017/03/15  hof: added onionskin setting layermask_mode
  * version 2.1.0a;  2004/06/03  hof: added onionskin setting ref_mode
  * version 1.3.25b; 2004/01/23  hof: bugfix: gap_val_load_textfile set correct line_nr
  * version 1.3.18b; 2003/08/23  hof: gap_vin_get_all: force timezoom value >= 1 (0 results in divison by 
zero)
@@ -85,6 +86,8 @@ p_set_onion_keywords(GapValKeyList *keylist, GapVinVideoInfo *vin_ptr)
    gap_val_set_keyword(keylist, "(onion_select_invert ", &vin_ptr->select_invert, GAP_VAL_GBOOLEAN, 0, "\0");
    gap_val_set_keyword(keylist, "(onion_select_string ", &vin_ptr->select_string[0], GAP_VAL_STRING, 
sizeof(vin_ptr->select_string), "\0");
    gap_val_set_keyword(keylist, "(onion_ascending_opacity ", &vin_ptr->asc_opacity, GAP_VAL_GBOOLEAN, 0, 
"\0");
+   gap_val_set_keyword(keylist, "(onion_layermask_mode ", &vin_ptr->layermask_mode, GAP_VAL_GINT32, 0, "\0");
+   gap_val_set_keyword(keylist, "(onion_active_mode ", &vin_ptr->active_mode, GAP_VAL_GINT32, 0, "\0");
 }  /* end p_set_onion_keywords */
 
 
@@ -163,6 +166,7 @@ gap_vin_get_all_keylist(GapValKeyList *keylist, GapVinVideoInfo *vin_ptr, char *
     vin_ptr->auto_replace_after_load = FALSE;
     vin_ptr->auto_delete_before_save = FALSE;
 
+    vin_ptr->ref_mode           = 0;
     vin_ptr->num_olayers        = 2;
     vin_ptr->ref_delta          = -1;
     vin_ptr->ref_cycle          = FALSE;
@@ -175,7 +179,9 @@ gap_vin_get_all_keylist(GapValKeyList *keylist, GapVinVideoInfo *vin_ptr, char *
     vin_ptr->select_mode        = 6;     /* GAP_MTCH_ALL_VISIBLE */
     vin_ptr->select_case        = 0;     /* 0 .. ignore case, 1..case sensitve */
     vin_ptr->select_invert      = 0;     /* 0 .. no invert, 1 ..invert */
-    vin_ptr->select_string[0] = '\0';
+    vin_ptr->select_string[0]   = '\0';
+    vin_ptr->layermask_mode     = 0;
+    vin_ptr->active_mode        = 0;
   }
   
   l_vin_filename = gap_vin_alloc_name(basename);
@@ -215,6 +221,7 @@ gap_vin_get_all(char *basename)
   {
      printf("gap_vin_get_all: RETURN with vin_ptr content:\n");
      printf("  num_olayers: %d\n",   (int)vin_ptr->num_olayers);
+     printf("  ref_mode: %d\n",      (int)vin_ptr->ref_mode);
      printf("  ref_delta: %d\n",     (int)vin_ptr->ref_delta);
      printf("  ref_cycle: %d\n",     (int)vin_ptr->ref_cycle);
      printf("  stack_pos: %d\n",     (int)vin_ptr->stack_pos);
@@ -226,6 +233,8 @@ gap_vin_get_all(char *basename)
      printf("  onionskin_auto_enable: %d\n",   (int)vin_ptr->onionskin_auto_enable);
      printf("  auto_replace_after_load: %d\n",   (int)vin_ptr->auto_replace_after_load);
      printf("  auto_delete_before_save: %d\n",   (int)vin_ptr->auto_delete_before_save);
+     printf("  layermask_mode: %d\n",(int)vin_ptr->layermask_mode);
+     printf("  active_mode: %d\n",   (int)vin_ptr->active_mode);
   }
   return(vin_ptr);
 }  /* end gap_vin_get_all */
diff --git a/gap/gap_vin.h b/gap/gap_vin.h
index 8452c15..b281077 100644
--- a/gap/gap_vin.h
+++ b/gap/gap_vin.h
@@ -74,6 +74,9 @@ typedef struct GapVinVideoInfo {
   gboolean asc_opacity;    /* TRUE: the far neighbour frames have higher opacity
                             * FALSE: near neighbour frames have higher opacity (DEFAULT)
                             */
+  gint32  layermask_mode;  /* onionskin layermask creation 0: NONE, 1:SELECTION, 2:BLACK, 3:WHITE */
+  gint32  active_mode;     /* 0 keep active layer, 1 set oinon layer active, 2 set onion layermask active */
+
 } GapVinVideoInfo;
 
 
diff --git a/gap/gap_wr_desaturate.c b/gap/gap_wr_desaturate.c
new file mode 100644
index 0000000..9de871e
--- /dev/null
+++ b/gap/gap_wr_desaturate.c
@@ -0,0 +1,539 @@
+/* gap_wr_desaturate.c
+ * 2017.03.07 hof (Wolfgang Hofer)
+ *
+ *  Wrapper Plugin for GIMP desaturate procedure
+ *
+ *  It provides a primitive Dialog Interface where you
+ *  can call the gimp_drawable_desaturate procedure.
+ *
+ *  Further it has an Interface to 'Run_with_last_values'
+ *  and an Iterator Procedure.
+ *  (This enables the 'Animated Filter Call' from
+ *   the GAP's Menu Filters->Filter all Layers)
+ *
+ */
+/* 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, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+/* Revision history
+ *  (2017/03/07)  v2.8.xx   hof: - created
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <gtk/gtk.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#include "gap_lastvaldesc.h"
+#include "gap_lastvaldesc.h"
+#include "gap_libgimpgap.h"
+
+#include "gap-intl.h"
+
+/* Defines */
+#define PLUG_IN_NAME        "plug-in-wr-desaturate"
+#define PLUG_IN_IMAGE_TYPES "RGB*, GRAY*"
+#define PLUG_IN_AUTHOR      "Wolfgang Hofer (hof gimp org)"
+#define PLUG_IN_COPYRIGHT   "Wolfgang Hofer"
+#define PLUG_IN_DESCRIPTION "Wrapper call for GIMP desaturate procedure"
+
+#define PLUG_IN_HELP_ID         "plug-in-wr-desaturate"
+
+
+
+typedef struct
+{
+  gint32    desaturate_mode;
+} wr_desaturate_val_t;
+
+
+typedef struct _WrDialog WrDialog;
+
+struct _WrDialog
+{
+  gint          run;
+  gint          show_progress;
+  GtkWidget       *shell;
+
+  GtkWidget       *radio_lightess;
+  GtkWidget       *radio_luminosity;
+  GtkWidget       *radio_average;
+
+  wr_desaturate_val_t *vals;
+};
+
+static wr_desaturate_val_t glob_vals =
+{
+   GIMP_DESATURATE_LIGHTNESS           /* desaturate_mode */
+};
+
+
+
+
+WrDialog *do_dialog (wr_desaturate_val_t *);
+static void  query (void);
+static void  run(const gchar *name
+           , gint nparams
+           , const GimpParam *param
+           , gint *nreturn_vals
+           , GimpParam **return_vals);
+
+/* Global Variables */
+GimpPlugInInfo PLUG_IN_INFO =
+{
+  NULL,   /* init_proc  */
+  NULL,   /* quit_proc  */
+  query,  /* query_proc */
+  run     /* run_proc   */
+};
+
+
+gint  gap_debug = 0;  /* 0.. no debug, 1 .. print debug messages */
+
+/* --------------
+ * procedures
+ * --------------
+ */
+
+
+
+
+static gint32
+p_run_desaturate_procedure(gint32 drawable_id, wr_desaturate_val_t *cuvals)
+{
+  gint32 desaturatedDrawableId;
+  if(gap_debug)
+  {
+     printf("p_run_desaturate_procedure: drawable_id :%d\n", (int)drawable_id);
+     printf("p_run_desaturate_procedure:  desaturate_mode:%d\n", (int)cuvals->desaturate_mode);
+  }
+  
+  /* for gimp-2.8.xx use the old name gimp_desaturate_full (that is deprected since gimp-2.9) */
+  gimp_desaturate_full(drawable_id, cuvals->desaturate_mode);
+
+// GIMP-2.9 gimp_desaturate_full is
+//  gimp_drawable_desaturate(drawable_id, cuvals->desaturate_mode);
+
+
+  desaturatedDrawableId = drawable_id;
+  return (desaturatedDrawableId);                     
+
+}
+
+MAIN ()
+
+static void
+query (void)
+{
+  static GimpLastvalDef lastvals[] =
+  {
+    GIMP_LASTVALDEF_GINT32          (GIMP_ITER_FALSE,  glob_vals.desaturate_mode,  "desaturate_mode")
+  };
+  
+  /* registration for last values buffer structure (useful for animated filter apply) */
+  gimp_lastval_desc_register(PLUG_IN_NAME,
+                             &glob_vals,
+                             sizeof(glob_vals),
+                             G_N_ELEMENTS (lastvals),
+                             lastvals);
+
+  static GimpParamDef args[] = {
+                  { GIMP_PDB_INT32,      "run_mode", "Interactive, non-interactive"},
+                  { GIMP_PDB_IMAGE,      "image", "Input image" },
+                  { GIMP_PDB_DRAWABLE,   "drawable", "Input drawable (must be a layer without layermask)"},
+                  { GIMP_PDB_INT32,      "desaturate_mode", " DESATURATE-LIGHTNESS (0), 
DESATURATE-LUMINOSITY (1), DESATURATE-AVERAGE (2)"}
+  };
+  static int nargs = sizeof(args) / sizeof(args[0]);
+
+  static GimpParamDef return_vals[] =
+  {
+    { GIMP_PDB_DRAWABLE, "the_drawable", "the handled drawable" }
+  };
+  static int nreturn_vals = sizeof(return_vals) / sizeof(return_vals[0]);
+
+
+  static GimpParamDef args_iter[] =
+  {
+    {GIMP_PDB_INT32, "run_mode", "non-interactive"},
+    {GIMP_PDB_INT32, "total_steps", "total number of steps (# of layers-1 to apply the related plug-in)"},
+    {GIMP_PDB_FLOAT, "current_step", "current (for linear iterations this is the layerstack position, 
otherwise some value inbetween)"},
+    {GIMP_PDB_INT32, "len_struct", "length of stored data structure with id is equal to the plug_in  
proc_name"},
+  };
+  static int nargs_iter = sizeof(args_iter) / sizeof(args_iter[0]);
+
+  static GimpParamDef *return_iter = NULL;
+  static int nreturn_iter = 0;
+
+  gimp_plugin_domain_register (GETTEXT_PACKAGE, LOCALEDIR);
+
+  /* the actual installation of the desaturate wrapper plugin */
+  gimp_install_procedure (PLUG_IN_NAME,
+                          PLUG_IN_DESCRIPTION,
+                         "This Plugin is a wrapper to call the GIMP Desaturate procedure 
(gimp_drawable_desaturate)"
+                         " it has a simplified Dialog (without preview) where you can enter the parameters"
+                         " this wrapper is useful for animated filtercalls and provides "
+                         " a PDB interface that runs in GIMP_RUN_WITH_LAST_VALUES mode"
+                         " and also provides an Iterator Procedure for animated calls"
+                          ,
+                          PLUG_IN_AUTHOR,
+                          PLUG_IN_COPYRIGHT,
+                          GAP_VERSION_WITH_DATE,
+                          N_("Desaturate..."),
+                          PLUG_IN_IMAGE_TYPES,
+                          GIMP_PLUGIN,
+                          nargs,
+                          nreturn_vals,
+                          args,
+                          return_vals);
+
+
+  {
+    /* Menu names */
+    const char *menupath_image_video_layer_colors = N_("<Image>/Video/Layer/Colors/");
+
+    //gimp_plugin_menu_branch_register("<Image>", "Video");
+    //gimp_plugin_menu_branch_register("<Image>/Video", "Layer");
+    //gimp_plugin_menu_branch_register("<Image>/Video/Layer", "Colors");
+
+    gimp_plugin_menu_register (PLUG_IN_NAME, menupath_image_video_layer_colors);
+  }
+}
+
+
+static void
+run(const gchar *name
+           , gint nparams
+           , const GimpParam *param
+           , gint *nreturn_vals
+           , GimpParam **return_vals)
+{
+  wr_desaturate_val_t l_cuvals;
+  WrDialog   *wcd = NULL;
+
+  gint32    l_image_id = -1;
+  gint32    l_drawable_id = -1;
+  gint32    l_handled_drawable_id = -1;
+
+  /* Get the runmode from the in-parameters */
+  GimpRunMode run_mode = param[0].data.d_int32;
+
+
+  if(gap_debug)
+  {
+    printf("START plug-in-wr-desaturate\n");
+  }
+
+
+  /* status variable, use it to check for errors in invocation usualy only
+     during non-interactive calling */
+  GimpPDBStatusType status = GIMP_PDB_SUCCESS;
+
+  /*always return at least the status to the caller. */
+  static GimpParam values[2];
+
+
+  INIT_I18N();
+
+  /* initialize the return of the status */
+  values[0].type = GIMP_PDB_STATUS;
+  values[0].data.d_status = status;
+  values[1].type = GIMP_PDB_DRAWABLE;
+  values[1].data.d_int32 = -1;
+  *nreturn_vals = 2;
+  *return_vals = values;
+
+
+  /* get image and drawable */
+  l_image_id = param[1].data.d_int32;
+  l_drawable_id = param[2].data.d_drawable;
+
+  if(status == GIMP_PDB_SUCCESS)
+  {
+    /* how are we running today? */
+    switch (run_mode)
+     {
+      case GIMP_RUN_INTERACTIVE:
+        /* Initial values */
+        l_cuvals.desaturate_mode = GIMP_DESATURATE_LIGHTNESS;
+
+        /* Get information from the dialog */
+        wcd = do_dialog(&l_cuvals);
+        wcd->show_progress = TRUE;
+        break;
+
+      case GIMP_RUN_NONINTERACTIVE:
+        /* check to see if invoked with the correct number of parameters */
+        if (nparams >= 9)
+        {
+           wcd = g_malloc (sizeof (WrDialog));
+           wcd->run = TRUE;
+           wcd->show_progress = FALSE;
+
+           l_cuvals.desaturate_mode  = param[3].data.d_int32;
+        }
+        else
+        {
+          status = GIMP_PDB_CALLING_ERROR;
+        }
+        break;
+
+      case GIMP_RUN_WITH_LAST_VALS:
+        wcd = g_malloc (sizeof (WrDialog));
+        wcd->run = TRUE;
+        wcd->show_progress = TRUE;
+        /* Possibly retrieve data from a previous run */
+        gimp_get_data (PLUG_IN_NAME, &l_cuvals);
+        break;
+
+      default:
+        break;
+    }
+  }
+
+  if (wcd == NULL)
+  {
+    status = GIMP_PDB_EXECUTION_ERROR;
+  }
+
+  if (status == GIMP_PDB_SUCCESS)
+  {
+     /* Run the main function */
+     if(wcd->run)
+     {
+        gimp_image_undo_group_start (l_image_id);
+        l_handled_drawable_id = p_run_desaturate_procedure(l_drawable_id, &l_cuvals);
+        gimp_image_undo_group_end (l_image_id);
+
+        /* Store variable states for next run */
+        if (run_mode == GIMP_RUN_INTERACTIVE)
+        {
+          gimp_set_data(PLUG_IN_NAME, &l_cuvals, sizeof(l_cuvals));
+        }
+     }
+     else
+     {
+       status = GIMP_PDB_EXECUTION_ERROR;       /* dialog ended with cancel button */
+     }
+
+     /* If run mode is interactive, flush displays, else (script) don't
+        do it, as the screen updates would make the scripts slow */
+     if (run_mode != GIMP_RUN_NONINTERACTIVE)
+     {
+       gimp_displays_flush ();
+     }
+  }
+  values[0].data.d_status = status;
+  values[1].data.d_int32 = l_handled_drawable_id;   /* return the id of handled layer */
+
+}       /* end run */
+
+
+/*
+ * DIALOG and callback stuff
+ */
+
+
+static void
+radio_callback(GtkWidget *wgt, gpointer user_data)
+{
+  WrDialog *wcd;
+
+  if(gap_debug) printf("radio_callback: START\n");
+  wcd = (WrDialog*)user_data;
+  if(wcd != NULL)
+  {
+    if(wcd->vals != NULL)
+    {
+       if(wgt == wcd->radio_lightess)    { wcd->vals->desaturate_mode = GIMP_DESATURATE_LIGHTNESS; }
+       if(wgt == wcd->radio_luminosity)  { wcd->vals->desaturate_mode = GIMP_DESATURATE_LUMINOSITY; }
+       if(wgt == wcd->radio_average)     { wcd->vals->desaturate_mode = GIMP_DESATURATE_AVERAGE; }
+
+       if(gap_debug)
+       {
+         printf("radio_callback: value: %d\n", (int)wcd->vals->desaturate_mode);
+       }
+    }
+  }
+}
+
+
+/* ---------------------------------
+ * wr_desaturate_response
+ * ---------------------------------
+ */
+static void
+wr_desaturate_response (GtkWidget *widget,
+                 gint       response_id,
+                 WrDialog *wcd)
+{
+  GtkWidget *dialog;
+
+  switch (response_id)
+  {
+    case GTK_RESPONSE_OK:
+      if(wcd)
+      {
+        if (GTK_WIDGET_VISIBLE (wcd->shell))
+          gtk_widget_hide (wcd->shell);
+
+        wcd->run = TRUE;
+      }
+
+    default:
+      dialog = NULL;
+      if(wcd)
+      {
+        dialog = wcd->shell;
+        if(dialog)
+        {
+          wcd->shell = NULL;
+          gtk_widget_destroy (dialog);
+        }
+      }
+      gtk_main_quit ();
+      break;
+  }
+}  /* end wr_desaturate_response */
+
+
+WrDialog *
+do_dialog (wr_desaturate_val_t *cuvals)
+{
+  WrDialog *wcd;
+  GtkWidget  *vbox;
+
+  GtkWidget *dialog1;
+  GtkWidget *dialog_vbox1;
+  GtkWidget *frame1;
+  GtkWidget *hbox1;
+  GtkWidget *vbox1;
+  GtkWidget *label1;
+  GSList *vbox1_group = NULL;
+  GtkWidget *radiobutton1;
+  GtkWidget *radiobutton2;
+  GtkWidget *radiobutton3;
+  GtkWidget *table1;
+  GtkWidget *dialog_action_area1;
+
+
+  /* Init UI  */
+  gimp_ui_init ("wr_desaturate", FALSE);
+
+  /*  The dialog1  */
+  wcd = g_malloc (sizeof (WrDialog));
+  wcd->run = FALSE;
+  wcd->vals = cuvals;
+
+  /*  The dialog1 and main vbox  */
+  dialog1 = gimp_dialog_new (_("Desaturate"), "desaturate_wrapper",
+                               NULL, 0,
+                               gimp_standard_help_func, PLUG_IN_HELP_ID,
+
+                               GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+                               GTK_STOCK_OK,     GTK_RESPONSE_OK,
+                               NULL);
+  wcd->shell = dialog1;
+
+
+  g_signal_connect (G_OBJECT (dialog1), "response",
+                    G_CALLBACK (wr_desaturate_response),
+                    wcd);
+
+  /* the vbox */
+  vbox = gtk_vbox_new (FALSE, 2);
+  gtk_container_set_border_width (GTK_CONTAINER (vbox), 2);
+  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog1)->vbox), vbox,
+                      TRUE, TRUE, 0);
+  gtk_widget_show (vbox);
+
+  dialog_vbox1 = GTK_DIALOG (dialog1)->vbox;
+  gtk_widget_show (dialog_vbox1);
+
+
+
+  /* the frame */
+  frame1 = gimp_frame_new (_("Choose shade of gray based on:"));
+  gtk_widget_show (frame1);
+  gtk_box_pack_start (GTK_BOX (dialog_vbox1), frame1, TRUE, TRUE, 0);
+  gtk_container_set_border_width (GTK_CONTAINER (frame1), 2);
+
+  hbox1 = gtk_hbox_new (FALSE, 0);
+  gtk_widget_show (hbox1);
+  gtk_container_add (GTK_CONTAINER (frame1), hbox1);
+  gtk_container_set_border_width (GTK_CONTAINER (hbox1), 4);
+
+  vbox1 = gtk_vbox_new (FALSE, 0);
+  gtk_widget_show (vbox1);
+  gtk_box_pack_start (GTK_BOX (hbox1), vbox1, TRUE, TRUE, 0);
+
+
+  /* Shades the label */
+  label1 = gtk_label_new (_("Shades:"));
+  gtk_widget_show (label1);
+  gtk_box_pack_start (GTK_BOX (vbox1), label1, FALSE, FALSE, 0);
+  gtk_misc_set_alignment (GTK_MISC (label1), 0, 0.5);
+
+
+  /* Shades the radio buttons */
+  radiobutton1 = gtk_radio_button_new_with_label (vbox1_group, _("Lightness"));
+  vbox1_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radiobutton1));
+  gtk_widget_show (radiobutton1);
+  gtk_box_pack_start (GTK_BOX (vbox1), radiobutton1, FALSE, FALSE, 0);
+
+  radiobutton2 = gtk_radio_button_new_with_label (vbox1_group, _("Luminosity"));
+  vbox1_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radiobutton2));
+  gtk_widget_show (radiobutton2);
+  gtk_box_pack_start (GTK_BOX (vbox1), radiobutton2, FALSE, FALSE, 0);
+
+  radiobutton3 = gtk_radio_button_new_with_label (vbox1_group, _("Average"));
+  vbox1_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radiobutton3));
+  gtk_widget_show (radiobutton3);
+  gtk_box_pack_start (GTK_BOX (vbox1), radiobutton3, FALSE, FALSE, 0);
+
+
+  dialog_action_area1 = GTK_DIALOG (dialog1)->action_area;
+  gtk_widget_show (dialog_action_area1);
+  gtk_container_set_border_width (GTK_CONTAINER (dialog_action_area1), 10);
+
+
+
+  wcd->radio_lightess  = radiobutton1;
+  wcd->radio_luminosity = radiobutton2;
+  wcd->radio_average   = radiobutton3;
+
+
+  /* signals */
+  g_signal_connect (G_OBJECT (wcd->radio_lightess),  "clicked",  G_CALLBACK (radio_callback), wcd);
+  g_signal_connect (G_OBJECT (wcd->radio_luminosity),     "clicked",  G_CALLBACK (radio_callback), wcd);
+  g_signal_connect (G_OBJECT (wcd->radio_average),   "clicked",  G_CALLBACK (radio_callback), wcd);
+
+
+  gtk_widget_show (dialog1);
+
+  gtk_main ();
+  gdk_flush ();
+
+
+  return wcd;
+}
diff --git a/gap/gap_wr_resynth.c b/gap/gap_wr_resynth.c
index 000ecf4..9864a81 100644
--- a/gap/gap_wr_resynth.c
+++ b/gap/gap_wr_resynth.c
@@ -3,7 +3,9 @@
  *   Useful to remove unwanted logos when processing video frames.
  * PRECONDITIONS:
  *   Requires resynthesizer plug-in.
- *  (resynthesizer-0.16.tar.gz is available in the gimp plug-in registry)
+ *  (resynthesizer-0.16.tar.gz is available in the gimp plug-in registry
+ *   alternative sourcecode download from https://github.com/bootchk/resynthesizer  Latest commit on 29 May 
2016
+ *   was testest working OK on 2017.03.02 with this wrapper)
  *  NOTE this wrapper also supports an extended variant plug-in-resynthesizer-s
  *       that has an additional seed parameter.
  */
@@ -57,6 +59,21 @@
 #define PLUG_IN_RESYNTHESIZER            "plug-in-resynthesizer"
 #define PLUG_IN_RESYNTHESIZER_WITH_SEED  "plug-in-resynthesizer-s"  /* unpublished prvate version */
 
+#define MAX_SVG_SIZE     1600
+#define BUTTON_MIN_WIDTH     50
+
+
+#define DIRECTION_ALL_AROUND      0
+#define DIRECTION_SIDES           1
+#define DIRECTION_ABOVE_AND_BELOW 2
+
+#define FILL_ORDER_RANDOM                0
+#define FILL_ORDER_INWARDS_TO_CENTER     1
+#define FILL_ORDER_OUTWARDS_FROM_CENTER  2
+
+
+#define SELECTION_FROM_VECTORS -2
+#define SELECTION_FROM_SVG_FILE -3
 
 
 /***** Magic numbers *****/
@@ -69,15 +86,32 @@
 
 typedef struct {
   gint32  corpus_border_radius;
+  gint32  directionParam;
+  gint32  orderParam;
+
   gint32  alt_selection;
   gint32  seed;
+  gchar   selectionSVGFileName[MAX_SVG_SIZE]; /* contains small xml string or reference to SVG file */
 } TransValues;
 
+typedef struct GuiStuff {
+  //gint32      imageId;
+  //GtkWidget  *ok_button;
+  //GtkWidget  *msg_label;
+  GtkWidget  *svg_entry;
+  GtkWidget  *svg_filesel;
+  TransValues *valPtr;
+} GuiStuff;
+
+
 static TransValues glob_vals =
 {
    20           /* corpus_border_radius */
+,  DIRECTION_ALL_AROUND           /* directionParam 0 = AllAround, 1 = Sides, 2 = Above and Below */
+,  FILL_ORDER_RANDOM              /* filling orderParam 0 = Random, 1 = Inwards To Center, 2 = Outwards from 
center */
 ,  -1           /* alt_selection (drawable id or -1 for using original selection) */
 , 4711          /* seed for random number generator */
+, "selection.svg"
 };
 
 
@@ -92,10 +126,18 @@ static void run   (const gchar      *name,
                    gint             *nreturn_vals,
                    GimpParam       **return_vals);
 
+static void p_set_selection_from_vectors_file(gint32 imageId, TransValues *val_ptr);
 static gint p_selectionConstraintFunc (gint32   image_id,
                gint32   drawable_id,
                gpointer data);
 static gboolean p_dialog(TransValues *val_ptr, gint32 drawable_id);
+static void p_on_gint32_combo_callback  (GtkWidget     *widget, gint32 *value);
+static void on_svg_entry_changed              (GtkEditable     *editable,
+                                   TransValues *val_ptr);
+static void on_filesel_button_clicked             (GtkButton *button,
+                                       GuiStuff  *guiStuffPtr);
+static GtkWidget* p_create_fileselection (GuiStuff *guiStuffPtr);
+
 static gint32 p_process_layer(gint32 image_id, gint32 drawable_id, TransValues *val_ptr);
 
 /***** Variables *****/
@@ -113,8 +155,11 @@ static GimpParamDef in_args[] = {
     { GIMP_PDB_IMAGE,    "image",                "Input image"                  },
     { GIMP_PDB_DRAWABLE, "drawable",             "The drawable (typically a layer)"               },
     { GIMP_PDB_INT32,    "corpus_border_radius", "Radius to take texture from"     },
+    { GIMP_PDB_INT32,    "directionParam",       "0 = AllAround, 1 = Sides, 2 = Above and Below"     },
+    { GIMP_PDB_INT32,    "orderParam",           "filling orderParam 0 = Random, 1 = Inwards To Center, 2 = 
Outwards from center"     },
     { GIMP_PDB_DRAWABLE, "alt_selection",        "id of a drawable to replace the selection (use -1 to 
operate with selection of the input image)"     },
-    { GIMP_PDB_INT32,    "seed",                 "seed for random numbers (use -1 to init with current 
time)"     }
+    { GIMP_PDB_INT32,    "seed",                 "seed for random numbers (use -1 to init with current 
time)"     },
+    { GIMP_PDB_STRING,   "selSVGFile",    "optional name of a file that contains the selection as vectors in 
SVG format. (set altSelection to -2)" }
   };
 
 static GimpParamDef return_vals[] = {
@@ -143,8 +188,12 @@ query (void)
   static GimpLastvalDef lastvals[] =
   {
     GIMP_LASTVALDEF_GINT32          (GIMP_ITER_TRUE,  glob_vals.corpus_border_radius,  
"corpus_border_radius"),
+    GIMP_LASTVALDEF_GINT32          (GIMP_ITER_FALSE, glob_vals.directionParam,        "directionParam"),
+    GIMP_LASTVALDEF_GINT32          (GIMP_ITER_FALSE, glob_vals.orderParam,            "orderParam"),
     GIMP_LASTVALDEF_DRAWABLE        (GIMP_ITER_TRUE,  glob_vals.alt_selection,         "alt_selection"),
-    GIMP_LASTVALDEF_GINT32          (GIMP_ITER_TRUE,  glob_vals.seed,                  "seed")
+    GIMP_LASTVALDEF_GINT32          (GIMP_ITER_TRUE,  glob_vals.seed,                  "seed"),
+    GIMP_LASTVALDEF_ARRAY           (GIMP_ITER_FALSE, glob_vals.selectionSVGFileName,    "svgFileNameArray"),
+    GIMP_LASTVALDEF_GCHAR           (GIMP_ITER_FALSE, glob_vals.selectionSVGFileName[0], 
"svgFilenameNameChar")
   };
 
   gimp_plugin_domain_register (GETTEXT_PACKAGE, LOCALEDIR);
@@ -158,7 +207,7 @@ query (void)
 
 
   gimp_install_procedure (PLUG_IN_PROC,
-                          N_("Smart selection eraser."),
+                          N_("Heal Selection"),
                           "Remove an object from an image by extending surrounding texture to cover it. "
                           "The object can be represented by the current selection  "
                           "or by an alternative selection (provided as parameter alt_selection) "
@@ -168,13 +217,13 @@ query (void)
                           "to identify the object that shall be replaced. "
                           "alt_selection value -1 indicates that the selection of the input image shall be 
used. "
                           "Requires resynthesizer plug-in. (available in the gimp plug-in registry) "
-                          "The smart selection eraser wrapper provides ability to run in GIMP_GAP 
filtermacros "
+                          "The Heal Selection wrapper provides ability to run in GIMP_GAP filtermacros "
                           "when processing video frames (typically for removing unwanted logos from video 
frames)."
                           "(using the same seed value for all frames is recommended) ",
                           "Wolfgang Hofer",
                           "Wolfgang Hofer",
                           PLUG_IN_VERSION,
-                          N_("Smart selection eraser..."),
+                          N_("Heal Selection..."),
                           "RGB*, GRAY*",
                           GIMP_PLUGIN,
                           global_number_in_args, global_number_out_args,
@@ -243,7 +292,15 @@ run (const gchar *name,          /* name of plugin */
 
       /* Initial values */
       glob_vals.corpus_border_radius = 20;
+      glob_vals.directionParam = 0;
+      glob_vals.orderParam = 0;
       glob_vals.alt_selection = -1;
+      
+      glob_vals.selectionSVGFileName[0] = '\0';
+      g_snprintf(glob_vals.selectionSVGFileName
+                   , sizeof(glob_vals.selectionSVGFileName), "%s"
+             , _("selection.svg"));
+      
       run_flag = TRUE;
 
       /* Possibly retrieve data from a previous run */
@@ -262,8 +319,10 @@ run (const gchar *name,          /* name of plugin */
           if (nparams >= global_number_in_args)
           {
              glob_vals.corpus_border_radius = param[3].data.d_int32;
-             glob_vals.alt_selection = param[4].data.d_int32;
-             glob_vals.seed = param[5].data.d_int32;
+             glob_vals.directionParam = param[4].data.d_int32;
+             glob_vals.orderParam = param[5].data.d_int32;
+             glob_vals.alt_selection = param[6].data.d_int32;
+             glob_vals.seed = param[7].data.d_int32;
           }
           else
           {
@@ -341,6 +400,53 @@ run (const gchar *name,          /* name of plugin */
 }       /* end run */
 
 
+
+/* ----------------------------------
+ * p_set_selection_from_vectors_file
+ * ----------------------------------
+ * import selection from an SVG vectors file
+ * and replace the current selection on success.
+ * (on errors keep current selection)
+ */
+static void
+p_set_selection_from_vectors_file(gint32 imageId, TransValues *val_ptr)
+{
+  gboolean vectorsOk;
+  gint     num_vectors;
+  gint32  *vectors_ids;
+
+  vectorsOk = FALSE;
+  if (val_ptr->selectionSVGFileName != '\0')
+  {
+    if(g_file_test(val_ptr->selectionSVGFileName, G_FILE_TEST_EXISTS))
+    {
+      vectorsOk = gimp_vectors_import_from_file   (imageId
+                                                  ,val_ptr->selectionSVGFileName
+                                                  , TRUE  /* Merge paths into a single vectors object. */
+                                                  , TRUE  /* Scale the SVG to image dimensions. */
+                                                  , &num_vectors
+                                                  , &vectors_ids
+                                                  );
+    }
+  }
+
+
+  if ((vectorsOk) && (vectors_ids != NULL) && (num_vectors > 0))
+  {
+    gint32         vectorId;
+    GimpChannelOps operation;
+
+    vectorId = vectors_ids[0];
+    operation = GIMP_CHANNEL_OP_REPLACE;
+    gimp_image_select_item(imageId, operation, vectorId);
+    gimp_image_remove_vectors(imageId, vectorId);
+    // doClearSelection = TRUE;
+  }
+
+}  /* end p_set_selection_from_vectors_file */
+
+
+
 /* ----------------------------
  * p_selectionConstraintFunc
  * ----------------------------
@@ -362,6 +468,8 @@ p_selectionConstraintFunc (gint32   image_id,
 }  /* end p_selectionConstraintFunc */
 
 
+
+
 /* ----------------------------
  * p_selectionComboCallback
  * ----------------------------
@@ -387,6 +495,181 @@ p_selectionComboCallback (GtkWidget *widget)
 
 }  /* end p_selectionComboCallback */
 
+/* ----------------------------------------
+ * p_on_gint32_combo_callback
+ * ----------------------------------------
+ */
+static void
+p_on_gint32_combo_callback  (GtkWidget     *widget,
+                       gint32 *valuePtr)
+{
+  gint       value;
+
+  if(gap_debug) printf("CB: p_on_gint32_combo_callback\n");
+
+  if(valuePtr == NULL) return;
+
+  gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &value);
+
+  if(gap_debug)
+  {
+    printf("CB: p_on_gint32_combo_callback value: %d\n", (int)value);
+  }
+
+  *valuePtr = value;
+
+}  /* end p_on_gint32_combo_callback */
+
+/* ---------------------------------
+ * on_svg_entry_changed
+ * ---------------------------------
+ */
+static void
+on_svg_entry_changed              (GtkEditable     *editable,
+                                   TransValues *val_ptr)
+{
+  GtkEntry *entry;
+
+ if(gap_debug)
+ {
+   printf("CB: on_svg_entry_changed\n");
+ }
+ if(val_ptr == NULL)
+ {
+   return;
+ }
+
+ entry = GTK_ENTRY(editable);
+ if(entry)
+ {
+    g_snprintf(val_ptr->selectionSVGFileName
+              , sizeof(val_ptr->selectionSVGFileName), "%s"
+              , gtk_entry_get_text(entry));
+    // TODO p_check_exec_condition_and_set_ok_sesitivity(guiStuffPtr);
+ }
+}
+
+
+/* ---------------------------------
+ * on_filesel_button_clicked
+ * ---------------------------------
+ */
+static void
+on_filesel_button_clicked             (GtkButton *button,
+                                       GuiStuff  *guiStuffPtr)
+{
+ if(gap_debug)
+ {
+   printf("CB: on_filesel_button_clicked\n");
+ }
+ if(guiStuffPtr == NULL)
+ {
+   return;
+ }
+
+ if(guiStuffPtr->svg_filesel == NULL)
+ {
+   guiStuffPtr->svg_filesel = p_create_fileselection(guiStuffPtr);
+   gtk_file_selection_set_filename (GTK_FILE_SELECTION (guiStuffPtr->svg_filesel),
+                                    guiStuffPtr->valPtr->selectionSVGFileName);
+
+   gtk_widget_show (guiStuffPtr->svg_filesel);
+ }
+
+}  /* end on_filesel_button_clicked */
+
+
+/* ---------------------------------
+ * svg fileselct callbacks
+ * ---------------------------------
+ */
+
+static void
+on_svg_filesel_destroy          (GtkObject *object,
+                                 GuiStuff  *guiStuffPtr)
+{
+ if(gap_debug) printf("CB: on_svg_filesel_destroy\n");
+ if(guiStuffPtr == NULL) return;
+
+ guiStuffPtr->svg_filesel = NULL;
+}
+
+static void
+on_svg__button_cancel_clicked          (GtkButton *button,
+                                        GuiStuff  *guiStuffPtr)
+{
+ if(gap_debug) printf("CB: on_svg__button_cancel_clicked\n");
+ if(guiStuffPtr == NULL) return;
+
+ if(guiStuffPtr->svg_filesel)
+ {
+   gtk_widget_destroy(guiStuffPtr->svg_filesel);
+   guiStuffPtr->svg_filesel = NULL;
+ }
+}
+
+static void
+on_svg__button_OK_clicked       (GtkButton *button,
+                                 GuiStuff  *guiStuffPtr)
+{
+  const gchar *filename;
+  GtkEntry *entry;
+
+ if(gap_debug) printf("CB: on_svg__button_OK_clicked\n");
+ if(guiStuffPtr == NULL) return;
+
+ if(guiStuffPtr->svg_filesel)
+ {
+   filename =  gtk_file_selection_get_filename (GTK_FILE_SELECTION (guiStuffPtr->svg_filesel));
+   g_snprintf(guiStuffPtr->valPtr->selectionSVGFileName
+             , sizeof(guiStuffPtr->valPtr->selectionSVGFileName), "%s"
+             , filename);
+   entry = GTK_ENTRY(guiStuffPtr->svg_entry);
+   if(entry)
+   {
+      gtk_entry_set_text(entry, filename);
+   }
+   on_svg__button_cancel_clicked(NULL, (gpointer)guiStuffPtr);
+ }
+}
+
+
+/* ----------------------------------------
+ * p_create_fileselection
+ * ----------------------------------------
+ */
+static GtkWidget*
+p_create_fileselection (GuiStuff *guiStuffPtr)
+{
+  GtkWidget *svg_filesel;
+  GtkWidget *svg__button_OK;
+  GtkWidget *svg__button_cancel;
+
+  svg_filesel = gtk_file_selection_new (_("Select vectorfile name"));
+  gtk_container_set_border_width (GTK_CONTAINER (svg_filesel), 10);
+
+  svg__button_OK = GTK_FILE_SELECTION (svg_filesel)->ok_button;
+  gtk_widget_show (svg__button_OK);
+  GTK_WIDGET_SET_FLAGS (svg__button_OK, GTK_CAN_DEFAULT);
+
+  svg__button_cancel = GTK_FILE_SELECTION (svg_filesel)->cancel_button;
+  gtk_widget_show (svg__button_cancel);
+  GTK_WIDGET_SET_FLAGS (svg__button_cancel, GTK_CAN_DEFAULT);
+
+  g_signal_connect (G_OBJECT (svg_filesel), "destroy",
+                      G_CALLBACK (on_svg_filesel_destroy),
+                      guiStuffPtr);
+  g_signal_connect (G_OBJECT (svg__button_OK), "clicked",
+                      G_CALLBACK (on_svg__button_OK_clicked),
+                      guiStuffPtr);
+  g_signal_connect (G_OBJECT (svg__button_cancel), "clicked",
+                      G_CALLBACK (on_svg__button_cancel_clicked),
+                      guiStuffPtr);
+
+  gtk_widget_grab_default (svg__button_cancel);
+  return svg_filesel;
+}  /* end p_create_fileselection */
+
 
 /* --------------------------
  * p_dialog
@@ -395,11 +678,15 @@ p_selectionComboCallback (GtkWidget *widget)
 static gboolean
 p_dialog (TransValues *val_ptr, gint32 drawable_id)
 {
+  GuiStuff guiStuffRecord;
+  GuiStuff *guiStuffPtr;
+  GtkWidget *button;
   GtkWidget *dialog;
   GtkWidget *main_vbox;
   GtkWidget *label;
   GtkWidget *table;
   GtkWidget *combo;
+  GtkWidget *entry;
   GtkObject *adj;
   gint       row;
   gboolean   run;
@@ -408,6 +695,14 @@ p_dialog (TransValues *val_ptr, gint32 drawable_id)
   gboolean foundResynth;
   gboolean foundResynthS;
 
+
+  guiStuffPtr = &guiStuffRecord;
+  guiStuffPtr->valPtr = val_ptr;
+  guiStuffPtr->svg_entry = NULL;
+  guiStuffPtr->svg_filesel = NULL;
+
+
+
   foundResynthS = gap_pdb_procedure_name_available(PLUG_IN_RESYNTHESIZER_WITH_SEED);
   foundResynth = gap_pdb_procedure_name_available(PLUG_IN_RESYNTHESIZER);
   isResynthesizerInstalled = ((foundResynthS) || (foundResynth));
@@ -417,7 +712,7 @@ p_dialog (TransValues *val_ptr, gint32 drawable_id)
 
   if (isResynthesizerInstalled)
   {
-    dialog = gimp_dialog_new (_("Smart selection eraser"), PLUG_IN_BINARY,
+    dialog = gimp_dialog_new (_("Heal Selection"), PLUG_IN_BINARY,
                             NULL, 0,
                             gimp_standard_help_func, PLUG_IN_PROC,
 
@@ -429,7 +724,7 @@ p_dialog (TransValues *val_ptr, gint32 drawable_id)
   }
   else
   {
-    dialog = gimp_dialog_new (_("Smart selection eraser"), PLUG_IN_BINARY,
+    dialog = gimp_dialog_new (_("Heal Selection"), PLUG_IN_BINARY,
                             NULL, 0,
                             gimp_standard_help_func, PLUG_IN_PROC,
 
@@ -484,6 +779,67 @@ p_dialog (TransValues *val_ptr, gint32 drawable_id)
                       &val_ptr->corpus_border_radius);
 
     row++;
+    
+    /* the directionParam label */
+    label = gtk_label_new (_("Sample from:"));
+    gtk_widget_show (label);
+    gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+    gtk_table_attach (GTK_TABLE (table), label, 0, 1, row, row+1,
+                        (GtkAttachOptions) (GTK_FILL),
+                        (GtkAttachOptions) (0), 0, 0);
+    
+    
+    /* the directionParam combo */
+    combo = gimp_int_combo_box_new ("All around",      DIRECTION_ALL_AROUND,
+                                    "Sides",           DIRECTION_SIDES,
+                                    "Above and below", DIRECTION_ABOVE_AND_BELOW,
+                                    NULL);
+    
+    gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo),
+                                val_ptr->directionParam,  /* inital value */
+                                G_CALLBACK (p_on_gint32_combo_callback),
+                                &val_ptr->directionParam);
+    
+    gtk_widget_show (combo);
+    gtk_table_attach (GTK_TABLE (table), combo, 1, 3, row, row+1,
+                        (GtkAttachOptions) (GTK_FILL),
+                        (GtkAttachOptions) (0), 0, 0);
+    gimp_help_set_help_data (combo, _("Select direction from where get sample pattern"), NULL);
+    
+    
+
+    row++;
+
+    /* the directionParam label */
+    label = gtk_label_new (_("Filling order:"));
+    gtk_widget_show (label);
+    gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+    gtk_table_attach (GTK_TABLE (table), label, 0, 1, row, row+1,
+                        (GtkAttachOptions) (GTK_FILL),
+                        (GtkAttachOptions) (0), 0, 0);
+    
+    
+    /* the directionParam combo */
+    combo = gimp_int_combo_box_new ("Random",                 FILL_ORDER_RANDOM,
+                                    "Inwards towards center", FILL_ORDER_INWARDS_TO_CENTER,
+                                    "Outwards from center",   FILL_ORDER_OUTWARDS_FROM_CENTER,
+                                    NULL);
+    
+    gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo),
+                                val_ptr->orderParam,  /* inital value */
+                                G_CALLBACK (p_on_gint32_combo_callback),
+                                &val_ptr->orderParam);
+    
+    gtk_widget_show (combo);
+    gtk_table_attach (GTK_TABLE (table), combo, 1, 3, row, row+1,
+                        (GtkAttachOptions) (GTK_FILL),
+                        (GtkAttachOptions) (0), 0, 0);
+    gimp_help_set_help_data (combo, _("Select filling order"), NULL);
+    
+
+    row++;
+
+    
 
     if (foundResynthS)
     {
@@ -509,13 +865,68 @@ p_dialog (TransValues *val_ptr, gint32 drawable_id)
     /* layer combo_box (Sample from where to pick the alternative selection */
     combo = gimp_layer_combo_box_new (p_selectionConstraintFunc, NULL);
 
+    gimp_int_combo_box_prepend (GIMP_INT_COMBO_BOX (combo),
+                              GIMP_INT_STORE_VALUE,    SELECTION_FROM_SVG_FILE,
+                              GIMP_INT_STORE_LABEL,    _("Selection From Vectors File"),
+                              GIMP_INT_STORE_STOCK_ID, GIMP_STOCK_PATH,
+                              -1);
+
     gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), drawable_id,
                                 G_CALLBACK (p_selectionComboCallback),
                                 NULL);
 
+
+
+
+
+
     gtk_table_attach (GTK_TABLE (table), combo, 1, 3, row, row + 1,
                       GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
     gtk_widget_show (combo);
+    
+    
+    row++;
+
+    /* the svg file label */
+    label = gtk_label_new (_("Vectors (SVG) file:"));
+    gtk_widget_show (label);
+    gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+    gtk_table_attach (GTK_TABLE (table), label, 0, 1, row, row+1,
+                        (GtkAttachOptions) (GTK_FILL),
+                        (GtkAttachOptions) (0), 0, 0);
+    
+    /* the svg file name entry */
+    entry = gtk_entry_new ();
+    guiStuffPtr->svg_entry = entry;
+    gtk_widget_show (entry);
+    gtk_table_attach (GTK_TABLE (table), entry, 1, 2, row, row + 1,
+                      GTK_FILL, GTK_FILL, 4, 0);
+    gimp_help_set_help_data (entry, _("Name of SVG vector file from where to load selection"), NULL);
+    if(strncmp("<?xml", val_ptr->selectionSVGFileName, 3) == 0)
+    {
+      g_snprintf(val_ptr->selectionSVGFileName
+                 , sizeof(val_ptr->selectionSVGFileName), "%s"
+                 , _("selection.svg"));
+    }
+    gtk_entry_set_text(GTK_ENTRY (entry), val_ptr->selectionSVGFileName);
+    g_signal_connect (G_OBJECT (entry), "changed",
+                      G_CALLBACK (on_svg_entry_changed),
+                      val_ptr);
+
+
+    button = gtk_button_new_with_label (_("..."));
+    gtk_widget_set_size_request (button, BUTTON_MIN_WIDTH, -1);
+    gtk_widget_show (button);
+    gtk_table_attach (GTK_TABLE (table), button, 2, 3, row, row + 1,
+                    GTK_FILL, GTK_FILL, 4, 0);
+
+    gimp_help_set_help_data (button, _("Select output svg vector file via browser"), NULL);
+    g_signal_connect (G_OBJECT (button), "clicked",
+                      G_CALLBACK (on_filesel_button_clicked),
+                      guiStuffPtr);
+
+                      
+                      
   }
 
   /* Done */
@@ -535,9 +946,10 @@ p_dialog (TransValues *val_ptr, gint32 drawable_id)
  * --------------------------------
  * check if non official variant with additional seed parameter
  * is installed. if not use the official published resynthesizer 0.16
+ * (API was still compatible to more recent src repository at https://github.com/bootchk/resynthesizer  
Latest commit on 29 May 2016)
  */
 static gboolean
-p_pdb_call_resynthesizer(gint32 image_id, gint32 layer_id, gint32 corpus_layer_id, gint32 seed)
+p_pdb_call_resynthesizer(gint32 image_id, gint32 layer_id, gint32 corpus_layer_id, gint32 useContext, gint32 
seed)
 {
    char            *l_called_proc;
    GimpParam       *return_vals;
@@ -555,7 +967,7 @@ p_pdb_call_resynthesizer(gint32 image_id, gint32 layer_id, gint32 corpus_layer_i
                                  GIMP_PDB_DRAWABLE,  layer_id,          /* input drawable (to be processed) 
*/
                                  GIMP_PDB_INT32,     0,                 /* vtile Make tilable vertically */
                                  GIMP_PDB_INT32,     0,                 /* htile Make tilable horizontally */
-                                 GIMP_PDB_INT32,     1,                 /* Dont change border pixels */
+                                 GIMP_PDB_INT32,     useContext,        /* useContext 1 =Dont change border 
pixels */
                                  GIMP_PDB_INT32,     corpus_layer_id,   /* corpus, Layer to use as corpus */
                                  GIMP_PDB_INT32,    -1,                 /* inmask Layer to use as input 
mask, -1 for none */
                                  GIMP_PDB_INT32,    -1,                 /* outmask Layer to use as output 
mask, -1 for none */
@@ -634,54 +1046,170 @@ p_pdb_call_resynthesizer(gint32 image_id, gint32 layer_id, gint32 corpus_layer_i
 static gint32
 p_create_corpus_layer(gint32 image_id, gint32 drawable_id, TransValues *val_ptr)
 {
-  gint32 dup_image_id;
-  gint32 channel_id;
-  gint32 channel_2_id;
-  GimpRGB  bck_color;
-  GimpRGB  white_opaque_color;
-  /* gboolean has_selection; */
+// ## the following comment ist the coresponding part of the script plugin-heal-selection.py that ships with 
resynthesizer
+// ## (this C codede wrapper to the resynthesizer engine shall provide same functionality)
+//   targetBounds = tdrawable.mask_bounds
+// 
+//   # In duplicate image, create the sample (corpus).
+//   # (I tried to use a temporary layer but found it easier to use duplicate image.)
+//   tempImage = pdb.gimp_image_duplicate(timg)
+//   if not tempImage:
+//       raise RuntimeError, "Failed duplicate image"
+//   
+//   # !!! The drawable can be a mask (grayscale channel), don't restrict to layer.
+//   work_drawable = pdb.gimp_image_get_active_drawable(tempImage)
+//   if not work_drawable:
+//       raise RuntimeError, "Failed get active drawable"
+//       
+//   '''
+//   grow and punch hole, making a frisket iow stencil iow donut
+//   
+//   '''
+//   orgSelection = pdb.gimp_selection_save(tempImage) # save for later use
+//   pdb.gimp_selection_grow(tempImage, samplingRadiusParam)
+//   # ??? returns None , docs say it returns SUCCESS
+//   
+//   # !!! Note that if selection is a bordering ring already, growing expanded it inwards.
+//   # Which is what we want, to make a corpus inwards.
+//   
+//   grownSelection = pdb.gimp_selection_save(tempImage)
+//   
+//   # Cut hole where the original selection was, so we don't sample from it.
+//   # !!! Note that gimp enums/constants are not prefixed with GIMP_
+//   pdb.gimp_selection_combine(orgSelection, CHANNEL_OP_SUBTRACT)
+//   
+//   '''
+//   Selection (to be the corpus) is donut or frisket around the original target T
+//     xxx
+//     xTx
+//     xxx
+//   '''
+//   
+//   # crop the temp image to size of selection to save memory and for directional healing!!
+//   frisketBounds = grownSelection.mask_bounds
+//   frisketLowerLeftX = frisketBounds[0]
+//   frisketLowerLeftY = frisketBounds[1]
+//   frisketUpperRightX = frisketBounds[2]
+//   frisketUpperRightY = frisketBounds[3]
+//   targetLowerLeftX = targetBounds[0]
+//   targetLowerLeftY = targetBounds[1]
+//   targetUpperRightX = targetBounds[2]
+//   targetUpperRightY = targetBounds[3]
+//   
+//   frisketWidth = frisketUpperRightX - frisketLowerLeftX
+//   frisketHeight = frisketUpperRightY - frisketLowerLeftY
+//   
+//   # User's choice of direction affects the corpus shape, and is also passed to resynthesizer plugin
+//   if directionParam == 0: # all around
+//       # Crop to the entire frisket
+//       newWidth, newHeight, newLLX, newLLY = ( frisketWidth, frisketHeight, 
+//         frisketLowerLeftX, frisketLowerLeftY )
+//   elif directionParam == 1: # sides
+//       # Crop to target height and frisket width:  XTX
+//       newWidth, newHeight, newLLX, newLLY =  ( frisketWidth, targetUpperRightY-targetLowerLeftY, 
+//         frisketLowerLeftX, targetLowerLeftY )
+//   elif directionParam == 2: # above and below
+//       # X Crop to target width and frisket height
+//       # T
+//       # X
+//       newWidth, newHeight, newLLX, newLLY = ( targetUpperRightX-targetLowerLeftX, frisketHeight, 
+//         targetLowerLeftX, frisketLowerLeftY )
+//   # Restrict crop to image size (condition of gimp_image_crop) eg when off edge of image
+//   newWidth = min(pdb.gimp_image_width(tempImage) - newLLX, newWidth)
+//   newHeight = min(pdb.gimp_image_height(tempImage) - newLLY, newHeight)
+//   pdb.gimp_image_crop(tempImage, newWidth, newHeight, newLLX, newLLY)
+  
+  
+  
+  
+  gint32 tempImage;
+  gint32 origSelectionChannelId;
+  gint32 grownSelectionChannelId;
   gboolean non_empty;
-  gint     x1, y1, x2, y2;
-  gint32   active_layer_stackposition;
-  gint32   active_dup_layer_id;
-
-
-  active_layer_stackposition = gap_layer_get_stackposition(image_id, drawable_id);
-
-  dup_image_id = gimp_image_duplicate(image_id);
-
-  channel_id = gimp_selection_save(dup_image_id);
-  gimp_selection_grow(dup_image_id, val_ptr->corpus_border_radius);
-  gimp_selection_invert(dup_image_id);
-
-  gimp_context_get_background(&bck_color);
-  channel_2_id = gimp_selection_save(dup_image_id);
-
-  gimp_image_select_item(dup_image_id, GIMP_CHANNEL_OP_REPLACE, channel_id);
-
-  gimp_rgba_set_uchar (&white_opaque_color, 255, 255, 255, 255);
-  gimp_context_set_background(&white_opaque_color);
-  gimp_edit_clear(channel_2_id);
-
-
-  gimp_context_set_background(&bck_color);  /* restore original background color */
-
-  gimp_selection_load(channel_2_id);
-
-  gimp_selection_invert(dup_image_id);
-
-  /* has_selection  = */ gimp_selection_bounds(dup_image_id, &non_empty, &x1, &y1, &x2, &y2);
-  gimp_image_crop(dup_image_id, (x2 - x1), (y2 - y1), x1, y1);
+  gint32   work_drawable; // the corpus layer
+  gint targetLowerLeftX;  //= targetBounds[0]
+  gint targetLowerLeftY;  //= targetBounds[1]
+  gint targetUpperRightX; // = targetBounds[2]
+  gint targetUpperRightY; // = targetBounds[3]
+  gint frisketLowerLeftX; // = frisketBounds[0]
+  gint frisketLowerLeftY; // = frisketBounds[1]
+  gint frisketUpperRightX; // = frisketBounds[2]
+  gint frisketUpperRightY; // = frisketBounds[3]
+  gint frisketWidth;
+  gint frisketHeight;
+  gint newWidth;
+  gint newHeight; 
+  gint newLLX; 
+  gint newLLY;
+
+  //active_layer_stackposition = gap_layer_get_stackposition(image_id, drawable_id);
+
+  tempImage = gimp_image_duplicate(image_id);
+  work_drawable = gimp_image_get_active_drawable(tempImage); // gap_layer_get_id_by_stackposition(tempImage, 
active_layer_stackposition);
+
+  /*   targetBounds = tdrawable.mask_bounds */
+  gimp_selection_bounds(tempImage, &non_empty, &targetLowerLeftX, &targetLowerLeftY, &targetUpperRightX, 
&targetUpperRightY);
+
+
+  /* grow and punch hole, making a frisket iow stencil iow donut */
+ 
+  origSelectionChannelId = gimp_selection_save(tempImage);
+  /* # !!! Note that if selection is a bordering ring already, growing expanded it inwards.
+   * # Which is what we want, to make a corpus inwards. 
+   */
+  gimp_selection_grow(tempImage, val_ptr->corpus_border_radius);
+ 
+  grownSelectionChannelId = gimp_selection_save(tempImage);
+  
+  /* # Cut hole where the original selection was, so we don't sample from it.  */
+  //gimp_selection_combine(origSelectionChannelId, GIMP_CHANNEL_OP_SUBTRACT);
+  gimp_image_select_item(tempImage, GIMP_CHANNEL_OP_SUBTRACT, origSelectionChannelId);
+ 
+  /*   Selection (to be the corpus) is donut or frisket around the original target T
+   *    xxx
+   *    xTx
+   *    xxx
+   */
+   
+  /*   frisketBounds = grownSelection.mask_bounds */
+  gimp_selection_bounds(tempImage, &non_empty, &frisketLowerLeftX, &frisketLowerLeftY, &frisketUpperRightX, 
&frisketUpperRightY);
+   
+  /* # crop the temp image to size of selection to save memory and for directional healing!! */
+  frisketWidth = frisketUpperRightX - frisketLowerLeftX;
+  frisketHeight = frisketUpperRightY - frisketLowerLeftY;
+  
+  /* default assume crop settings for "all around" */
+  newWidth = frisketWidth;
+  newHeight = frisketHeight; 
+  newLLX = frisketLowerLeftX; 
+  newLLY = frisketLowerLeftY;
+  
+  if(val_ptr->directionParam == DIRECTION_SIDES) /* # 1 sides */ 
+  {
+    /* # Crop to target height and frisket width:  XTX */
+    newHeight = targetUpperRightY - targetLowerLeftY;
+  }
+  else if(val_ptr->directionParam == DIRECTION_ABOVE_AND_BELOW) /* # 2 above and below */
+  {
+    /* # X Crop to target width and frisket height
+     * # T
+     * # X      
+     */
+    newWidth = targetUpperRightX - targetLowerLeftX;
+  }
 
-  gimp_selection_invert(dup_image_id);
-  active_dup_layer_id = gap_layer_get_id_by_stackposition(dup_image_id, active_layer_stackposition);
+  /*  # Restrict crop to image size (condition of gimp_image_crop) eg when off edge of image */
+  newWidth = MIN(gimp_image_width(tempImage) - newLLX, newWidth);
+  newHeight = MIN(gimp_image_height(tempImage) - newLLY, newHeight);
+  gimp_image_crop(tempImage, newWidth, newHeight, newLLX, newLLY);
+  
 
   if (1==0)
   {
     /* debug code shows the duplicate image by adding a display */
-    gimp_display_new(dup_image_id);
+    gimp_display_new(tempImage);
   }
-  return (active_dup_layer_id);
+  return (work_drawable);
 
 }  /* end p_create_corpus_layer */
 
@@ -732,6 +1260,8 @@ p_process_layer(gint32 image_id, gint32 drawable_id, TransValues *val_ptr)
   if(gap_debug)
   {
     printf("corpus_border_radius: %d\n", (int)val_ptr->corpus_border_radius);
+    printf("directionParam: %d\n", (int)val_ptr->directionParam);
+    printf("orderParam: %d\n", (int)val_ptr->orderParam);
     printf("alt_selection: %d\n", (int)val_ptr->alt_selection);
     printf("seed: %d\n", (int)val_ptr->seed);
   }
@@ -739,6 +1269,12 @@ p_process_layer(gint32 image_id, gint32 drawable_id, TransValues *val_ptr)
   gimp_image_undo_group_start(image_id);
 
 
+  if(val_ptr->alt_selection == SELECTION_FROM_SVG_FILE)
+  {
+    p_set_selection_from_vectors_file(image_id, val_ptr);
+  }
+
+
   trans_drawable_id = -1;
   alt_selection_success = FALSE;
 
@@ -752,6 +1288,15 @@ p_process_layer(gint32 image_id, gint32 drawable_id, TransValues *val_ptr)
   }
 
   has_selection  = gimp_selection_bounds(image_id, &non_empty, &x1, &y1, &x2, &y2);
+  
+  if (non_empty != TRUE)
+  {
+    /* in case of empty selection check if possible can load selection from SVG file (even if not explicite 
requested) */
+    p_set_selection_from_vectors_file(image_id, val_ptr);
+    has_selection  = gimp_selection_bounds(image_id, &non_empty, &x1, &y1, &x2, &y2);
+  }
+  
+  
   if(gap_debug)
   {
     printf("p_process_layer has_selection: %d\n", (int)has_selection);
@@ -767,12 +1312,37 @@ p_process_layer(gint32 image_id, gint32 drawable_id, TransValues *val_ptr)
   {
     gint32 corpus_layer_id;
     gint32 corpus_image_id;
+    gint32 useContext;
+    
+    useContext = 1; /* default random filling */
+    /* # Encode two script params into one resynthesizer param.
+     * # use border 1 means fill target in random order
+     * # use border 0 is for texture mapping operations, not used by this script
+     */
+    switch(val_ptr->orderParam)
+    {
+      case FILL_ORDER_RANDOM:
+        useContext = 1; /* # 0:  User wants NO order, ie random filling */
+        break;
+      case FILL_ORDER_INWARDS_TO_CENTER:           /* # Inward to corpus.  2,3,4 */
+        useContext = val_ptr->directionParam + 2;  /*  # !!! Offset by 2 to get past the original two 
boolean values */
+        break;
+      case FILL_ORDER_OUTWARDS_FROM_CENTER:           /* # Outward from image center.  */
+        /* # Outward from image center.  
+         * # 5+0=5 outward concentric
+         * # 5+1=6 outward from sides
+         * # 5+2=7 outward above and below
+         */
+        useContext = val_ptr->directionParam + 5;
+        break;
+    }
+
 
     trans_drawable_id = drawable_id;
 
     corpus_layer_id = p_create_corpus_layer(image_id, drawable_id, val_ptr);
 
-    p_pdb_call_resynthesizer(image_id, drawable_id, corpus_layer_id, val_ptr->seed);
+    p_pdb_call_resynthesizer(image_id, drawable_id, corpus_layer_id, useContext, val_ptr->seed);
 
     /* delete the temporary working duplicate */
     corpus_image_id = gimp_item_get_image(corpus_layer_id);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]