[gimp-gap] Merged Fixes and new Features for MovePath, ModifyFrames, Onionskin, BlendFill, FrameRename, DetailT
- From: Wolfgang Hofer <wolfgangh src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gimp-gap] Merged Fixes and new Features for MovePath, ModifyFrames, Onionskin, BlendFill, FrameRename, DetailT
- Date: Sat, 15 Apr 2017 13:48:24 +0000 (UTC)
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]